Repository: ITensor/ITensorInfiniteMPS.jl
Pull request #101: Gauge improvements
Author: login: ryanlevy
Created: 2025-02-27T15:54:02Z | Merged: 2025-03-13T19:27:49Z

This implements two improvements:

- `finite_mps` can now have its gauge center be on the first site instead of the last site
- `orthogonalize` ensures a real Hermitian transfer matrix eigenvector thanks to `schursolve` (shout out @lkdvos for the tip)

The first change is a small speed improvement for long `finite_mps` calls as `correlation_matrix` will immediately move the orthogonality center to the first site

```
julia> @btime corrR = correlation_matrix(finite_mps(ψ,1:24; ortho="right"),"Cdagup","Cup"; sites=2:24+1);
  57.864 ms (900184 allocations: 146.93 MiB)

julia> @btime corrL = correlation_matrix(finite_mps(ψ,1:24; ortho="left"),"Cdagup","Cup"; sites=2:24+1);
  47.981 ms (764616 allocations: 117.28 MiB)
```

The second change includes the additional "gauge fixing" of the output of vumps in the Hubbard example, which should probably always be done so that the state you want to measure has a more consistent `C` matrix. 


Diff:
diff --git a/Project.toml b/Project.toml
index bf70aab..dce4615 100644
--- a/Project.toml
+++ b/Project.toml
@@ -1,7 +1,7 @@
 name = "ITensorInfiniteMPS"
 uuid = "1dc1fb26-a137-4954-ae60-1bd4106e95ad"
 authors = ["Matthew Fishman <mfishman@flatironinstitute.org> and contributors"]
-version = "0.2.0"
+version = "0.2.1"
 
 [deps]
 Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
diff --git a/examples/vumps/vumps_hubbard_extended.jl b/examples/vumps/vumps_hubbard_extended.jl
index 284ed84..c899d82 100644
--- a/examples/vumps/vumps_hubbard_extended.jl
+++ b/examples/vumps/vumps_hubbard_extended.jl
@@ -56,6 +56,7 @@ subspace_expansion_kwargs = (cutoff=cutoff, maxdim=maxdim)
 
 println("\nRun VUMPS on initial product state, unit cell size $N")
 ψ = vumps_subspace_expansion(H, ψ; outer_iters, subspace_expansion_kwargs, vumps_kwargs)
+ψ = orthogonalize(ψ.AL, :; tol=1e-14) # ensure translation invariance
 
 # Check translational invariance
 println("\nCheck translational invariance of optimized infinite MPS")
diff --git a/src/infinitecanonicalmps.jl b/src/infinitecanonicalmps.jl
index 2385450..6d1dd6a 100644
--- a/src/infinitecanonicalmps.jl
+++ b/src/infinitecanonicalmps.jl
@@ -249,21 +249,37 @@ function InfMPS(s::CelledVector, f::Function)
   return ψ = InfiniteCanonicalMPS(ψL, ψC, ψR)
 end
 
-function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange)
+function finite_mps(
+  ψ::InfiniteCanonicalMPS,
+  range::AbstractRange;
+  ortho_lims::AbstractUnitRange=last(range):last(range),
+)
   @assert isone(step(range))
+  @assert first(ortho_lims) == last(ortho_lims) # TODO: variable ortho_lims
+  @assert first(ortho_lims) ∈ range
+
   N = length(range)
-  ψ_finite = ψ.AL[range]
-  ψ_finite[N] *= ψ.C[last(range)]
+  ψ_finite = Vector{ITensor}(undef, N)
+  for i in first(range):first(ortho_lims)
+    ψ_finite[i] = ψ.AL[i]
+  end
+  ψ_finite[first(ortho_lims)] *= ψ.C[first(ortho_lims)]
+
+  for i in (last(ortho_lims) + 1):last(range)
+    ψ_finite[i] = ψ.AR[i]
+  end
+
   l0 = linkind(ψ.AL, first(range) - 1 => first(range))
-  l̃0 = sim(l0)
   lN = linkind(ψ.AR, last(range) => last(range) + 1)
