View Source Nx.LinAlg (Nx v0.2.0)

Nx conveniences for linear algebra.

Link to this section Summary

Functions

Returns the adjoint of a given tensor.

Performs a Cholesky decomposition of a square matrix.

Calculates the determinant of a square 2D tensor.

Calculates the Eigenvalues and Eigenvectors of symmetric 2-D tensors.

Inverts a square 2-D tensor.

Calculates the A = PLU decomposition of a 2-D tensor A with shape {N, N}.

Produces the tensor taken to the given power by dot-product.

Calculates the p-norm of a tensor.

Calculates the QR decomposition of a 2-D tensor with shape {M, N}.

Solves the system AX = B.

Calculates the Singular Value Decomposition of 2-D tensors.

Solve the equation a x = b for x, assuming a is a triangular matrix. Can also solve x a = b for x. See the :left_side option below.

Link to this section Functions

Returns the adjoint of a given tensor.

If the input tensor is real, it is the same as Nx.transpose/2. Otherwise, it is the same as tensor |> Nx.transpose(opts) |> Nx.conjugate().

examples

Examples

iex> Nx.LinAlg.adjoint(Nx.tensor([[1, 2], [3, 4]]))
#Nx.Tensor<
  s64[2][2]
  [
    [1, 3],
    [2, 4]
  ]
>

iex> Nx.LinAlg.adjoint(Nx.tensor([[1, Complex.new(0, 2)], [3, Complex.new(0, -4)]]))
#Nx.Tensor<
  c64[2][2]
  [
    [1.0+0.0i, 3.0+0.0i],
    [0.0-2.0i, 0.0+4.0i]
  ]
>

Performs a Cholesky decomposition of a square matrix.

The matrix must be positive-definite and either Hermitian if complex or symmetric if real. An error is raised by the default backend if those conditions are not met. Other backends may emit undefined behaviour.

examples

Examples

iex> Nx.LinAlg.cholesky(Nx.tensor([[20.0, 17.6], [17.6, 16.0]]))
#Nx.Tensor<
  f32[2][2]
  [
    [4.4721360206604, 0.0],
    [3.9354796409606934, 0.7155417203903198]
  ]
>

iex> t = Nx.tensor([
...>   [6.0, 3.0, 4.0, 8.0],
...>   [3.0, 6.0, 5.0, 1.0],
...>   [4.0, 5.0, 10.0, 7.0],
...>   [8.0, 1.0, 7.0, 25.0]
...> ])
iex> Nx.LinAlg.cholesky(t)
#Nx.Tensor<
  f32[4][4]
  [
    [2.4494898319244385, 0.0, 0.0, 0.0],
    [1.2247447967529297, 2.1213204860687256, 0.0, 0.0],
    [1.6329931020736694, 1.4142135381698608, 2.309401035308838, 0.0],
    [3.265986204147339, -1.4142132997512817, 1.5877132415771484, 3.13249135017395]
  ]
>

iex> Nx.LinAlg.cholesky(Nx.tensor([[1.0, Complex.new(0, -2)], [Complex.new(0, 2), 5.0]]))
#Nx.Tensor<
  c64[2][2]
  [
    [1.0+0.0i, 0.0+0.0i],
    [0.0+2.0i, 1.0+0.0i]
  ]
>

error-cases

Error cases

iex> Nx.LinAlg.cholesky(Nx.tensor([[1.0, 2.0], [3.0, 4.0]]))
** (ArgumentError) matrix must be hermitian, a matrix is hermitian iff X = adjoint(X)

Calculates the determinant of a square 2D tensor.

examples

Examples

For 2x2 and 3x3, the results are given by the closed formulas:

iex> Nx.LinAlg.determinant(Nx.tensor([[1, 2], [3, 4]]))
#Nx.Tensor<
  f32
  -2.0
>

iex> Nx.LinAlg.determinant(Nx.tensor([[1.0, 2.0, 3.0], [1.0, -2.0, 3.0], [7.0, 8.0, 9.0]]))
#Nx.Tensor<
  f32
  48.0
