Repository: ITensor/TensorAlgebra.jl
Pull request #52: More matrix factorizations
Author: login: mtfishman
Created: 2025-04-05T22:11:02Z | Merged: 2025-04-07T15:37:54Z

This moves logic for selecting the proper MatrixAlgebraKit.jl matrix factorization from the tensor level to the matrix level by introducing a submodule `TensorAlgebra.MatrixAlgebra`. This enables making some of the tensor factorization definitions more compact by generating them in an `@eval` loop. It also introduces new shorthand factorizations `polar` and `orth`, where left or right can be selected with a keyword argument.


Diff:
diff --git a/Project.toml b/Project.toml
index cf887d5c..9fa42f7b 100644
--- a/Project.toml
+++ b/Project.toml
@@ -1,7 +1,7 @@
 name = "TensorAlgebra"
 uuid = "68bd88dc-f39d-4e12-b2ca-f046b68fcc6a"
 authors = ["ITensor developers <support@itensor.org> and contributors"]
-version = "0.2.9"
+version = "0.2.10"
 
 [deps]
 ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"
diff --git a/src/MatrixAlgebra.jl b/src/MatrixAlgebra.jl
new file mode 100644
index 00000000..a3796b91
--- /dev/null
+++ b/src/MatrixAlgebra.jl
@@ -0,0 +1,136 @@
+module MatrixAlgebra
+
+export eigen,
+  eigen!,
+  eigvals,
+  eigvals!,
+  factorize,
+  factorize!,
+  lq,
+  lq!,
+  orth,
+  orth!,
+  polar,
+  polar!,
+  qr,
+  qr!,
+  svd,
+  svd!,
+  svdvals,
+  svdvals!
+
+using LinearAlgebra: LinearAlgebra
+using MatrixAlgebraKit
+
+for (f, f_full, f_compact) in (
+  (:qr, :qr_full, :qr_compact),
+  (:qr!, :qr_full!, :qr_compact!),
+  (:lq, :lq_full, :lq_compact),
+  (:lq!, :lq_full!, :lq_compact!),
+)
+  @eval begin
+    function $f(A::AbstractMatrix; full::Bool=false, kwargs...)
+      f = full ? $f_full : $f_compact
+      return f(A; kwargs...)
+    end
+  end
+end
+
+for (eigen, eigh_full, eig_full, eigh_trunc, eig_trunc) in (
+  (:eigen, :eigh_full, :eig_full, :eigh_trunc, :eig_trunc),
+  (:eigen!, :eigh_full!, :eig_full!, :eigh_trunc!, :eig_trunc!),
+)
+  @eval begin
+    function $eigen(A::AbstractMatrix; trunc=nothing, ishermitian=nothing, kwargs...)
+      ishermitian = @something ishermitian LinearAlgebra.ishermitian(A)
+      f = if !isnothing(trunc)
+        ishermitian ? $eigh_trunc : $eig_trunc
+      else
+        ishermitian ? $eigh_full : $eig_full
+      end
+      return f(A; kwargs...)
+    end
+  end
+end
+
+for (eigvals, eigh_vals, eig_vals) in
+    ((:eigvals, :eigh_vals, :eig_vals), (:eigvals!, :eigh_vals!, :eig_vals!))
+  @eval begin
+    function $eigvals(A::AbstractMatrix; ishermitian=nothing, kwargs...)
+      ishermitian = @something ishermitian LinearAlgebra.ishermitian(A)
+      f = (ishermitian ? $eigh_vals : $eig_vals)
+      return f(A; kwargs...)
+    end
+  end
+end
+
+for (svd, svd_trunc, svd_full, svd_compact) in (
+  (:svd, :svd_trunc, :svd_full, :svd_compact),
+  (:svd!, :svd_trunc!, :svd_full!, :svd_compact!),
+)
+  @eval begin
+    function $svd(A::AbstractMatrix; full::Bool=false, trunc=nothing, kwargs...)
+      return if !isnothing(trunc)
+        @assert !full "Specified both full and truncation, currently not supported"
+        $svd_trunc(A; trunc, kwargs...)
+      else
+        (full ? $svd_full : $svd_compact)(A; kwargs...)
+      end
+    end
+  end
+end
+
+for (svdvals, svd_vals) in ((:svdvals, :svd_vals), (:svdvals!, :svd_vals!))
+  @eval begin
+    function $svdvals(A::AbstractMatrix; ishermitian=nothing, kwargs...)
+      return $svd_vals(A; kwargs...)
+    end
+  end
+end
+
+for (polar, left_polar, right_polar) in
+    ((:polar, :left_polar, :right_polar), (:polar!, :left_polar!, :right_polar!))
+  @eval begin
+    function $polar(A::AbstractMatrix; side=:left, kwargs...)
+      f = if side == :left
+        $left_polar
+      elseif side == :right
+        $right_polar
+      else
+        throw(ArgumentError("`side=$side` not supported."))
+      end
+      return f(A; kwargs...)
+    end
+  end
+end
+
+for (orth, left_orth, right_orth) in
+    ((:orth, :left_orth, :right_orth), (:orth!, :left_orth!, :right_orth!))
+  @eval begin
+    function $orth(A::AbstractMatrix; side=:left, kwargs...)
+      f = if side == :left
+        $left_orth
+      elseif side == :right
+        $right_orth
+      else
+        throw(ArgumentError("`side=$side` not supported."))
+      end
+      return f(A; kwargs...)
+    end
+  end
+end
+
+for (factorize, orth_f) in ((:factorize, :(MatrixAlgebra.orth)), (:factorize!, :orth!))
+  @eval begin
+    function $factorize(A::AbstractMatrix; orth=:left, kwargs...)
+      f = if orth in (:left, :right)
+        $orth_f
+      else
+        throw(ArgumentError("`orth=$orth` not supported."))
+      end
+      return f(A; side=orth, kwargs...)
+    end
+  end
+end
+
+end
diff --git a/src/TensorAlgebra.jl b/src/TensorAlgebra.jl
index 591133b3..1bc23e44 100644
--- a/src/TensorAlgebra.jl
+++ b/src/TensorAlgebra.jl
@@ -1,7 +1,24 @@
 module TensorAlgebra
 