+  l̃0 = sim(l0)
   l̃N = sim(lN)
   δl0 = δ(dag(l̃0), l0)
   δlN = δ(dag(l̃N), lN)
   ψ_finite[1] *= δl0
   ψ_finite[N] *= dag(δlN)
-  ψ_finite = MPS([dag(δl0); [ψ_finiteᵢ for ψ_finiteᵢ in ψ_finite]; δlN])
-  set_ortho_lims!(ψ_finite, (N + 1):(N + 1))
+  ψ_finite = MPS(
+    [dag(δl0); ψ_finite; δlN]; ortho_lims=(first(ortho_lims) + 1):(last(ortho_lims) + 1)
+  )
   return ψ_finite
 end
 function ITensorMPS.expect(ψ::InfiniteCanonicalMPS, o::String, n::Int)
diff --git a/src/orthogonalize.jl b/src/orthogonalize.jl
index 0e85613..e88cfef 100644
--- a/src/orthogonalize.jl
+++ b/src/orthogonalize.jl
@@ -1,7 +1,13 @@
+using KrylovKit: schursolve, Arnoldi
 # TODO: call as `orthogonalize(ψ, -∞)`
 # TODO: could use commontags(ψ) as a default for left_tags
 function right_orthogonalize(
-  ψ::InfiniteMPS; left_tags=ts"Left", right_tags=ts"Right", tol::Real=1e-12
+  ψ::InfiniteMPS;
+  left_tags=ts"Left",
+  right_tags=ts"Right",
+  tol::Real=1e-12,
+  eager=true,
+  ishermitian_kwargs=(; rtol=tol * 100),
 )
   # A transfer matrix made from the 1st unit cell of the infinite MPS
   T = TransferMatrix(ψ)