>

When there are linearly dependent rows or columns, the determinant is 0:

iex> Nx.LinAlg.determinant(Nx.tensor([[1.0, 0.0], [3.0, 0.0]]))
#Nx.Tensor<
  f32
  0.0
>

iex> Nx.LinAlg.determinant(Nx.tensor([[1.0, 2.0, 3.0], [-1.0, -2.0, -3.0], [4.0, 5.0, 6.0]]))
#Nx.Tensor<
  f32
  0.0
>

The determinant can also be calculated when the axes are bigger than 3:

iex> Nx.LinAlg.determinant(Nx.tensor([
...> [1, 0, 0, 0],
...> [0, 1, 2, 3],
...> [0, 1, -2, 3],
...> [0, 7, 8, 9.0]
...> ]))
#Nx.Tensor<
  f32
  48.0
>

iex> Nx.LinAlg.determinant(Nx.tensor([
...> [0, 0, 0, 0, -1],
...> [0, 1, 2, 3, 0],
...> [0, 1, -2, 3, 0],
...> [0, 7, 8, 9, 0],
...> [1, 0, 0, 0, 0]
...> ]))
#Nx.Tensor<
  f32
  48.0
>

If the axes are named, their names are not preserved in the output:

iex> two_by_two = Nx.tensor([[1, 2], [3, 4]], names: [:x, :y])
iex> Nx.LinAlg.determinant(two_by_two)
#Nx.Tensor<
  f32
  -2.0
>

iex> three_by_three = Nx.tensor([[1.0, 2.0, 3.0], [1.0, -2.0, 3.0], [7.0, 8.0, 9.0]], names: [:x, :y])
iex> Nx.LinAlg.determinant(three_by_three)
#Nx.Tensor<
  f32
  48.0
>

Also supports complex inputs:

iex> t = Nx.tensor([[1, 0, 0], [0, Complex.new(0, 2), 0], [0, 0, 3]])
iex> Nx.LinAlg.determinant(t)
#Nx.Tensor<
  c64
  0.0+6.0i
>

iex> t = Nx.tensor([[0, 0, 0, 1], [0, Complex.new(0, 2), 0, 0], [0, 0, 3, 0], [1, 0, 0, 0]])
iex> Nx.LinAlg.determinant(t)
#Nx.Tensor<
  c64
  -0.0-6.0i
>
Link to this function

eigh(tensor, opts \\ [])

View Source

Calculates the Eigenvalues and Eigenvectors of symmetric 2-D tensors.

It returns {eigenvals, eigenvecs}.

options

Options

  • :max_iter - integer. Defaults to 50_000 Number of maximum iterations before stopping the decomposition

  • :eps - float. Defaults to 1.0e-10 Tolerance applied during the decomposition

Note not all options apply to all backends, as backends may have specific optimizations that render these mechanisms unnecessary.

examples

Examples

iex> {eigenvals, eigenvecs} = Nx.LinAlg.eigh(Nx.tensor([[1, 0], [0, 2]]))
iex> Nx.round(eigenvals)
#Nx.Tensor<
  f32[2]
  [1.0, 2.0]
>
iex> eigenvecs
#Nx.Tensor<
  f32[2][2]
  [
    [1.0, 0.0],
    [0.0, 1.0]
  ]
>

iex> {eigenvals, eigenvecs} = Nx.LinAlg.eigh(Nx.tensor([[0, 1, 2], [1, 0, 2], [2, 2, 3]]))
iex> Nx.round(eigenvals)
#Nx.Tensor<
  f32[3]
  [5.0, -1.0, -1.0]
>
iex> eigenvecs
#Nx.Tensor<
  f32[3][3]
  [
    [0.4082472324371338, 0.9128734469413757, 0.0],
    [0.40824851393699646, -0.18257413804531097, 0.8944271802902222],
    [0.8164970278739929, -0.36514827609062195, -0.4472135901451111]
  ]
>

error-cases

Error cases