-export contract, contract!, eigen, eigvals, lq, left_null, qr, right_null, svd, svdvals
+export contract,
+  contract!,
+  eigen,
+  eigvals,
+  factorize,
+  left_null,
+  left_orth,
+  left_polar,
+  lq,
+  qr,
+  right_null,
+  right_orth,
+  right_polar,
+  orth,
+  polar,
+  svd,
+  svdvals
 
+include("MatrixAlgebra.jl")
 include("blockedtuple.jl")
 include("blockedpermutation.jl")
 include("BaseExtensions/BaseExtensions.jl")
diff --git a/src/factorizations.jl b/src/factorizations.jl
index 31aac731..a2fee2d9 100644
--- a/src/factorizations.jl
+++ b/src/factorizations.jl
@@ -1,25 +1,29 @@
 using LinearAlgebra: LinearAlgebra
-using MatrixAlgebraKit:
-  eig_full!,
-  eig_trunc!,
-  eig_vals!,
-  eigh_full!,
-  eigh_trunc!,
-  eigh_vals!,
-  left_null!,
-  left_orth!,
-  left_polar!,
-  lq_full!,
-  lq_compact!,
-  qr_full!,
-  qr_compact!,
-  right_null!,
-  right_orth!,
-  right_polar!,
-  svd_full!,
-  svd_compact!,
-  svd_trunc!,
-  svd_vals!
+using MatrixAlgebraKit: MatrixAlgebraKit
+
+for f in (
+  :qr, :lq, :left_polar, :right_polar, :polar, :left_orth, :right_orth, :orth, :factorize
+)
+  @eval begin
+    function $f(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...)
+      biperm = blockedperm_indexin(Tuple.((labels_A, labels_codomain, labels_domain))...)
+      return $f(A, biperm; kwargs...)
+    end
+    function $f(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...)
+      # tensor to matrix
+      A_mat = fusedims(A, biperm)
+
+      # factorization
+      X, Y = MatrixAlgebra.$f(A_mat; kwargs...)
+
+      # matrix to tensor
+      axes_codomain, axes_domain = blockpermute(axes(A), biperm)
+      axes_X = (axes_codomain..., axes(X, 2))
+      axes_Y = (axes(Y, 1), axes_domain...)
+      return splitdims(X, axes_X), splitdims(Y, axes_Y)
+    end
+  end
+end
 
 """
     qr(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...) -> Q, R
@@ -37,23 +41,7 @@ their labels, or directly through a `biperm`.
 
 See also `MatrixAlgebraKit.qr_full!` and `MatrixAlgebraKit.qr_compact!`.
 """
-function qr(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...)
-  biperm = blockedperm_indexin(Tuple.((labels_A, labels_codomain, labels_domain))...)
-  return qr(A, biperm; kwargs...)
-end
-function qr(A::AbstractArray, biperm::BlockedPermutation{2}; full::Bool=false, kwargs...)
-  # tensor to matrix
-  A_mat = fusedims(A, biperm)
-
-  # factorization
-  Q, R = full ? qr_full!(A_mat; kwargs...) : qr_compact!(A_mat; kwargs...)
-
-  # matrix to tensor
-  axes_codomain, axes_domain = blockpermute(axes(A), biperm)
-  axes_Q = (axes_codomain..., axes(Q, 2))
-  axes_R = (axes(R, 1), axes_domain...)
-  return splitdims(Q, axes_Q), splitdims(R, axes_R)
-end
+qr
 
 """
     lq(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...) -> L, Q
@@ -71,23 +59,87 @@ their labels, or directly through a `biperm`.
 
 See also `MatrixAlgebraKit.lq_full!` and `MatrixAlgebraKit.lq_compact!`.
 """
-function lq(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...)
-  biperm = blockedperm_indexin(Tuple.((labels_A, labels_codomain, labels_domain))...)
-  return lq(A, biperm; kwargs...)
-end
-function lq(A::AbstractArray, biperm::BlockedPermutation{2}; full::Bool=false, kwargs...)
-  # tensor to matrix
-  A_mat = fusedims(A, biperm)
+lq
 