@@ -12,9 +18,20 @@ function right_orthogonalize(
   # Start by getting the right eivenvector/eigenvalue of T
   # TODO: make a function `right_environments(::InfiniteMPS)` that computes
   # all of the right environments using `eigsolve` and shifting unit cells
-  λ⃗₁ᴿᴺ, v⃗₁ᴿᴺ, eigsolve_info = eigsolve(T, v₁ᴿᴺ, 1, :LM; tol=tol)
+
+  # original eigsolve function, switch to schur which enforces real
+  #λ⃗₁ᴿᴺ, v⃗₁ᴿᴺ, eigsolve_info = eigsolve(T, v₁ᴿᴺ, 1, :LM; tol, eager)
+  TT, v⃗₁ᴿᴺ, λ⃗₁ᴿᴺ, info = schursolve(T, v₁ᴿᴺ, 1, :LM, Arnoldi(; tol, eager))
   λ₁ᴿᴺ, v₁ᴿᴺ = λ⃗₁ᴿᴺ[1], v⃗₁ᴿᴺ[1]
 
+  if info.converged == 0
+    @warn "orthogonalize not converged after $(info.numiter) iterations"
+  end
+
+  if size(TT, 2) > 1 && TT[2, 1] != 0
+    @warn("Non-unique largest eigenvector of transfer matrix found")
+  end
+
   if imag(λ₁ᴿᴺ) / norm(λ₁ᴿᴺ) > 1e-15
     @show λ₁ᴿᴺ
     error(
@@ -24,11 +41,9 @@ function right_orthogonalize(
 
   # Fix the phase of the diagonal to make Hermitian
   v₁ᴿᴺ .*= conj(sign(v₁ᴿᴺ[1, 1]))
-  if !ishermitian(v₁ᴿᴺ; rtol=tol)
-    @show λ₁ᴿᴺ
-    @show v₁ᴿᴺ
+  if !ishermitian(v₁ᴿᴺ; ishermitian_kwargs...)
     @show norm(v₁ᴿᴺ - swapinds(dag(v₁ᴿᴺ), reverse(Pair(inds(v₁ᴿᴺ)...))))
-    error("v₁ᴿᴺ not hermitian")
+    @warn("v₁ᴿᴺ is not hermitian, passed kwargs: $ishermitian_kwargs")
   end
   if norm(imag(v₁ᴿᴺ)) / norm(v₁ᴿᴺ) > 1e-13
     println(
@@ -105,8 +120,8 @@ end
 function mixed_canonical(
   ψ::InfiniteMPS; left_tags=ts"Left", right_tags=ts"Right", tol::Real=1e-12
 )
-  _, ψᴿ, _ = right_orthogonalize(ψ; left_tags=ts"", right_tags=ts"Right")
-  ψᴸ, C, λ = left_orthogonalize(ψᴿ; left_tags=ts"Left", right_tags=ts"Right")
+  _, ψᴿ, _ = right_orthogonalize(ψ; left_tags=ts"", right_tags)
+  ψᴸ, C, λ = left_orthogonalize(ψᴿ; left_tags, right_tags)
   if λ ≉ one(λ)
     error("λ should be approximately 1 after orthogonalization, instead it is $λ")
   end


Reviews:
user: login: mtfishman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T16:01:25Z
user: login: mtfishman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T16:02:45Z
user: login: mtfishman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T16:03:27Z
user: login: lkdvos
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-27T16:04:50Z
user: login: ryanlevy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-27T16:06:19Z
user: login: lkdvos
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-27T16:06:40Z
user: login: ryanlevy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-27T16:11:31Z
user: login: mtfishman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T16:16:32Z
user: login: mtfishman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T16:17:50Z
user: login: mtfishman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T16:42:40Z
user: login: mtfishman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T19:11:51Z
user: login: mtfishman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T19:12:43Z
user: login: ryanlevy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-27T19:15:22Z
user: login: mtfishman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T20:03:14Z
user: login: mtfishman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T20:03:41Z
user: login: mtfishman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T14:48:05Z
user: login: mtfishman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T14:49:05Z
user: login: mtfishman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T14:49:36Z
user: login: ryanlevy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T14:51:21Z
user: login: ryanlevy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T14:53:55Z
user: login: mtfishman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T15:03:10Z

Review comments:
diff_hunk: @@ -249,21 +249,35 @@ function InfMPS(s::CelledVector, f::Function)
   return ψ = InfiniteCanonicalMPS(ψL, ψC, ψR)
 end
 
-function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange)
+function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange; ortho::String="right")
path: src/infinitecanonicalmps.jl
user: login: mtfishman
body: It would make more sense to me to instead have a keyword argument `ortho_lims`, like the MPS constructor.
created_at: 2025-02-27T16:01:25Z
author_association: MEMBER
diff_hunk: @@ -249,21 +249,35 @@ function InfMPS(s::CelledVector, f::Function)
   return ψ = InfiniteCanonicalMPS(ψL, ψC, ψR)
 end
 
-function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange)
+function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange; ortho::String="right")
path: src/infinitecanonicalmps.jl
user: login: lkdvos
body: I'm guessing this is a bit of a convention thing, but setting options like this via `Symbol`s ensures that the compiler can see this. In particular, it can then typically constant propagate this entire function and eliminate the branching.
Since it's in a kwarg, you can always consider "standardizing" the input beforehand to allow both?
```suggestion
function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange; ortho=:right)
    _ortho = ortho isa Symbol ? ortho : Symbol(ortho)
```