iex> Nx.LinAlg.eigh(Nx.tensor([[1, 2, 3], [4, 5, 6]]))
** (ArgumentError) tensor must be a square matrix (a tensor with two equal axes), got shape: {2, 3}

iex> Nx.LinAlg.eigh(Nx.tensor([[1, 2], [3, 4]]))
** (ArgumentError) input tensor must be symmetric

Inverts a square 2-D tensor.

For non-square tensors, use svd/2 for pseudo-inverse calculations.

examples

Examples

iex> a = Nx.tensor([[1, 2, 1, 1], [0, 1, 0, 1], [0, 0, 1, 1], [0 , 0, 0, 1]])
iex> a_inv = Nx.LinAlg.invert(a)
#Nx.Tensor<
  f32[4][4]
  [
    [1.0, -2.0, -1.0, 2.0],
    [0.0, 1.0, 0.0, -1.0],
    [0.0, 0.0, 1.0, -1.0],
    [0.0, 0.0, 0.0, 1.0]
  ]
>
iex> Nx.dot(a, a_inv)
#Nx.Tensor<
  f32[4][4]
  [
    [1.0, 0.0, 0.0, 0.0],
    [0.0, 1.0, 0.0, 0.0],
    [0.0, 0.0, 1.0, 0.0],
    [0.0, 0.0, 0.0, 1.0]
  ]
>
iex> Nx.dot(a_inv, a)
#Nx.Tensor<
  f32[4][4]
  [
    [1.0, 0.0, 0.0, 0.0],
    [0.0, 1.0, 0.0, 0.0],
    [0.0, 0.0, 1.0, 0.0],
    [0.0, 0.0, 0.0, 1.0]
  ]
>

error-cases

Error cases

iex> Nx.LinAlg.invert(Nx.tensor([[3, 0, 0, 0], [2, 1, 0, 0]]))
** (ArgumentError) expected tensor to match shape {n, n}, got tensor with shape {2, 4}

iex> Nx.LinAlg.invert(Nx.tensor([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 1, 1]]))
** (ArgumentError) can't solve for singular matrix

Calculates the A = PLU decomposition of a 2-D tensor A with shape {N, N}.

options

Options

  • :eps - Rounding error threshold that can be applied during the factorization

examples

Examples

iex> {p, l, u} = Nx.LinAlg.lu(Nx.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]]))
iex> p
#Nx.Tensor<
  s64[3][3]
  [
    [0, 0, 1],
    [0, 1, 0],
    [1, 0, 0]
  ]
>
iex> l
#Nx.Tensor<
  f32[3][3]
  [
    [1.0, 0.0, 0.0],
    [0.5714285969734192, 1.0, 0.0],
    [0.1428571492433548, 2.0, 1.0]
  ]
>
iex> u
#Nx.Tensor<
  f32[3][3]
  [
    [7.0, 8.0, 9.0],
    [0.0, 0.4285714328289032, 0.8571428656578064],
    [0.0, 0.0, 0.0]
  ]
>
iex> p |> Nx.dot(l) |> Nx.dot(u)
#Nx.Tensor<
  f32[3][3]
  [
    [1.0, 2.0, 3.0],
    [4.0, 5.0, 6.0],
    [7.0, 8.0, 9.0]
  ]
>

iex> {p, l, u} = Nx.LinAlg.lu(Nx.tensor([[1, 0, 1], [-1, 0, -1], [1, 1, 1]]))
iex> p
#Nx.Tensor<
  s64[3][3]
  [
    [1, 0, 0],
    [0, 0, 1],
    [0, 1, 0]
  ]
>
iex> l
#Nx.Tensor<
  f32[3][3]
  [
    [1.0, 0.0, 0.0],
    [1.0, 1.0, 0.0],
    [-1.0, 0.0, 1.0]
  ]
>
iex> u
#Nx.Tensor<
  f32[3][3]
  [
    [1.0, 0.0, 1.0],
    [0.0, 1.0, 0.0],
    [0.0, 0.0, 0.0]
  ]