-  # factorization
-  L, Q = (full ? lq_full! : lq_compact!)(A_mat; kwargs...)
+"""
+    left_polar(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...) -> W, P
+    left_polar(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...) -> W, P
 
-  # matrix to tensor
-  axes_codomain, axes_domain = blockpermute(axes(A), biperm)
-  axes_L = (axes_codomain..., axes(L, ndims(L)))
-  axes_Q = (axes(Q, 1), axes_domain...)
-  return splitdims(L, axes_L), splitdims(Q, axes_Q)
-end
+Compute the left polar decomposition of a generic N-dimensional array, by interpreting it as
+a linear map from the domain to the codomain indices. These can be specified either via
+their labels, or directly through a `biperm`.
+
+## Keyword arguments
+
+- Keyword arguments are passed on directly to MatrixAlgebraKit.
+
+See also `MatrixAlgebraKit.left_polar!`.
+"""
+left_polar
+
+"""
+    right_polar(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...) -> P, W
+    right_polar(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...) -> P, W
+
+Compute the right polar decomposition of a generic N-dimensional array, by interpreting it as
+a linear map from the domain to the codomain indices. These can be specified either via
+their labels, or directly through a `biperm`.
+
+## Keyword arguments
+
+- Keyword arguments are passed on directly to MatrixAlgebraKit.
+
+See also `MatrixAlgebraKit.right_polar!`.
+"""
+right_polar
+
+"""
+    left_orth(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...) -> V, C
+    left_orth(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...) -> V, C
+
+Compute the left orthogonal decomposition of a generic N-dimensional array, by interpreting it as
+a linear map from the domain to the codomain indices. These can be specified either via
+their labels, or directly through a `biperm`.
+
+## Keyword arguments
+
+- Keyword arguments are passed on directly to MatrixAlgebraKit.
+
+See also `MatrixAlgebraKit.left_orth!`.
+"""
+left_orth
+
+"""
+    right_orth(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...) -> C, V
+    right_orth(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...) -> C, V
+
+Compute the right orthogonal decomposition of a generic N-dimensional array, by interpreting it as
+a linear map from the domain to the codomain indices. These can be specified either via
+their labels, or directly through a `biperm`.
+
+## Keyword arguments
+
+- Keyword arguments are passed on directly to MatrixAlgebraKit.
+
+See also `MatrixAlgebraKit.right_orth!`.
+"""
+right_orth
+
+"""
+    factorize(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...) -> X, Y
+    factorize(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...) -> X, Y
+
+Compute the decomposition of a generic N-dimensional array, by interpreting it as
+a linear map from the domain to the codomain indices. These can be specified either via
+their labels, or directly through a `biperm`.
+
+## Keyword arguments
+
+- `orth::Symbol=:left`: specify the orthogonality of the decomposition.
+  Currently only `:left` and `:right` are supported.
+- Other keywords are passed on directly to MatrixAlgebraKit.
+"""
+factorize
 
 """
     eigen(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...) -> D, V
@@ -111,25 +163,12 @@ function eigen(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwarg
   biperm = blockedperm_indexin(Tuple.((labels_A, labels_codomain, labels_domain))...)
   return eigen(A, biperm; kwargs...)
 end
-function eigen(
-  A::AbstractArray,
-  biperm::BlockedPermutation{2};
-  trunc=nothing,
-  ishermitian=nothing,
-  kwargs...,
-)
+function eigen(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...)
   # tensor to matrix
   A_mat = fusedims(A, biperm)
 
-  ishermitian = @something ishermitian LinearAlgebra.ishermitian(A_mat)
-
   # factorization
-  f! = if !isnothing(trunc)
-    ishermitian ? eigh_trunc! : eig_trunc!
-  else
-    ishermitian ? eigh_full! : eig_full!
-  end
-  D, V = f!(A_mat; kwargs...)
+  D, V = MatrixAlgebra.eigen!(A_mat; kwargs...)
 
   # matrix to tensor
   axes_codomain, = blockpermute(axes(A), biperm)
@@ -157,15 +196,11 @@ function eigvals(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwa
   biperm = blockedperm_indexin(Tuple.((labels_A, labels_codomain, labels_domain))...)
   return eigvals(A, biperm; kwargs...)
 end
-function eigvals(
-  A::AbstractArray, biperm::BlockedPermutation{2}; ishermitian=nothing, kwargs...
-)
+function eigvals(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...)
   A_mat = fusedims(A, biperm)
-  ishermitian = @something ishermitian LinearAlgebra.ishermitian(A_mat)
-  return (ishermitian ? eigh_vals! : eig_vals!)(A_mat; kwargs...)
+  return MatrixAlgebra.eigvals!(A_mat; kwargs...)
 end
 
-# TODO: separate out the algorithm selection step from the implementation
 """
     svd(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...) -> U, S, Vᴴ
     svd(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...) -> U, S, Vᴴ