(of course, probably does not matter all that much)
created_at: 2025-02-27T16:02:29Z
author_association: NONE
diff_hunk: @@ -12,9 +13,15 @@ function right_orthogonalize(
   # Start by getting the right eivenvector/eigenvalue of T
   # TODO: make a function `right_environments(::InfiniteMPS)` that computes
   # all of the right environments using `eigsolve` and shifting unit cells
-  λ⃗₁ᴿᴺ, v⃗₁ᴿᴺ, eigsolve_info = eigsolve(T, v₁ᴿᴺ, 1, :LM; tol=tol)
+
+  # original eigsolve function, switch to schur which enforces real
+  #λ⃗₁ᴿᴺ, v⃗₁ᴿᴺ, eigsolve_info = eigsolve(T, v₁ᴿᴺ, 1, :LM; tol, eager=true)
+  TT, v⃗₁ᴿᴺ, λ⃗₁ᴿᴺ, eigsolve_info = schursolve(T, v₁ᴿᴺ, 1, :LM, Arnoldi(; tol))
   λ₁ᴿᴺ, v₁ᴿᴺ = λ⃗₁ᴿᴺ[1], v⃗₁ᴿᴺ[1]
 
+  if size(TT, 2) > 1 && TT[2, 1] != 0
+    @warn("Largest transfer matrix eigenvector is not real?")
path: src/orthogonalize.jl
user: login: mtfishman
body: Seems a bit funny to make this a question. Can't you check if it is real?
created_at: 2025-02-27T16:02:45Z
author_association: MEMBER
diff_hunk: @@ -12,9 +13,15 @@ function right_orthogonalize(
   # Start by getting the right eivenvector/eigenvalue of T
   # TODO: make a function `right_environments(::InfiniteMPS)` that computes
   # all of the right environments using `eigsolve` and shifting unit cells
-  λ⃗₁ᴿᴺ, v⃗₁ᴿᴺ, eigsolve_info = eigsolve(T, v₁ᴿᴺ, 1, :LM; tol=tol)
+
+  # original eigsolve function, switch to schur which enforces real
+  #λ⃗₁ᴿᴺ, v⃗₁ᴿᴺ, eigsolve_info = eigsolve(T, v₁ᴿᴺ, 1, :LM; tol, eager=true)
+  TT, v⃗₁ᴿᴺ, λ⃗₁ᴿᴺ, eigsolve_info = schursolve(T, v₁ᴿᴺ, 1, :LM, Arnoldi(; tol))
   λ₁ᴿᴺ, v₁ᴿᴺ = λ⃗₁ᴿᴺ[1], v⃗₁ᴿᴺ[1]
 
+  if size(TT, 2) > 1 && TT[2, 1] != 0
+    @warn("Largest transfer matrix eigenvector is not real?")
path: src/orthogonalize.jl
user: login: mtfishman
body: If it isn't a definitive check, it would be better to phrase it as `"Largest transfer matrix eigenvector might not be real."`.
created_at: 2025-02-27T16:03:27Z
author_association: MEMBER
diff_hunk: @@ -12,9 +13,15 @@ function right_orthogonalize(
   # Start by getting the right eivenvector/eigenvalue of T
   # TODO: make a function `right_environments(::InfiniteMPS)` that computes
   # all of the right environments using `eigsolve` and shifting unit cells
-  λ⃗₁ᴿᴺ, v⃗₁ᴿᴺ, eigsolve_info = eigsolve(T, v₁ᴿᴺ, 1, :LM; tol=tol)
+
+  # original eigsolve function, switch to schur which enforces real
+  #λ⃗₁ᴿᴺ, v⃗₁ᴿᴺ, eigsolve_info = eigsolve(T, v₁ᴿᴺ, 1, :LM; tol, eager=true)
+  TT, v⃗₁ᴿᴺ, λ⃗₁ᴿᴺ, eigsolve_info = schursolve(T, v₁ᴿᴺ, 1, :LM, Arnoldi(; tol))
path: src/orthogonalize.jl
user: login: lkdvos
body: ```suggestion
  TT, v⃗₁ᴿᴺ, λ⃗₁ᴿᴺ, eigsolve_info = schursolve(T, v₁ᴿᴺ, 1, :LM, Arnoldi(; tol, eager=true))
```
created_at: 2025-02-27T16:04:37Z
author_association: NONE
diff_hunk: @@ -12,9 +13,15 @@ function right_orthogonalize(
   # Start by getting the right eivenvector/eigenvalue of T
   # TODO: make a function `right_environments(::InfiniteMPS)` that computes
   # all of the right environments using `eigsolve` and shifting unit cells
-  λ⃗₁ᴿᴺ, v⃗₁ᴿᴺ, eigsolve_info = eigsolve(T, v₁ᴿᴺ, 1, :LM; tol=tol)
+
+  # original eigsolve function, switch to schur which enforces real
+  #λ⃗₁ᴿᴺ, v⃗₁ᴿᴺ, eigsolve_info = eigsolve(T, v₁ᴿᴺ, 1, :LM; tol, eager=true)
+  TT, v⃗₁ᴿᴺ, λ⃗₁ᴿᴺ, eigsolve_info = schursolve(T, v₁ᴿᴺ, 1, :LM, Arnoldi(; tol))
   λ₁ᴿᴺ, v₁ᴿᴺ = λ⃗₁ᴿᴺ[1], v⃗₁ᴿᴺ[1]
 
+  if size(TT, 2) > 1 && TT[2, 1] != 0
+    @warn("Largest transfer matrix eigenvector is not real?")
path: src/orthogonalize.jl
user: login: ryanlevy
body: I guess it should be "Non-unique largest eigenvector" (or the eigenvector shouldn't be real)
created_at: 2025-02-27T16:06:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -12,9 +13,15 @@ function right_orthogonalize(
   # Start by getting the right eivenvector/eigenvalue of T
   # TODO: make a function `right_environments(::InfiniteMPS)` that computes
   # all of the right environments using `eigsolve` and shifting unit cells
-  λ⃗₁ᴿᴺ, v⃗₁ᴿᴺ, eigsolve_info = eigsolve(T, v₁ᴿᴺ, 1, :LM; tol=tol)
+
+  # original eigsolve function, switch to schur which enforces real
+  #λ⃗₁ᴿᴺ, v⃗₁ᴿᴺ, eigsolve_info = eigsolve(T, v₁ᴿᴺ, 1, :LM; tol, eager=true)
+  TT, v⃗₁ᴿᴺ, λ⃗₁ᴿᴺ, eigsolve_info = schursolve(T, v₁ᴿᴺ, 1, :LM, Arnoldi(; tol))
   λ₁ᴿᴺ, v₁ᴿᴺ = λ⃗₁ᴿᴺ[1], v⃗₁ᴿᴺ[1]
 
+  if size(TT, 2) > 1 && TT[2, 1] != 0
+    @warn("Largest transfer matrix eigenvector is not real?")
path: src/orthogonalize.jl
user: login: lkdvos
body: I think what it really means is that it is not unique, this results in complex conjugate eigenvalue pairs (hence checking the `TT[2, 1]`, `TT[1:2,1:2] = [real(lambda) imag(lambda); -imag(lambda) real(lambda)]` or something like that). 
created_at: 2025-02-27T16:06:40Z
author_association: NONE
diff_hunk: @@ -249,21 +249,35 @@ function InfMPS(s::CelledVector, f::Function)
   return ψ = InfiniteCanonicalMPS(ψL, ψC, ψR)
 end
 
-function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange)
+function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange; ortho::String="right")
path: src/infinitecanonicalmps.jl
user: login: ryanlevy
body: `ortho_lims` is a bit funny because they will be off by 1 due to the environment tensors. But we can just translate 1=>2 and N=> N+1
created_at: 2025-02-27T16:11:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -249,21 +249,35 @@ function InfMPS(s::CelledVector, f::Function)
   return ψ = InfiniteCanonicalMPS(ψL, ψC, ψR)
 end
 
-function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange)
+function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange; ortho::String="right")
path: src/infinitecanonicalmps.jl
user: login: mtfishman
body: See my comment above, I think we should specify the desired orthogonality center as a range anyway so no need for strings/symbols.
created_at: 2025-02-27T16:16:32Z
author_association: MEMBER
diff_hunk: @@ -249,21 +249,35 @@ function InfMPS(s::CelledVector, f::Function)
   return ψ = InfiniteCanonicalMPS(ψL, ψC, ψR)
 end
 
-function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange)
+function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange; ortho::String="right")
path: src/infinitecanonicalmps.jl
user: login: mtfishman
body: (But also as a convention we generally use strings for keyword arguments that select different options unless there is a performance reason, since it looks friendlier to users, but as you say we can just allow generic inputs and canonicalize the input.)
created_at: 2025-02-27T16:17:50Z
author_association: MEMBER
diff_hunk: @@ -249,21 +249,35 @@ function InfMPS(s::CelledVector, f::Function)
   return ψ = InfiniteCanonicalMPS(ψL, ψC, ψR)
 end
 