>
iex> p |> Nx.dot(l) |> Nx.dot(u)
#Nx.Tensor<
  f32[3][3]
  [
    [1.0, 0.0, 1.0],
    [-1.0, 0.0, -1.0],
    [1.0, 1.0, 1.0]
  ]
>

error-cases

Error cases

iex> Nx.LinAlg.lu(Nx.tensor([[1, 1, 1, 1], [-1, 4, 4, -1], [4, -2, 2, 0]]))
** (ArgumentError) tensor must have as many rows as columns, got shape: {3, 4}
Link to this function

matrix_power(tensor, power)

View Source

Produces the tensor taken to the given power by dot-product.

The input is always a square tensor and a non-negative integer, and the output is a square tensor of the same dimensions as the input tensor.

The dot-products are unrolled inside defn.

examples

Examples

iex> Nx.LinAlg.matrix_power(Nx.tensor([[1, 2], [3, 4]]), 0)
#Nx.Tensor<
  s64[2][2]
  [
    [1, 0],
    [0, 1]
  ]
>

iex> Nx.LinAlg.matrix_power(Nx.tensor([[1, 2], [3, 4]]), 6)
#Nx.Tensor<
  s64[2][2]
  [
    [5743, 8370],
    [12555, 18298]
  ]
>

iex> Nx.LinAlg.matrix_power(Nx.eye(3), 65535)
#Nx.Tensor<
  s64[3][3]
  [
    [1, 0, 0],
    [0, 1, 0],
    [0, 0, 1]
  ]
>

iex> Nx.LinAlg.matrix_power(Nx.tensor([[1, 2], [3, 4]]), -1)
#Nx.Tensor<
  f32[2][2]
  [
    [-2.0, 1.0],
    [1.5, -0.5]
  ]
>

iex> Nx.LinAlg.matrix_power(Nx.tensor([[1, 2], [3, 4], [5, 6]]), 1)
** (ArgumentError) expected tensor to match shape {x, x}, got tensor with shape {3, 2}
Link to this function

norm(tensor, opts \\ [])

View Source

Calculates the p-norm of a tensor.

For the 0-norm, the norm is the number of non-zero elements in the tensor.

options

Options

  • :axes - defines the axes upon which the norm will be calculated. Applies only on 2-norm for 2-D tensors. Default: nil.
  • :ord - defines which norm will be calculated according to the table below. Default: 2
ord2-D1-D
nilFrobenius norm2-norm
:nuclearNuclear norm-
:frobeniusFrobenius norm-
:infmax(sum(abs(x), axes: [1]))max(abs(x))
:neg_infmin(sum(abs(x), axes: [1]))min(abs(x))
0-Number of non-zero elements
1max(sum(abs(x), axes: [0]))as below
-1min(sum(abs(x), axes: [0]))as below
22-normas below
-2smallest singular valueas below
other-power(sum(power(abs(x), p)), 1/p)

examples

Examples

vector-norms

Vector norms

iex> Nx.LinAlg.norm(Nx.tensor([3, 4]))
#Nx.Tensor<
  f32
  5.0
>

iex> Nx.LinAlg.norm(Nx.tensor([3, 4]), ord: 1)
#Nx.Tensor<
  f32
  7.0
>

iex> Nx.LinAlg.norm(Nx.tensor([3, -4]), ord: :inf)
#Nx.Tensor<
  f32
  4.0
>

iex> Nx.LinAlg.norm(Nx.tensor([3, -4]), ord: :neg_inf)
#Nx.Tensor<
  f32
  3.0
>

iex> Nx.LinAlg.norm(Nx.tensor([3, -4, 0, 0]), ord: 0)
#Nx.Tensor<
  f32
  2.0
>

matrix-norms

Matrix norms

iex> Nx.LinAlg.norm(Nx.tensor([[3, -1], [2, -4]]), ord: -1)
#Nx.Tensor<
  f32
  5.0
>

iex> Nx.LinAlg.norm(Nx.tensor([[3, -2], [2, -4]]), ord: 1)
#Nx.Tensor<
  f32
  6.0