@@ -187,23 +222,12 @@ function svd(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs.
   biperm = blockedperm_indexin(Tuple.((labels_A, labels_codomain, labels_domain))...)
   return svd(A, biperm; kwargs...)
 end
-function svd(
-  A::AbstractArray,
-  biperm::BlockedPermutation{2};
-  full::Bool=false,
-  trunc=nothing,
-  kwargs...,
-)
+function svd(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...)
   # tensor to matrix
   A_mat = fusedims(A, biperm)
 
   # factorization
-  if !isnothing(trunc)
-    @assert !full "Specified both full and truncation, currently not supported"
-    U, S, Vᴴ = svd_trunc!(A_mat; trunc, kwargs...)
-  else
-    U, S, Vᴴ = full ? svd_full!(A_mat; kwargs...) : svd_compact!(A_mat; kwargs...)
-  end
+  U, S, Vᴴ = MatrixAlgebra.svd!(A_mat; kwargs...)
 
   # matrix to tensor
   axes_codomain, axes_domain = blockpermute(axes(A), biperm)
@@ -228,7 +252,7 @@ function svdvals(A::AbstractArray, labels_A, labels_codomain, labels_domain)
 end
 function svdvals(A::AbstractArray, biperm::BlockedPermutation{2})
   A_mat = fusedims(A, biperm)
-  return svd_vals!(A_mat)
+  return MatrixAlgebra.svdvals!(A_mat)
 end
 
 """
@@ -254,7 +278,7 @@ function left_null(A::AbstractArray, labels_A, labels_codomain, labels_domain; k
 end
 function left_null(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...)
   A_mat = fusedims(A, biperm)
-  N = left_null!(A_mat; kwargs...)
+  N = MatrixAlgebraKit.left_null!(A_mat; kwargs...)
   axes_codomain, _ = blockpermute(axes(A), biperm)
   axes_N = (axes_codomain..., axes(N, 2))
   N_tensor = splitdims(N, axes_N)
@@ -284,168 +308,8 @@ function right_null(A::AbstractArray, labels_A, labels_codomain, labels_domain;
 end
 function right_null(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...)
   A_mat = fusedims(A, biperm)
-  Nᴴ = right_null!(A_mat; kwargs...)
+  Nᴴ = MatrixAlgebraKit.right_null!(A_mat; kwargs...)
   _, axes_domain = blockpermute(axes(A), biperm)
   axes_Nᴴ = (axes(Nᴴ, 1), axes_domain...)
   return splitdims(Nᴴ, axes_Nᴴ)
 end
-
-"""
-    left_polar(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...) -> W, P
-    left_polar(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...) -> W, P
-
-Compute the left polar decomposition of a generic N-dimensional array, by interpreting it as
-a linear map from the domain to the codomain indices. These can be specified either via
-their labels, or directly through a `biperm`.
-
-## Keyword arguments
-
-- Keyword arguments are passed on directly to MatrixAlgebraKit.
-
-See also `MatrixAlgebraKit.left_polar!`.
-"""
-function left_polar(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...)
-  biperm = blockedperm_indexin(Tuple.((labels_A, labels_codomain, labels_domain))...)
-  return left_polar(A, biperm; kwargs...)
-end
-function left_polar(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...)
-  # tensor to matrix
-  A_mat = fusedims(A, biperm)
-
-  # factorization
-  W, P = left_polar!(A_mat; kwargs...)
-
-  # matrix to tensor
-  axes_codomain, axes_domain = blockpermute(axes(A), biperm)
-  axes_W = (axes_codomain..., axes(W, 2))
-  axes_P = (axes(P, 1), axes_domain...)
-  return splitdims(W, axes_W), splitdims(P, axes_P)
-end
-
-"""
-    right_polar(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...) -> P, W
-    right_polar(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...) -> P, W
-
-Compute the right polar decomposition of a generic N-dimensional array, by interpreting it as
-a linear map from the domain to the codomain indices. These can be specified either via
-their labels, or directly through a `biperm`.
-
-## Keyword arguments
-
-- Keyword arguments are passed on directly to MatrixAlgebraKit.
-
-See also `MatrixAlgebraKit.right_polar!`.
-"""
-function right_polar(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...)
-  biperm = blockedperm_indexin(Tuple.((labels_A, labels_codomain, labels_domain))...)
-  return right_polar(A, biperm; kwargs...)
-end
-function right_polar(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...)
-  # tensor to matrix
-  A_mat = fusedims(A, biperm)
-
-  # factorization
-  P, W = right_polar!(A_mat; kwargs...)
-
-  # matrix to tensor
-  axes_codomain, axes_domain = blockpermute(axes(A), biperm)
-  axes_P = (axes_codomain..., axes(P, ndims(P)))
-  axes_W = (axes(W, 1), axes_domain...)
-  return splitdims(P, axes_P), splitdims(W, axes_W)
-end
-
-"""
-    left_orth(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...) -> V, C
-    left_orth(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...) -> V, C
-
-Compute the left orthogonal decomposition of a generic N-dimensional array, by interpreting it as
-a linear map from the domain to the codomain indices. These can be specified either via
-their labels, or directly through a `biperm`.
-
-## Keyword arguments
-
-- Keyword arguments are passed on directly to MatrixAlgebraKit.
-
-See also `MatrixAlgebraKit.left_orth!`.
-"""
-function left_orth(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...)
-  biperm = blockedperm_indexin(Tuple.((labels_A, labels_codomain, labels_domain))...)
-  return left_orth(A, biperm; kwargs...)
-end
-function left_orth(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...)
-  # tensor to matrix
-  A_mat = fusedims(A, biperm)
-
-  # factorization
-  V, C = left_orth!(A_mat; kwargs...)
-
-  # matrix to tensor
-  axes_codomain, axes_domain = blockpermute(axes(A), biperm)
-  axes_V = (axes_codomain..., axes(V, 2))
-  axes_C = (axes(C, 1), axes_domain...)
-  return splitdims(V, axes_V), splitdims(C, axes_C)
-end
-
-"""
-    right_orth(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...) -> C, V
-    right_orth(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...) -> C, V
-
-Compute the right orthogonal decomposition of a generic N-dimensional array, by interpreting it as
-a linear map from the domain to the codomain indices. These can be specified either via
-their labels, or directly through a `biperm`.
-
-## Keyword arguments
-
-- Keyword arguments are passed on directly to MatrixAlgebraKit.
-
-See also `MatrixAlgebraKit.right_orth!`.
-"""
-function right_orth(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...)
-  biperm = blockedperm_indexin(Tuple.((labels_A, labels_codomain, labels_domain))...)
-  return right_orth(A, biperm; kwargs...)
-end
-function right_orth(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...)
-  # tensor to matrix
-  A_mat = fusedims(A, biperm)
-
-  # factorization
-  P, W = right_orth!(A_mat; kwargs...)
-
-  # matrix to tensor
-  axes_codomain, axes_domain = blockpermute(axes(A), biperm)
-  axes_P = (axes_codomain..., axes(P, ndims(P)))
-  axes_W = (axes(W, 1), axes_domain...)
-  return splitdims(P, axes_P), splitdims(W, axes_W)
-end
-
-"""
-    factorize(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...) -> X, Y
-    factorize(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...) -> X, Y
-
-Compute the decomposition of a generic N-dimensional array, by interpreting it as
-a linear map from the domain to the codomain indices. These can be specified either via
-their labels, or directly through a `biperm`.
-
-## Keyword arguments
-
-- `orth::Symbol=:left`: specify the orthogonality of the decomposition.
-  Currently only `:left` and `:right` are supported.
-- Other keywords are passed on directly to MatrixAlgebraKit.
-"""
-function factorize(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...)
-  biperm = blockedperm_indexin(Tuple.((labels_A, labels_codomain, labels_domain))...)
-  return factorize(A, biperm; kwargs...)
-end
-function factorize(A::AbstractArray, biperm::BlockedPermutation{2}; orth=:left, kwargs...)
-  # tensor to matrix
-  A_mat = fusedims(A, biperm)
-
-  # factorization
-  X, Y = (orth == :left ? left_orth! : right_orth!)(A_mat; kwargs...)
-
-  # matrix to tensor
-  axes_codomain, axes_domain = blockpermute(axes(A), biperm)
-  axes_X = (axes_codomain..., axes(X, ndims(X)))
-  axes_Y = (axes(Y, 1), axes_domain...)
-  return splitdims(X, axes_X), splitdims(Y, axes_Y)
-end
diff --git a/test/test_exports.jl b/test/test_exports.jl
index dbef64fc..8dba1bfb 100644
--- a/test/test_exports.jl
+++ b/test/test_exports.jl
@@ -7,12 +7,42 @@ using Test: @test, @testset
     :contract!,
     :eigen,
     :eigvals,
+    :factorize,
     :left_null,
+    :left_orth,
+    :left_polar,
     :lq,
+    :orth,
+    :polar,
     :qr,
     :right_null,
+    :right_orth,
+    :right_polar,
     :svd,
     :svdvals,
   ]
   @test issetequal(names(TensorAlgebra), exports)
+
+  exports = [
+    :MatrixAlgebra,
+    :eigen,
+    :eigen!,
+    :eigvals,
+    :eigvals!,
+    :factorize,
+    :factorize!,
+    :lq,
+    :lq!,
+    :orth,
+    :orth!,
+    :polar,
+    :polar!,
+    :qr,
+    :qr!,
+    :svd,
+    :svd!,
+    :svdvals,
+    :svdvals!,
+  ]
+  @test issetequal(names(TensorAlgebra.MatrixAlgebra), exports)
 end
diff --git a/test/test_factorizations.jl b/test/test_factorizations.jl
index 62b15618..0c5ccdf4 100644
--- a/test/test_factorizations.jl
+++ b/test/test_factorizations.jl
@@ -10,6 +10,8 @@ using TensorAlgebra:
   left_orth,
   left_polar,
   lq,
+  orth,
+  polar,
   qr,
   right_null,
   right_orth,
@@ -216,11 +218,16 @@ end
   labels_P = (:d, :c)
 
   Acopy = deepcopy(A)
-  W, P = left_polar(A, labels_A, labels_W, labels_P)
-  @test A == Acopy # should not have altered initial array
-  A′ = contract(labels_A, W, (labels_W..., :w), P, (:w, labels_P...))
-  @test A ≈ A′
-  @test size(W, 3) == min(size(A, 1) * size(A, 2), size(A, 3) * size(A, 4))
+  for (W, P) in (
+    left_polar(A, labels_A, labels_W, labels_P),
+    polar(A, labels_A, labels_W, labels_P; side=:left),
+    polar(A, labels_A, labels_W, labels_P),
+  )
+    @test A == Acopy # should not have altered initial array
+    A′ = contract(labels_A, W, (labels_W..., :w), P, (:w, labels_P...))
+    @test A ≈ A′
+    @test size(W, 3) == min(size(A, 1) * size(A, 2), size(A, 3) * size(A, 4))
+  end
 end
 
 @testset "Right polar ($T)" for T in elts
@@ -230,11 +237,15 @@ end
   labels_W = (:d, :c)
 
   Acopy = deepcopy(A)
-  P, W = right_polar(A, labels_A, labels_P, labels_W)
-  @test A == Acopy # should not have altered initial array
-  A′ = contract(labels_A, P, (labels_P..., :w), W, (:w, labels_W...))
-  @test A ≈ A′
-  @test size(W, 1) == min(size(A, 1) * size(A, 2), size(A, 3) * size(A, 4))
+  for (P, W) in (
+    right_polar(A, labels_A, labels_P, labels_W),
+    polar(A, labels_A, labels_P, labels_W; side=:right),
+  )
+    @test A == Acopy # should not have altered initial array
+    A′ = contract(labels_A, P, (labels_P..., :w), W, (:w, labels_W...))
+    @test A ≈ A′
+    @test size(W, 1) == min(size(A, 1) * size(A, 2), size(A, 3) * size(A, 4))
+  end
 end
 
 @testset "Left orth ($T)" for T in elts
@@ -244,11 +255,16 @@ end
   labels_P = (:d, :c)
 
   Acopy = deepcopy(A)
-  W, P = left_orth(A, labels_A, labels_W, labels_P)
-  @test A == Acopy # should not have altered initial array
-  A′ = contract(labels_A, W, (labels_W..., :w), P, (:w, labels_P...))
-  @test A ≈ A′
-  @test size(W, 3) == min(size(A, 1) * size(A, 2), size(A, 3) * size(A, 4))
+  for (W, P) in (
+    left_orth(A, labels_A, labels_W, labels_P),
+    orth(A, labels_A, labels_W, labels_P; side=:left),
+    orth(A, labels_A, labels_W, labels_P),
+  )
+    @test A == Acopy # should not have altered initial array
+    A′ = contract(labels_A, W, (labels_W..., :w), P, (:w, labels_P...))
+    @test A ≈ A′
+    @test size(W, 3) == min(size(A, 1) * size(A, 2), size(A, 3) * size(A, 4))
+  end
 end
 
 @testset "Right orth ($T)" for T in elts
@@ -258,11 +274,15 @@ end
   labels_W = (:d, :c)
 
   Acopy = deepcopy(A)
-  P, W = right_orth(A, labels_A, labels_P, labels_W)
-  @test A == Acopy # should not have altered initial array
-  A′ = contract(labels_A, P, (labels_P..., :w), W, (:w, labels_W...))
-  @test A ≈ A′
-  @test size(W, 1) == min(size(A, 1) * size(A, 2), size(A, 3) * size(A, 4))
+  for (P, W) in (
+    right_orth(A, labels_A, labels_P, labels_W),
+    orth(A, labels_A, labels_P, labels_W; side=:right),
+  )
+    @test A == Acopy # should not have altered initial array
+    A′ = contract(labels_A, P, (labels_P..., :w), W, (:w, labels_W...))
+    @test A ≈ A′
+    @test size(W, 1) == min(size(A, 1) * size(A, 2), size(A, 3) * size(A, 4))
+  end
 end
 
 @testset "factorize ($T)" for T in elts
diff --git a/test/test_matrixalgebra.jl b/test/test_matrixalgebra.jl
new file mode 100644
index 00000000..7a391c37
--- /dev/null
+++ b/test/test_matrixalgebra.jl
@@ -0,0 +1,148 @@
+using LinearAlgebra: I, diag, isposdef
+using TensorAlgebra.MatrixAlgebra: MatrixAlgebra
+using Test: @test, @testset
+
+elts = (Float32, Float64, ComplexF32, ComplexF64)
+
+@testset "TensorAlgebra.MatrixAlgebra (elt=$elt)" for elt in elts
+  A = randn(elt, 3, 2)
+  for positive in (false, true)
+    for (Q, R) in (MatrixAlgebra.qr(A; positive), MatrixAlgebra.qr(A; full=false, positive))
+      @test A ≈ Q * R
+      @test size(Q) == size(A)
+      @test size(R) == (size(A, 2), size(A, 2))
+      @test Q' * Q ≈ I
+      @test Q * Q' ≉ I
+      if positive
+        @test all(≥(0), real(diag(R)))
+        @test all(≈(0), imag(diag(R)))
+      end
+    end
+  end
+
+  A = randn(elt, 3, 2)
+  for positive in (false, true)
+    Q, R = MatrixAlgebra.qr(A; full=true, positive)
+    @test A ≈ Q * R
+    @test size(Q) == (size(A, 1), size(A, 1))
+    @test size(R) == size(A)
+    @test Q' * Q ≈ I
+    @test Q * Q' ≈ I
+    if positive
+      @test all(≥(0), real(diag(R)))
+      @test all(≈(0), imag(diag(R)))
+    end
+  end
+
+  A = randn(elt, 2, 3)
+  for positive in (false, true)
+    for (L, Q) in (MatrixAlgebra.lq(A; positive), MatrixAlgebra.lq(A; full=false, positive))
+      @test A ≈ L * Q
+      @test size(L) == (size(A, 1), size(A, 1))
+      @test size(Q) == size(A)
+      @test Q * Q' ≈ I
+      @test Q' * Q ≉ I
+      if positive
+        @test all(≥(0), real(diag(L)))
+        @test all(≈(0), imag(diag(L)))
+      end
+    end
+  end
+
+  A = randn(elt, 3, 2)
+  for positive in (false, true)
+    L, Q = MatrixAlgebra.lq(A; full=true, positive)
+    @test A ≈ L * Q
+    @test size(L) == size(A)
+    @test size(Q) == (size(A, 2), size(A, 2))
+    @test Q * Q' ≈ I
+    @test Q' * Q ≈ I
+    if positive
+      @test all(≥(0), real(diag(L)))
+      @test all(≈(0), imag(diag(L)))
+    end
+  end
+
+  A = randn(elt, 3, 2)
+  for (W, C) in (MatrixAlgebra.orth(A), MatrixAlgebra.orth(A; side=:left))
+    @test A ≈ W * C
+    @test size(W) == size(A)
+    @test size(C) == (size(A, 2), size(A, 2))
+    @test W' * W ≈ I
+    @test W * W' ≉ I
+  end
+
+  A = randn(elt, 2, 3)
+  C, W = MatrixAlgebra.orth(A; side=:right)
+  @test A ≈ C * W
+  @test size(C) == (size(A, 1), size(A, 1))
+  @test size(W) == size(A)
+  @test W * W' ≈ I
+  @test W' * W ≉ I
+
+  A = randn(elt, 3, 2)
+  for (W, P) in (MatrixAlgebra.polar(A), MatrixAlgebra.polar(A; side=:left))
+    @test A ≈ W * P
+    @test size(W) == size(A)
+    @test size(P) == (size(A, 2), size(A, 2))
+    @test W' * W ≈ I
+    @test W * W' ≉ I
+    @test isposdef(P)
+  end
+
+  A = randn(elt, 2, 3)
+  P, W = MatrixAlgebra.polar(A; side=:right)
+  @test A ≈ P * W
+  @test size(P) == (size(A, 1), size(A, 1))
+  @test size(W) == size(A)
+  @test W * W' ≈ I
+  @test W' * W ≉ I
+  @test isposdef(P)
+
+  A = randn(elt, 3, 2)
+  for (W, C) in (MatrixAlgebra.factorize(A), MatrixAlgebra.factorize(A; orth=:left))
+    @test A ≈ W * C
+    @test size(W) == size(A)
+    @test size(C) == (size(A, 2), size(A, 2))
+    @test W' * W ≈ I
+    @test W * W' ≉ I
+  end
+
+  A = randn(elt, 2, 3)
+  C, W = MatrixAlgebra.factorize(A; orth=:right)
+  @test A ≈ C * W
+  @test size(C) == (size(A, 1), size(A, 1))
+  @test size(W) == size(A)
+  @test W * W' ≈ I
+  @test W' * W ≉ I
+
+  A = randn(elt, 3, 3)
+  D, V = MatrixAlgebra.eigen(A)
+  @test A * V ≈ V * D
+  @test MatrixAlgebra.eigvals(A) ≈ diag(D)
+
+  A = randn(elt, 3, 2)
+  for (U, S, V) in (MatrixAlgebra.svd(A), MatrixAlgebra.svd(A; full=false))
+    @test A ≈ U * S * V
+    @test size(U) == size(A)
+    @test size(S) == (size(A, 2), size(A, 2))
+    @test size(V) == (size(A, 2), size(A, 2))
+    @test U' * U ≈ I
+    @test U * U' ≉ I
+    @test V * V' ≈ I
+    @test V' * V ≈ I
+    @test MatrixAlgebra.svdvals(A) ≈ diag(S)
+  end
+
+  A = randn(elt, 3, 2)
+  U, S, V = MatrixAlgebra.svd(A; full=true)
+  @test A ≈ U * S * V
+  @test size(U) == (size(A, 1), size(A, 1))
+  @test size(S) == size(A)
+  @test size(V) == (size(A, 2), size(A, 2))
+  @test U' * U ≈ I
+  @test U * U' ≈ I
+  @test V * V' ≈ I
+  @test V' * V ≈ I
+  @test MatrixAlgebra.svdvals(A) ≈ diag(S)
+end


Reviews:
user: login: lkdvos
body: Definitely a good idea to have a submodule for this.

I feels a bit odd that there are some functions that exist there and some that do not (eg `svdvals`), but I guess `MatrixAlgebra` is not meant to be used outside of this package and then this does not matter?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-06T19:39:18Z
user: login: lkdvos
body: Overall looks good to me!

Just wanted to share some thoughts I had while reading through this, in the end this is completely subjective and shouldn't have any major implications, but I did want to know how you feel about this:

While the `@eval` loops are nice, there are definitely also some drawbacks associated to them:
- I do feel it becomes more involved to read the source code and immediately capture what is going on. 
- Static analysis tools have a harder time dealing with this, for example Cthulhu combined with VSCode has a harder time mapping type analysis to source code statements (since the source code statements are somewhat hidden).
- It becomes somewhat tricky to debug when you want to insert some print statements.

All of this just to highlight that macros don't come without drawbacks.
Here, in most cases I do think they are warranted and nice, but for example for the "loop"s over `eigen` and `eigen!`, and similarly the other in-place vs non-inplace versions I'm a bit more on the fence: the benefit in terms of convenience might not outweigh the drawbacks. Ultimately I don't think there is a "right" choice, but I don't think it's bad to bring this up and actively consider it every now and again :)
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T14:52:34Z

Review comments:
diff_hunk: @@ -1,25 +1,42 @@
 using LinearAlgebra: LinearAlgebra
-using MatrixAlgebraKit:
-  eig_full!,
-  eig_trunc!,
-  eig_vals!,
-  eigh_full!,
-  eigh_trunc!,
-  eigh_vals!,
-  left_null!,
-  left_orth!,
-  left_polar!,
-  lq_full!,
-  lq_compact!,
-  qr_full!,
-  qr_compact!,
-  right_null!,
-  right_orth!,
-  right_polar!,
-  svd_full!,
-  svd_compact!,
-  svd_trunc!,
-  svd_vals!
+using .MatrixAlgebra: MatrixAlgebra
path: src/factorizations.jl
user: login: lkdvos
body: ```suggestion
```
This is already included in `TensorAlgebra.jl`.
I think it could be clearer to move all imports to that file, to avoid these kinds of things.
created_at: 2025-04-06T19:21:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,6 +2,8 @@ module TensorAlgebra
 
 export contract, contract!, eigen, eigvals, lq, left_null, qr, right_null, svd, svdvals
 
+include("MatrixAlgebra.jl")
+using .MatrixAlgebra: MatrixAlgebra
path: src/TensorAlgebra.jl
user: login: lkdvos
body: ```suggestion
```
If we don't plan to import any of the names explicitly, I think this isn't even necessary, defining a module makes it available:
```julia
julia> module Test
       f(x) = x
       end
Main.Test

julia> Test.f(1)
1
```
created_at: 2025-04-06T19:22:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,25 +1,42 @@
 using LinearAlgebra: LinearAlgebra
-using MatrixAlgebraKit:
-  eig_full!,
-  eig_trunc!,
-  eig_vals!,
-  eigh_full!,
-  eigh_trunc!,
-  eigh_vals!,
-  left_null!,
-  left_orth!,
-  left_polar!,
-  lq_full!,
-  lq_compact!,
-  qr_full!,
-  qr_compact!,
-  right_null!,
-  right_orth!,
-  right_polar!,
-  svd_full!,
-  svd_compact!,
-  svd_trunc!,
-  svd_vals!
+using .MatrixAlgebra: MatrixAlgebra
+using MatrixAlgebraKit: MatrixAlgebraKit
+
+function factorize_with(f, A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...)
+  # tensor to matrix
+  A_mat = fusedims(A, biperm)
+
+  # factorization
+  X, Y = f(A_mat; kwargs...)
+
+  # matrix to tensor
+  axes_codomain, axes_domain = blockpermute(axes(A), biperm)
+  axes_X = (axes_codomain..., axes(X, 2))
+  axes_Y = (axes(Y, 1), axes_domain...)
+  return splitdims(X, axes_X), splitdims(Y, axes_Y)
+end
+
+for (f, f_mat) in (
+  (:qr, :(MatrixAlgebra.qr)),
+  (:lq, :(MatrixAlgebra.lq)),
+  (:left_polar, :(MatrixAlgebra.left_polar)),
+  (:right_polar, :(MatrixAlgebra.right_polar)),
+  (:polar, :(MatrixAlgebra.polar)),
+  (:left_orth, :(MatrixAlgebra.left_orth)),
+  (:right_orth, :(MatrixAlgebra.right_orth)),
+  (:orth, :(MatrixAlgebra.orth)),
+  (:factorize, :(MatrixAlgebra.factorize)),
+)
+  @eval begin
+    function $f(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...)
+      biperm = blockedperm_indexin(Tuple.((labels_A, labels_codomain, labels_domain))...)
+      return $f(A, biperm; kwargs...)
+    end
+    function $f(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...)
+      return factorize_with($f_mat, A, biperm; kwargs...)
+    end
+  end
+end
path: src/factorizations.jl
user: login: lkdvos
body: ```suggestion
for f in (:qr, :lq, :left_polar, :right_polar, :polar, :left_orth, :right_orth, :orth, :factorize)
  @eval begin
    function $f(A::AbstractArray, labels_A, labels_codomain, labels_domain; kwargs...)
      biperm = blockedperm_indexin(Tuple.((labels_A, labels_codomain, labels_domain))...)
      return $f(A, biperm; kwargs...)
    end
    function $f(A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...)
      return factorize_with(MatrixAlgebra.$f, A, biperm; kwargs...)
    end
  end
end
```
created_at: 2025-04-06T19:25:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,25 +1,42 @@
 using LinearAlgebra: LinearAlgebra
-using MatrixAlgebraKit:
-  eig_full!,
-  eig_trunc!,
-  eig_vals!,
-  eigh_full!,
-  eigh_trunc!,
-  eigh_vals!,
-  left_null!,
-  left_orth!,
-  left_polar!,
-  lq_full!,
-  lq_compact!,
-  qr_full!,
-  qr_compact!,
-  right_null!,
-  right_orth!,
-  right_polar!,
-  svd_full!,
-  svd_compact!,
-  svd_trunc!,
-  svd_vals!
+using .MatrixAlgebra: MatrixAlgebra
+using MatrixAlgebraKit: MatrixAlgebraKit
+
+function factorize_with(f, A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...)
path: src/factorizations.jl
user: login: lkdvos
body: By default this does not specialize on `f`, so this might alter type stability a bit (make it less likely that the constprop of the kwargs that influence the output type work)
Since we're using this in the eval loop anyways, it might make sense to simply copy-paste this there directly instead?
created_at: 2025-04-06T19:28:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -161,11 +213,9 @@ function eigvals(
   A::AbstractArray, biperm::BlockedPermutation{2}; ishermitian=nothing, kwargs...
path: src/factorizations.jl
user: login: lkdvos
body: ```suggestion
  A::AbstractArray, biperm::BlockedPermutation{2}; kwargs...
```
I think this was discarding the `ishermitian` flag and not passing it on before
created_at: 2025-04-06T19:29:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,143 @@
+module MatrixAlgebra
+
+using LinearAlgebra: LinearAlgebra
+using MatrixAlgebraKit:
+  eig_full,
+  eig_full!,
+  eig_trunc,
+  eig_trunc!,
+  eig_vals,
+  eig_vals!,
+  eigh_full,
+  eigh_full!,
+  eigh_trunc,
+  eigh_trunc!,
+  eigh_vals,
+  eigh_vals!,
+  left_orth,
+  left_orth!,
+  left_polar,
+  left_polar!,
+  lq_full,
+  lq_full!,
+  lq_compact,
+  lq_compact!,
+  qr_full,
+  qr_full!,
+  qr_compact,
+  qr_compact!,
+  right_orth,
+  right_orth!,
+  right_polar,
+  right_polar!,
+  svd_full,
+  svd_full!,
+  svd_compact,
+  svd_compact!,
+  svd_trunc,
+  svd_trunc!
path: src/MatrixAlgebra.jl
user: login: lkdvos
body: ```suggestion
using MatrixAlgebraKit
```
Given that we're importing almost everything, and this is contained in a submodule, we could simplify this here
created_at: 2025-04-06T19:32:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,6 +2,8 @@ module TensorAlgebra
 
 export contract, contract!, eigen, eigvals, lq, left_null, qr, right_null, svd, svdvals
path: src/TensorAlgebra.jl
user: login: lkdvos
body: I didn't get the chance to comment on this before, but I think the export list also needs to be updated
created_at: 2025-04-06T19:35:11Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-04-05T22:12:36Z
body: ## [Codecov](https://app.codecov.io/gh/ITensor/TensorAlgebra.jl/pull/52?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITensor) Report
Attention: Patch coverage is `96.55172%` with `2 lines` in your changes missing coverage. Please review.
> Project coverage is 95.42%. Comparing base [(`43553e3`)](https://app.codecov.io/gh/ITensor/TensorAlgebra.jl/commit/43553e3782bd79fbc7f348942d30eb7b46251963?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITensor) to head [(`89c8d33`)](https://app.codecov.io/gh/ITensor/TensorAlgebra.jl/commit/89c8d33da38a4669d12eb8a9030268d0fedcc1dd?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITensor).
> Report is 1 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/ITensor/TensorAlgebra.jl/pull/52?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITensor) | Patch % | Lines |
|---|---|---|
| [src/MatrixAlgebra.jl](https://app.codecov.io/gh/ITensor/TensorAlgebra.jl/pull/52?src=pr&el=tree&filepath=src%2FMatrixAlgebra.jl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITensor#diff-c3JjL01hdHJpeEFsZ2VicmEuamw=) | 94.87% | [2 Missing :warning: ](https://app.codecov.io/gh/ITensor/TensorAlgebra.jl/pull/52?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITensor) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main      #52      +/-   ##
==========================================
- Coverage   95.91%   95.42%   -0.49%     
==========================================
  Files          14       15       +1     
  Lines         489      459      -30     
==========================================
- Hits          469      438      -31     
- Misses         20       21       +1     
```

| [Flag](https://app.codecov.io/gh/ITensor/TensorAlgebra.jl/pull/52/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITensor) | Coverage Δ | |
|---|---|---|
| [docs](https://app.codecov.io/gh/ITensor/TensorAlgebra.jl/pull/52/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITensor) | `0.00% <0.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITensor#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/ITensor/TensorAlgebra.jl/pull/52?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITensor).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ITensor).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: mtfishman
created_at: 2025-04-06T20:40:59Z
body: @lkdvos thanks for the thoughtful comments, I'll go through them.
author_association: MEMBER
user: login: mtfishman
created_at: 2025-04-07T13:40:18Z
body: > I feels a bit odd that there are some functions that exist there and some that do not (eg `svdvals`), but I guess `MatrixAlgebra` is not meant to be used outside of this package and then this does not matter?

Good catch. I originally left out `svdvals` since it would just be the same definition as `MatrixAlgebraKit.svd_vals`, and I was thinking of `TensorAlgebra.MatrixAlgebra` as new functionality added on top of `MatrixAlgebraKit`. However, for completeness I added it in. And yes, `TensorAlgebra.MatrixAlgebra` right now is meant to mostly be used internally, though I added exports for all of the `TensorAlgebra.MatrixAlgebra` functions (from `TensorAlgebra.MatrixAlgebra`, not from `TensorAlgebra`) which allows us to do `using TensorAlgebra.MatrixAlgebra` and access those functions. I also added tests for them.
author_association: MEMBER
user: login: mtfishman
created_at: 2025-04-07T14:04:56Z
body: @lkdvos I think I've addressed all of your comments, let me know what you think.
author_association: MEMBER
user: login: mtfishman
created_at: 2025-04-07T15:11:53Z
body: That's fair, I definitely don't like to overuse macros and it is good to assess where it is really worth it. Here I liked it because it guarantees that the logic across definitions are exactly the same. Some of definitions I think are long enough to warrant using `@eval` since I don't want to repeat the same logic multiple times, while as you say others are shorter and maybe don't warrant it, but for consistency I just defined all of them through `@eval` so I didn't have to make a judgement call about where that line is. I also am anticipating we may add more features and options to some of the definitions (particularly `factorize`), and I think having them defined with `@eval` will help to make that easier (as you saw, it is pretty easy to forget to forward the right set of keyword arguments and also it is tough testing every combination of options). Anyway, I'll leave it as it is for now, but I'm happy to reassess that choice going forward.
author_association: MEMBER
user: login: mtfishman
created_at: 2025-04-07T15:37:07Z
body: @ogauthe this will require updating #50 since it changes some factorization definitions, but I think that won't be very complicated (if anything it should be easier now since I merged certain definitions together).
author_association: MEMBER