-function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange)
+function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange; ortho::String="right")
path: src/infinitecanonicalmps.jl
user: login: mtfishman
body: Sounds reasonable.
created_at: 2025-02-27T16:42:40Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,13 @@
+using KrylovKit: schursolve, Arnoldi
 # TODO: call as `orthogonalize(ψ, -∞)`
 # TODO: could use commontags(ψ) as a default for left_tags
 function right_orthogonalize(
-  ψ::InfiniteMPS; left_tags=ts"Left", right_tags=ts"Right", tol::Real=1e-12
+  ψ::InfiniteMPS;
+  left_tags=ts"Left",
+  right_tags=ts"Right",
+  tol::Real=1e-12,
+  eager=true,
+  htol::Real=1e-10,
path: src/orthogonalize.jl
user: login: mtfishman
body: I would call this `ishermitian_rtol`, no need for a short name here. Shouldn't we define it relative to `tol`, for example `ishermitian_rtol=10tol`?

Also if the Hermitian check is causing trouble, maybe we should just turn it into a warning. 
created_at: 2025-02-27T19:11:51Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,13 @@
+using KrylovKit: schursolve, Arnoldi
 # TODO: call as `orthogonalize(ψ, -∞)`
 # TODO: could use commontags(ψ) as a default for left_tags
 function right_orthogonalize(
-  ψ::InfiniteMPS; left_tags=ts"Left", right_tags=ts"Right", tol::Real=1e-12
+  ψ::InfiniteMPS;
+  left_tags=ts"Left",
+  right_tags=ts"Right",
+  tol::Real=1e-12,
+  eager=true,
+  htol::Real=1e-10,
path: src/orthogonalize.jl
user: login: mtfishman
body: Or better yet, I would call this `ishermitian_kwargs` and set a default like `ishermitian_kwargs=(; rtol=10tol)`.
created_at: 2025-02-27T19:12:43Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,13 @@
+using KrylovKit: schursolve, Arnoldi
 # TODO: call as `orthogonalize(ψ, -∞)`
 # TODO: could use commontags(ψ) as a default for left_tags
 function right_orthogonalize(
-  ψ::InfiniteMPS; left_tags=ts"Left", right_tags=ts"Right", tol::Real=1e-12
+  ψ::InfiniteMPS;
+  left_tags=ts"Left",
+  right_tags=ts"Right",
+  tol::Real=1e-12,
+  eager=true,
+  htol::Real=1e-10,
path: src/orthogonalize.jl
user: login: ryanlevy
body: Sure thing, I'll do both
created_at: 2025-02-27T19:15:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,7 +1,13 @@
+using KrylovKit: schursolve, Arnoldi
 # TODO: call as `orthogonalize(ψ, -∞)`
 # TODO: could use commontags(ψ) as a default for left_tags
 function right_orthogonalize(
-  ψ::InfiniteMPS; left_tags=ts"Left", right_tags=ts"Right", tol::Real=1e-12
+  ψ::InfiniteMPS;
+  left_tags=ts"Left",
+  right_tags=ts"Right",
+  tol::Real=1e-12,
+  eager=true,
+  ishermitian_rtol::Real=tol * 100,
path: src/orthogonalize.jl
user: login: mtfishman
body: ```suggestion
  ishermitian_kwargs=(; rtol=tol * 100),
```
created_at: 2025-02-27T20:03:14Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,13 @@
+using KrylovKit: schursolve, Arnoldi
 # TODO: call as `orthogonalize(ψ, -∞)`
 # TODO: could use commontags(ψ) as a default for left_tags
 function right_orthogonalize(
-  ψ::InfiniteMPS; left_tags=ts"Left", right_tags=ts"Right", tol::Real=1e-12
+  ψ::InfiniteMPS;
+  left_tags=ts"Left",
+  right_tags=ts"Right",
+  tol::Real=1e-12,
+  eager=true,
+  ishermitian_rtol::Real=tol * 100,
path: src/orthogonalize.jl
user: login: mtfishman
body: Then you can splat `ishermitian_kwargs` into the `ishermitian` call.
created_at: 2025-02-27T20:03:41Z
author_association: MEMBER
diff_hunk: @@ -249,21 +249,37 @@ function InfMPS(s::CelledVector, f::Function)
   return ψ = InfiniteCanonicalMPS(ψL, ψC, ψR)
 end
 
-function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange)
+function finite_mps(
+  ψ::InfiniteCanonicalMPS,
+  range::AbstractRange;
+  ortho_lims::UnitRange=last(range):last(range),
path: src/infinitecanonicalmps.jl
user: login: mtfishman
body: ```suggestion
  ortho_lims::AbstractUnitRange=last(range):last(range),
```
created_at: 2025-03-13T14:48:05Z
author_association: MEMBER
diff_hunk: @@ -24,11 +41,11 @@ function right_orthogonalize(
 
   # Fix the phase of the diagonal to make Hermitian
   v₁ᴿᴺ .*= conj(sign(v₁ᴿᴺ[1, 1]))
-  if !ishermitian(v₁ᴿᴺ; rtol=tol)
-    @show λ₁ᴿᴺ
-    @show v₁ᴿᴺ
+  if !ishermitian(v₁ᴿᴺ; ishermitian_kwargs...)
+    #@show λ₁ᴿᴺ
+    #@show v₁ᴿᴺ
path: src/orthogonalize.jl
user: login: mtfishman
body: ```suggestion
```
created_at: 2025-03-13T14:49:05Z
author_association: MEMBER
diff_hunk: @@ -105,11 +122,16 @@ end
 function mixed_canonical(
   ψ::InfiniteMPS; left_tags=ts"Left", right_tags=ts"Right", tol::Real=1e-12
 )
-  _, ψᴿ, _ = right_orthogonalize(ψ; left_tags=ts"", right_tags=ts"Right")
-  ψᴸ, C, λ = left_orthogonalize(ψᴿ; left_tags=ts"Left", right_tags=ts"Right")
+  _, ψᴿ, _ = right_orthogonalize(ψ; left_tags=ts"", right_tags)
+  ψᴸ, C, λ = left_orthogonalize(ψᴿ; left_tags, right_tags)
   if λ ≉ one(λ)
     error("λ should be approximately 1 after orthogonalization, instead it is $λ")
   end
+  # tags have been added, remove them now
+  # This is broken until we fix the tags for C
+  #removetags!(ψᴸ, left_tags)
+  #removetags!(ψᴿ, right_tags)
+  #removetags!(C,addtags(right_tags,left_tags))
path: src/orthogonalize.jl
user: login: mtfishman
body: Can we just delete this for now?
created_at: 2025-03-13T14:49:35Z
author_association: MEMBER
diff_hunk: @@ -249,21 +249,37 @@ function InfMPS(s::CelledVector, f::Function)
   return ψ = InfiniteCanonicalMPS(ψL, ψC, ψR)
 end
 
-function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange)
+function finite_mps(
+  ψ::InfiniteCanonicalMPS,
+  range::AbstractRange;
+  ortho_lims::UnitRange=last(range):last(range),
path: src/infinitecanonicalmps.jl
user: login: ryanlevy
body: `MPST` takes `UnitRange` and not `AbstractUnitRange` so we should keep it consistent right? 
created_at: 2025-03-13T14:51:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -105,11 +122,16 @@ end
 function mixed_canonical(
   ψ::InfiniteMPS; left_tags=ts"Left", right_tags=ts"Right", tol::Real=1e-12
 )
-  _, ψᴿ, _ = right_orthogonalize(ψ; left_tags=ts"", right_tags=ts"Right")
-  ψᴸ, C, λ = left_orthogonalize(ψᴿ; left_tags=ts"Left", right_tags=ts"Right")
+  _, ψᴿ, _ = right_orthogonalize(ψ; left_tags=ts"", right_tags)
+  ψᴸ, C, λ = left_orthogonalize(ψᴿ; left_tags, right_tags)
   if λ ≉ one(λ)
     error("λ should be approximately 1 after orthogonalization, instead it is $λ")
   end
+  # tags have been added, remove them now
+  # This is broken until we fix the tags for C
+  #removetags!(ψᴸ, left_tags)
+  #removetags!(ψᴿ, right_tags)
+  #removetags!(C,addtags(right_tags,left_tags))
path: src/orthogonalize.jl
user: login: ryanlevy
body: Yeah my bad
created_at: 2025-03-13T14:53:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -249,21 +249,37 @@ function InfMPS(s::CelledVector, f::Function)
   return ψ = InfiniteCanonicalMPS(ψL, ψC, ψR)
 end
 
-function finite_mps(ψ::InfiniteCanonicalMPS, range::AbstractRange)
+function finite_mps(
+  ψ::InfiniteCanonicalMPS,
+  range::AbstractRange;
+  ortho_lims::UnitRange=last(range):last(range),
path: src/infinitecanonicalmps.jl
user: login: mtfishman
body: I think for higher up functions it is better to allow more general inputs, if it fails lower down then that is fine.
created_at: 2025-03-13T15:03:10Z
author_association: MEMBER

PR comments:
user: login: mtfishman
created_at: 2025-02-27T17:04:29Z
body: Nice to see it get more general and simpler!
author_association: MEMBER
user: login: ryanlevy
created_at: 2025-02-27T17:50:58Z
body: @lkdvos it seems that using `eager=true` causes the vector to be non-hermitian (at least higher than the Arnoldi tolerance)
author_association: CONTRIBUTOR
user: login: lkdvos
created_at: 2025-02-27T18:06:05Z
body: That's interesting... The only difference should be that it is checking every iteration if the tolerance has been reached, instead of waiting until the full krylov subspace is populated. Basically, it's probably not fair to expect that you reach the same tolerance for the eigenvector hermiticity as you would get for the eigenvector itself. If you don't put `eager=true`, you often just get a higher precision than you asked for, because you are doing additional iterations. However, in our cases the extra iterations are quite expensive, so this **strongly** affects performance
author_association: NONE
user: login: mtfishman
created_at: 2025-02-27T18:21:39Z
body: Sounds like we should play around with different tolerances when checking for Hermiticity, I don't think there is anything magical about the current choice besides that it has worked so far for catching issues while not being too strict.
author_association: MEMBER
user: login: ryanlevy
created_at: 2025-02-27T19:15:02Z
body: I noticed a bug with `orthogonalize`, you can't call it twice because it adds tags which interfere with the next run
```julia
julia> p = orthogonalize(ψ.AL, : ; tol=1e-14)
       p2 = orthogonalize(p.AL, :)
ERROR: In `isapprox(::ITensor, ::ITensor)`, the indices of the ITensors do not
          match. The first ITensor has indices:

#....
```


author_association: CONTRIBUTOR
user: login: mtfishman
created_at: 2025-02-27T19:18:47Z
body: > I noticed a bug with `orthogonalize`, you can't call it twice because it adds tags which interfere with the next run
> 
> ```julia
> julia> p = orthogonalize(ψ.AL, : ; tol=1e-14)
>        p2 = orthogonalize(p.AL, :)
> ERROR: In `isapprox(::ITensor, ::ITensor)`, the indices of the ITensors do not
>           match. The first ITensor has indices:
> 
> #....
> ```

Got it, I don't have time to look into that but feel free to make a PR.
author_association: MEMBER
user: login: ryanlevy
created_at: 2025-03-13T14:33:26Z
body: @mtfishman is this good to go?
author_association: CONTRIBUTOR
user: login: mtfishman
created_at: 2025-03-13T15:19:36Z
body: Can you bump the package version?
author_association: MEMBER
user: login: ryanlevy
created_at: 2025-03-13T17:51:37Z
body: Sure thing!
author_association: CONTRIBUTOR