>

iex> Nx.LinAlg.norm(Nx.tensor([[3, -2], [2, -4]]), ord: :neg_inf)
#Nx.Tensor<
  f32
  5.0
>

iex> Nx.LinAlg.norm(Nx.tensor([[3, -2], [2, -4]]), ord: :inf)
#Nx.Tensor<
  f32
  6.0
>

iex> Nx.LinAlg.norm(Nx.tensor([[3, 0], [0, -4]]), ord: :frobenius)
#Nx.Tensor<
  f32
  5.0
>

iex> Nx.LinAlg.norm(Nx.tensor([[1, 0, 0], [0, -4, 0], [0, 0, 9]]), ord: :nuclear)
#Nx.Tensor<
  f32
  14.0
>

iex> Nx.LinAlg.norm(Nx.tensor([[1, 0, 0], [0, -4, 0], [0, 0, 9]]), ord: -2)
#Nx.Tensor<
  f32
  1.0
>

iex> Nx.LinAlg.norm(Nx.tensor([[3, 0], [0, -4]]))
#Nx.Tensor<
  f32
  5.0
>

iex> Nx.LinAlg.norm(Nx.tensor([[3, 4], [0, -4]]), axes: [1])
#Nx.Tensor<
  f32[2]
  [5.0, 4.0]
>

iex> Nx.LinAlg.norm(Nx.tensor([[Complex.new(0, 3), 4], [4, 0]]), axes: [0])
#Nx.Tensor<
  f32[2]
  [5.0, 4.0]
>

iex> Nx.LinAlg.norm(Nx.tensor([[Complex.new(0, 3), 0], [4, 0]]), ord: :neg_inf)
#Nx.Tensor<
  f32
  3.0
>

error-cases

Error cases

iex> Nx.LinAlg.norm(Nx.tensor([3, 4]), ord: :frobenius)
** (ArgumentError) expected a 2-D tensor for ord: :frobenius, got a 1-D tensor

Calculates the QR decomposition of a 2-D tensor with shape {M, N}.

options

Options

  • :mode - Can be one of :reduced, :complete. Defaults to :reduced For the following, K = min(M, N)

    • :reduced - returns q and r with shapes {M, K} and {K, N}
    • :complete - returns q and r with shapes {M, M} and {M, N}
  • :eps - Rounding error threshold that can be applied during the triangularization

examples

Examples

iex> {q, r} = Nx.LinAlg.qr(Nx.tensor([[-3, 2, 1], [0, 1, 1], [0, 0, -1]]))
iex> q
#Nx.Tensor<
  f32[3][3]
  [
    [1.0, 0.0, 0.0],
    [0.0, 1.0, 0.0],
    [0.0, 0.0, 1.0]
  ]
>
iex> r
#Nx.Tensor<
  f32[3][3]
  [
    [-3.0, 2.0, 1.0],
    [0.0, 1.0, 1.0],
    [0.0, 0.0, -1.0]
  ]
>

iex> t = Nx.tensor([[3, 2, 1], [0, 1, 1], [0, 0, 1]])
iex> {q, r} = Nx.LinAlg.qr(t)
iex> q
#Nx.Tensor<
  f32[3][3]
  [
    [1.0, 0.0, 0.0],
    [0.0, 1.0, 0.0],
    [0.0, 0.0, 1.0]
  ]
>
iex> r
#Nx.Tensor<
  f32[3][3]
  [
    [3.0, 2.0, 1.0],
    [0.0, 1.0, 1.0],
    [0.0, 0.0, 1.0]
  ]
>

iex> t = Nx.tensor([[3, 2, 1], [0, 1, 1], [0, 0, 1], [0, 0, 1]], type: {:f, 32})
iex> {q, r} = Nx.LinAlg.qr(t, mode: :reduced)
iex> q
#Nx.Tensor<
  f32[4][3]
  [
    [1.0, 0.0, 0.0],
    [0.0, 1.0, 0.0],
    [0.0, 0.0, 0.7071067690849304],
    [0.0, 0.0, 0.7071067690849304]
  ]
>
iex> r
#Nx.Tensor<
  f32[3][3]
  [
    [3.0, 2.0, 1.0],
    [0.0, 1.0, 1.0],
    [0.0, 0.0, 1.4142135381698608]
  ]
>

iex> t = Nx.tensor([[3, 2, 1], [0, 1, 1], [0, 0, 1], [0, 0, 0]], type: {:f, 32})
iex> {q, r} = Nx.LinAlg.qr(t, mode: :complete)
iex> q
#Nx.Tensor<
  f32[4][4]
  [
    [1.0, 0.0, 0.0, 0.0],
    [0.0, 1.0, 0.0, 0.0],
    [0.0, 0.0, 1.0, 0.0],
    [0.0, 0.0, 0.0, 1.0]
  ]
>
iex> r
#Nx.Tensor<
  f32[4][3]
  [
    [3.0, 2.0, 1.0],
    [0.0, 1.0, 1.0],
    [0.0, 0.0, 1.0],
    [0.0, 0.0, 0.0]
  ]
>

error-cases

Error cases

iex> Nx.LinAlg.qr(Nx.tensor([[1, 1, 1, 1], [-1, 4, 4, -1], [4, -2, 2, 0]]))
** (ArgumentError) tensor must have at least as many rows as columns, got shape: {3, 4}

Solves the system AX = B.

A must have shape {n, n} and B must have shape {n, m} or {n}. X has the same shape as B.

examples

Examples

iex> a = Nx.tensor([[1, 3, 2, 1], [2, 1, 0, 0], [1, 0, 1, 0], [1, 1, 1, 1]])
iex> Nx.LinAlg.solve(a, Nx.tensor([-3, 0, 4, -2])) |> Nx.round()
#Nx.Tensor<
  f32[4]
  [1.0, -2.0, 3.0, -4.0]
>

iex> a = Nx.tensor([[1, 0, 1], [1, 1, 0], [1, 1, 1]], type: {:f, 64})
iex> Nx.LinAlg.solve(a, Nx.tensor([0, 2, 1])) |> Nx.round()
#Nx.Tensor<
  f64[3]
  [1.0, 1.0, -1.0]
>

iex> a = Nx.tensor([[1, 0, 1], [1, 1, 0], [0, 1, 1]])
iex> b = Nx.tensor([[2, 2, 3], [2, 2, 4], [2, 0, 1]])
iex> Nx.LinAlg.solve(a, b) |> Nx.round()
#Nx.Tensor<
  f32[3][3]
  [
    [1.0, 2.0, 3.0],
    [1.0, 0.0, 1.0],
    [1.0, 0.0, 0.0]
  ]
>

If the axes are named, their names are not preserved in the output:

iex> a = Nx.tensor([[1, 0, 1], [1, 1, 0], [1, 1, 1]], names: [:x, :y])
iex> Nx.LinAlg.solve(a, Nx.tensor([0, 2, 1], names: [:z])) |> Nx.round()
#Nx.Tensor<
  f32[3]
  [1.0, 1.0, -1.0]
>

error-cases

Error cases

iex> Nx.LinAlg.solve(Nx.tensor([[1, 0], [0, 1]]), Nx.tensor([4, 2, 4, 2]))
** (ArgumentError) `b` tensor has incompatible dimensions, expected {2, 2} or {2}, got: {4}

iex> Nx.LinAlg.solve(Nx.tensor([[3, 0, 0, 0], [2, 1, 0, 0], [1, 1, 1, 1]]), Nx.tensor([4]))
** (ArgumentError) `a` tensor has incompatible dimensions, expected a 2-D tensor with as many rows as columns, got: {3, 4}

Calculates the Singular Value Decomposition of 2-D tensors.

It returns {u, s, vt} where the elements of s are sorted from highest to lowest.

options

Options

  • :max_iter - integer. Defaults to 1000 Number of maximum iterations before stopping the decomposition

  • :eps - float. Defaults to 1.0e-12 Tolerance applied during the decomposition

Note not all options apply to all backends, as backends may have specific optimizations that render these mechanisms unnecessary.

examples

Examples

iex> {u, s, v} = Nx.LinAlg.svd(Nx.tensor([[1, 0, 0], [0, 1, 0], [0, 0, -1]]))
iex> u
#Nx.Tensor<
  f32[3][3]
  [
    [1.0, 0.0, 0.0],
    [0.0, 1.0, 0.0],
    [0.0, 0.0, 1.0]
  ]
>
iex> s
#Nx.Tensor<
  f32[3]
  [1.0, 1.0, 1.0]
>
iex> v
#Nx.Tensor<
  f32[3][3]
  [
    [1.0, 0.0, 0.0],
    [0.0, 1.0, 0.0],
    [0.0, 0.0, -1.0]
  ]
>

iex> {u, s, vt} = Nx.LinAlg.svd(Nx.tensor([[2, 0, 0], [0, 3, 0], [0, 0, -1], [0, 0, 0]]))
iex> u
#Nx.Tensor<
  f32[4][4]
  [
    [1.0, 0.0, 0.0, 0.0],
    [0.0, 1.0, 0.0, 0.0],
    [0.0, 0.0, 1.0, 0.0],
    [0.0, 0.0, 0.0, 1.0]
  ]
>
iex> s
#Nx.Tensor<
  f32[3]
  [3.0, 2.0, 1.0]
>
iex> vt
#Nx.Tensor<
  f32[3][3]
  [
    [0.0, 1.0, 0.0],
    [1.0, 0.0, 0.0],
    [0.0, 0.0, -1.0]
  ]
>
Link to this function

triangular_solve(a, b, opts \\ [])

View Source

Solve the equation a x = b for x, assuming a is a triangular matrix. Can also solve x a = b for x. See the :left_side option below.

b must either be a square matrix with the same dimensions as a or a 1-D tensor with as many rows as a.

options

Options

The following options are defined in order of precedence

  • :transform_a - Defines op(a), depending on its value. Can be one of:
    • :none -> op(a) = a
    • :transpose -> op(a) = transpose(a) Defaults to :none
  • :lower - When true, defines the a matrix as lower triangular. If false, a is upper triangular. Defaults to true
  • :left_side - When true, solves the system as op(A).X = B. Otherwise, solves X.op(A) = B. Defaults to true.

examples

Examples

iex> a = Nx.tensor([[3, 0, 0, 0], [2, 1, 0, 0], [1, 0, 1, 0], [1, 1, 1, 1]])
iex> Nx.LinAlg.triangular_solve(a, Nx.tensor([4, 2, 4, 2]))
#Nx.Tensor<
  f32[4]
  [1.3333333730697632, -0.6666666865348816, 2.6666667461395264, -1.3333333730697632]
>

iex> a = Nx.tensor([[1, 0, 0], [1, 1, 0], [1, 1, 1]], type: {:f, 64})
iex> Nx.LinAlg.triangular_solve(a, Nx.tensor([1, 2, 1]))
#Nx.Tensor<
  f64[3]
  [1.0, 1.0, -1.0]
>

iex> a = Nx.tensor([[1, 0, 0], [1, 1, 0], [0, 1, 1]])
iex> b = Nx.tensor([[1, 2, 3], [2, 2, 4], [2, 0, 1]])
iex> Nx.LinAlg.triangular_solve(a, b)
#Nx.Tensor<
  f32[3][3]
  [
    [1.0, 2.0, 3.0],
    [1.0, 0.0, 1.0],
    [1.0, 0.0, 0.0]
  ]
>

iex> a = Nx.tensor([[1, 1, 1, 1], [0, 1, 0, 1], [0, 0, 1, 2], [0, 0, 0, 3]])
iex> Nx.LinAlg.triangular_solve(a, Nx.tensor([2, 4, 2, 4]), lower: false)
#Nx.Tensor<
  f32[4]
  [-1.3333333730697632, 2.6666667461395264, -0.6666666865348816, 1.3333333730697632]
>

iex> a = Nx.tensor([[1, 0, 0], [1, 1, 0], [1, 2, 1]])
iex> b = Nx.tensor([[0, 2, 1], [1, 1, 0], [3, 3, 1]])
iex> Nx.LinAlg.triangular_solve(a, b, left_side: false)
#Nx.Tensor<
  f32[3][3]
  [
    [-1.0, 0.0, 1.0],
    [0.0, 1.0, 0.0],
    [1.0, 1.0, 1.0]
  ]
>

iex> a = Nx.tensor([[1, 1, 1], [0, 1, 1], [0, 0, 1]], type: {:f, 64})
iex> Nx.LinAlg.triangular_solve(a, Nx.tensor([1, 2, 1]), transform_a: :transpose, lower: false)
#Nx.Tensor<
  f64[3]
  [1.0, 1.0, -1.0]
>

iex> a = Nx.tensor([[1, 0, 0], [1, 1, 0], [1, 1, 1]], type: {:f, 64})
iex> Nx.LinAlg.triangular_solve(a, Nx.tensor([1, 2, 1]), transform_a: :none)
#Nx.Tensor<
  f64[3]
  [1.0, 1.0, -1.0]
>

iex> a = Nx.tensor([[1, 0, 0], [1, 1, 0], [1, 2, 1]])
iex> b = Nx.tensor([[0, 1, 3], [2, 1, 3]])
iex> Nx.LinAlg.triangular_solve(a, b, left_side: false)
#Nx.Tensor<
  f32[2][3]
  [
    [2.0, -5.0, 3.0],
    [4.0, -5.0, 3.0]
  ]
>

iex> a = Nx.tensor([[1, 0, 0], [1, 1, 0], [1, 2, 1]])
iex> b = Nx.tensor([[0, 2], [3, 0], [0, 0]])
iex> Nx.LinAlg.triangular_solve(a, b, left_side: true)
#Nx.Tensor<
  f32[3][2]
  [
    [0.0, 2.0],
    [3.0, -2.0],
    [-6.0, 2.0]
  ]
>

iex> a = Nx.tensor([
...> [1, 0, 0],
...> [1, Complex.new(0, 1), 0],
...> [Complex.new(0, 1), 1, 1]
...>])
iex> b = Nx.tensor([1, -1, Complex.new(3, 3)])
iex> Nx.LinAlg.triangular_solve(a, b)
#Nx.Tensor<
  c64[3]
  [1.0+0.0i, 0.0+2.0i, 3.0+0.0i]
>

error-cases

Error cases

iex> Nx.LinAlg.triangular_solve(Nx.tensor([[3, 0, 0, 0], [2, 1, 0, 0]]), Nx.tensor([4, 2, 4, 2]))
** (ArgumentError) expected a square matrix, got matrix with shape: {2, 4}

iex> Nx.LinAlg.triangular_solve(Nx.tensor([[3, 0, 0, 0], [2, 1, 0, 0], [1, 1, 1, 1], [1, 1, 1, 1]]), Nx.tensor([4]))
** (ArgumentError) incompatible dimensions for a and b on triangular solve

iex> Nx.LinAlg.triangular_solve(Nx.tensor([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 1, 1]]), Nx.tensor([4, 2, 4, 2]))
** (ArgumentError) can't solve for singular matrix

iex> a = Nx.tensor([[1, 0, 0], [1, 1, 0], [1, 1, 1]], type: {:f, 64})
iex> Nx.LinAlg.triangular_solve(a, Nx.tensor([1, 2, 1]), transform_a: :conjugate)
** (ArgumentError) complex numbers not supported yet

iex> a = Nx.tensor([[1, 0, 0], [1, 1, 0], [1, 1, 1]], type: {:f, 64})
iex> Nx.LinAlg.triangular_solve(a, Nx.tensor([1, 2, 1]), transform_a: :other)
** (ArgumentError) invalid value for :transform_a option, expected :none, :transpose, or :conjugate, got: :other