From cf5effad69ab269c28690d76b80efe56ef2cf8c5 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 16 Feb 2026 12:54:42 +0100 Subject: [PATCH 01/28] first attempt at extension packages --- Project.toml | 12 +++- ext/MPSKitMakieExt.jl | 152 ++++++++++++++++++++++++++++++++++++++++ ext/MPSKitPlotsExt.jl | 114 ++++++++++++++++++++++++++++++ src/MPSKit.jl | 1 - src/utility/plotting.jl | 104 --------------------------- test/misc/plots.jl | 1 + 6 files changed, 276 insertions(+), 108 deletions(-) create mode 100644 ext/MPSKitMakieExt.jl create mode 100644 ext/MPSKitPlotsExt.jl diff --git a/Project.toml b/Project.toml index 0ba745a7f..0b9884baa 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "MPSKit" uuid = "bb1c41ca-d63c-52ed-829e-0820dda26502" -version = "0.13.9" authors = "Lukas Devos, Maarten Van Damme and contributors" +version = "0.13.9" [deps] Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" @@ -17,12 +17,19 @@ OhMyThreads = "67456a42-1dca-4109-a031-0a68de7e3ad5" OptimKit = "77e91f04-9b3b-57a6-a776-40b61faaebe0" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" TensorKit = "07d1fe3e-3e46-537d-9eac-e9e13d0d4cec" TensorKitManifolds = "11fa318c-39cb-4a83-b1ed-cdc7ba1e3684" TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" +[weakdeps] +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" + +[extensions] +MPSKitMakieExt = "Makie" +MPSKitPlotsExt = "Plots" + [compat] Accessors = "0.1" Aqua = "0.8.9" @@ -42,7 +49,6 @@ Pkg = "1" Plots = "1.40" Printf = "1" Random = "1" -RecipesBase = "1.1" TensorKit = "0.16" TensorKitManifolds = "0.7" TensorKitTensors = "0.2" diff --git a/ext/MPSKitMakieExt.jl b/ext/MPSKitMakieExt.jl new file mode 100644 index 000000000..0caf1f0b1 --- /dev/null +++ b/ext/MPSKitMakieExt.jl @@ -0,0 +1,152 @@ +module MPSKitMakieExt + +using Makie +using MPSKit +import MPSKit: entanglementplot, transferplot + +@recipe(EntanglementPlot, mps) do scene + Attributes( + site = 0, + expand_symmetry = false, + sortby = maximum, + sector_margin = 1 // 10, + sector_formatter = string, + ) +end + +function Makie.plot!(ep::EntanglementPlot) + + mps = ep.mps[] + site = ep.site[] + + (site <= length(mps) && !(isa(mps, FiniteMPS) && site == 0)) || + throw(ArgumentError("Invalid site $site for the given mps.")) + + spectra = entanglement_spectrum(mps, site) + + sectors = [] + spectrum = Vector{Vector{Float64}}() + + for (c, b) in pairs(spectra) + if ep.expand_symmetry[] + b′ = repeat(b, dim(c)) + sort!(b′; rev = true) + push!(spectrum, b′) + else + push!(spectrum, b) + end + push!(sectors, c) + end + + # Sort sectors + if length(spectrum) > 1 + order = sortperm(spectrum; by = ep.sortby[], rev = true) + spectrum = spectrum[order] + sectors = sectors[order] + end + + ax = ep.axis + + # Axis styling + ax.title = "Entanglement Spectrum" + ax.xlabel = "χ = $(round(Int, dim(left_virtualspace(mps, site))))" + ax.yscale = log10 + ax.xticklabelrotation = π / 4 + ax.xticklabelalign = (:center, :top) + + # Plot data + for (i, (partial_spectrum, sector)) in enumerate(zip(spectrum, sectors)) + + n_spectrum = length(partial_spectrum) + + if n_spectrum == 1 + x = [i + 0.5] + else + x = collect( + range( + i + float(ep.sector_margin[]), + i + 1 - float(ep.sector_margin[]); + length = n_spectrum + ) + ) + end + + scatter!(ep, x, partial_spectrum) + end + + ax.xticks = ( + 1:length(sectors), + ep.sector_formatter[].(sectors), + ) + + xlims!(ax, 1, length(sectors) + 1) + ylims!(ax, nothing, 1 + 1.0e-1) + + return ep +end + +#------------------------------------------------------------ + +@recipe(TransferPlot, mps) do scene + Attributes( + sectors = nothing, + transferkwargs = NamedTuple(), + thetaorigin = 0.0, + sector_formatter = string, + ) +end + +function Makie.plot!(tp::TransferPlot) + + mps = tp.mps[] + + sectors = tp.sectors[] + transferkwargs = tp.transferkwargs[] + thetaorigin = tp.thetaorigin[] + sector_formatter = tp.sector_formatter[] + + if sectors === nothing + sectors = [leftunit(mps)] + end + + # axis configuration matches old Plots recipe + ax = tp.axis + ax.title = "Transfer Spectrum" + ax.xlabel = "θ" + ax.ylabel = "r" + ax.xlimits = (thetaorigin, thetaorigin + 2π) + ax.ylimits = (nothing, 1.05) + ax.xticks = ( + range(0, 2π; length = 7), + [ + "$(rationalize(x / π, tol = 0.05))π" + for x in range(0, 2π; length = 7) + ], + ) + ax.xgridvisible = true + ax.ygridvisible = true + + # same as framestyle --> :zerolines + ax.leftspinevisible = false + ax.bottomspinevisible = false + hlines!(ax, 0, linewidth = 1) + vlines!(ax, 0, linewidth = 1) + + below = length(tp.args) == 1 ? mps : tp.args[2][] + + for sector in sectors + spectrum = transfer_spectrum( + mps; below = below, sector = sector, + transferkwargs... + ) + + θ = mod2pi.(angle.(spectrum) .+ thetaorigin) .- thetaorigin + r = abs.(spectrum) + + scatter!(tp, θ, r; label = sector_formatter(sector)) + end + + return tp +end + +end diff --git a/ext/MPSKitPlotsExt.jl b/ext/MPSKitPlotsExt.jl new file mode 100644 index 000000000..ae4bd6ada --- /dev/null +++ b/ext/MPSKitPlotsExt.jl @@ -0,0 +1,114 @@ +module MPSKitPlotsExt + +using RecipesBase +using MPSKit + +@userplot EntanglementPlot + +@recipe function f( + h::EntanglementPlot; site = 0, expand_symmetry = false, sortby = maximum, + sector_margin = 1 // 10, sector_formatter = string + ) + mps = h.args[1] + (site <= length(mps) && !(isa(mps, FiniteMPS) && site == 0)) || + throw(ArgumentError("Invalid site $site for the given mps.")) + + spectra = entanglement_spectrum(mps, site) + sectors = [] + spectrum = [] + for (c, b) in pairs(spectra) + if expand_symmetry # Duplicate entries according to the quantum dimension. + b′ = repeat(b, dim(c)) + sort!(b′; rev = true) + push!(spectrum, b′) + else + push!(spectrum, b) + end + push!(sectors, c) + end + + if length(spectrum) > 1 + order = sortperm(spectrum; by = sortby, rev = true) + spectrum = spectrum[order] + sectors = sectors[order] + end + + for (i, (partial_spectrum, sector)) in enumerate(zip(spectrum, sectors)) + @series begin + seriestype := :scatter + label := sector_formatter(sector) + n_spectrum = length(partial_spectrum) + + # Put single dot in the middle, or a linear range with padding. + if n_spectrum == 1 + x = [i + 1 // 2] + else + x = range(i + sector_margin, i + 1 - sector_margin; length = n_spectrum) + end + return x, partial_spectrum + end + end + + title --> "Entanglement Spectrum" + legend --> false + grid --> :xy + widen --> true + + xguide --> "χ = $(dim(left_virtualspace(mps, site)))" + xticks --> (1:length(sectors), sector_formatter.(sectors)) + xtickfonthalign --> :center + xtick_direction --> :out + xrotation --> 45 + xlims --> (1, length(sectors) + 1) + + ylims --> (-Inf, 1 + 1.0e-1) + yscale --> :log10 + label := nothing + + return [] +end + +#----------------------------------------------------------------------------- + +@userplot TransferPlot + +@recipe function f( + h::TransferPlot; sectors = nothing, transferkwargs = (;), thetaorigin = 0, + sector_formatter = string + ) + if sectors === nothing + sectors = [leftunit(h.args[1])] + end + + for sector in sectors + below = length(h.args) == 1 ? h.args[1] : h.args[2] + spectrum = transfer_spectrum( + h.args[1]; below = below, sector = sector, + transferkwargs... + ) + + @series begin + yguide --> "r" + ylims --> (-Inf, 1.05) + + xguide --> "θ" + xlims --> (thetaorigin, thetaorigin + 2pi) + xticks --> range(0, 2pi; length = 7) + xformatter --> x -> "$(rationalize(x / π, tol = 0.05))π" + xwiden --> true + seriestype := :scatter + markershape --> :auto + label := sector_formatter(sector) + return mod2pi.(angle.(spectrum) .+ thetaorigin) .- thetaorigin, abs.(spectrum) + end + end + + title --> "Transfer Spectrum" + legend --> false + grid --> :xy + framestyle --> :zerolines + + return nothing +end + +end diff --git a/src/MPSKit.jl b/src/MPSKit.jl index cd465643d..9f65507d4 100644 --- a/src/MPSKit.jl +++ b/src/MPSKit.jl @@ -70,7 +70,6 @@ using KrylovKit: KrylovAlgorithm using OptimKit using Base.Threads using Base.Iterators -using RecipesBase using VectorInterface using Accessors using HalfIntegers diff --git a/src/utility/plotting.jl b/src/utility/plotting.jl index d94f90c37..91cdaa89d 100644 --- a/src/utility/plotting.jl +++ b/src/utility/plotting.jl @@ -23,70 +23,6 @@ Plot the [entanglement spectrum](@ref entanglement_spectrum) of a given MPS `sta """ function entanglementplot end -@userplot EntanglementPlot - -@recipe function f( - h::EntanglementPlot; site = 0, expand_symmetry = false, sortby = maximum, - sector_margin = 1 // 10, sector_formatter = string - ) - mps = h.args[1] - (site <= length(mps) && !(isa(mps, FiniteMPS) && site == 0)) || - throw(ArgumentError("Invalid site $site for the given mps.")) - - spectra = entanglement_spectrum(mps, site) - sectors = [] - spectrum = [] - for (c, b) in pairs(spectra) - if expand_symmetry # Duplicate entries according to the quantum dimension. - b′ = repeat(b, dim(c)) - sort!(b′; rev = true) - push!(spectrum, b′) - else - push!(spectrum, b) - end - push!(sectors, c) - end - - if length(spectrum) > 1 - order = sortperm(spectrum; by = sortby, rev = true) - spectrum = spectrum[order] - sectors = sectors[order] - end - - for (i, (partial_spectrum, sector)) in enumerate(zip(spectrum, sectors)) - @series begin - seriestype := :scatter - label := sector_formatter(sector) - n_spectrum = length(partial_spectrum) - - # Put single dot in the middle, or a linear range with padding. - if n_spectrum == 1 - x = [i + 1 // 2] - else - x = range(i + sector_margin, i + 1 - sector_margin; length = n_spectrum) - end - return x, partial_spectrum - end - end - - title --> "Entanglement Spectrum" - legend --> false - grid --> :xy - widen --> true - - xguide --> "χ = $(dim(left_virtualspace(mps, site)))" - xticks --> (1:length(sectors), sector_formatter.(sectors)) - xtickfonthalign --> :center - xtick_direction --> :out - xrotation --> 45 - xlims --> (1, length(sectors) + 1) - - ylims --> (-Inf, 1 + 1.0e-1) - yscale --> :log10 - label := nothing - - return [] -end """ transferplot(above, below=above; sectors=[], transferkwargs=(;)[, kwargs...]) @@ -112,43 +48,3 @@ Plot the partial transfer matrix spectrum of two InfiniteMPS's. """ function transferplot end -@userplot TransferPlot - -@recipe function f( - h::TransferPlot; sectors = nothing, transferkwargs = (;), thetaorigin = 0, - sector_formatter = string - ) - if sectors === nothing - sectors = [leftunit(h.args[1])] - end - - for sector in sectors - below = length(h.args) == 1 ? h.args[1] : h.args[2] - spectrum = transfer_spectrum( - h.args[1]; below = below, sector = sector, - transferkwargs... - ) - - @series begin - yguide --> "r" - ylims --> (-Inf, 1.05) - - xguide --> "θ" - xlims --> (thetaorigin, thetaorigin + 2pi) - xticks --> range(0, 2pi; length = 7) - xformatter --> x -> "$(rationalize(x / π, tol = 0.05))π" - xwiden --> true - seriestype := :scatter - markershape --> :auto - label := sector_formatter(sector) - return mod2pi.(angle.(spectrum) .+ thetaorigin) .- thetaorigin, abs.(spectrum) - end - end - - title --> "Transfer Spectrum" - legend --> false - grid --> :xy - framestyle --> :zerolines - - return nothing -end diff --git a/test/misc/plots.jl b/test/misc/plots.jl index 412d69d93..82fba65bc 100644 --- a/test/misc/plots.jl +++ b/test/misc/plots.jl @@ -10,6 +10,7 @@ using MPSKit using TensorKit using TensorKit: ℙ using Plots +# using CairoMakie @testset "plot tests" begin ψ = InfiniteMPS([ℙ^2], [ℙ^5]) From 0f295babb2cc1ed0c0ebf0940010149fb234a4a9 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 16 Feb 2026 14:01:56 +0100 Subject: [PATCH 02/28] plots weakdep --- Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Project.toml b/Project.toml index 0b9884baa..ec1662c2f 100644 --- a/Project.toml +++ b/Project.toml @@ -25,6 +25,7 @@ VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" [extensions] MPSKitMakieExt = "Makie" From 15a90227026db7acb2bf866686f895b16f1adc8c Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Mon, 16 Feb 2026 15:58:24 +0100 Subject: [PATCH 03/28] only recipesbase weakdep --- Project.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index ec1662c2f..d711865ee 100644 --- a/Project.toml +++ b/Project.toml @@ -25,11 +25,10 @@ VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" -Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" [extensions] MPSKitMakieExt = "Makie" -MPSKitPlotsExt = "Plots" +MPSKitPlotsExt = "RecipesBase" [compat] Accessors = "0.1" From 49bd34d499c11e00483a95c99a9068bdbaccbfe4 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 20 Feb 2026 15:27:00 +0100 Subject: [PATCH 04/28] tests --- test/misc/makie.jl | 18 ++++++++++++++++++ test/misc/plots.jl | 7 +++---- 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 test/misc/makie.jl diff --git a/test/misc/makie.jl b/test/misc/makie.jl new file mode 100644 index 000000000..4b786cfe7 --- /dev/null +++ b/test/misc/makie.jl @@ -0,0 +1,18 @@ +println(" +----------------------------------- +| Plot tests with Makie.jl | +----------------------------------- +") + +using .TestSetup +using Test, TestExtras +using MPSKit +using TensorKit +using TensorKit: ℙ +using CairoMakie + +@testset "plot tests" begin + ψ = InfiniteMPS([ℙ^2], [ℙ^5]) + @test transferplot(ψ) isa CairoMakie.Plot + @test entanglementplot(ψ) isa CairoMakie.Plot +end diff --git a/test/misc/plots.jl b/test/misc/plots.jl index 82fba65bc..af5c23bbb 100644 --- a/test/misc/plots.jl +++ b/test/misc/plots.jl @@ -1,7 +1,7 @@ println(" ---------------------- -| Plot tests | ---------------------- +----------------------------------- +| Plot tests with Plots.jl | +----------------------------------- ") using .TestSetup @@ -10,7 +10,6 @@ using MPSKit using TensorKit using TensorKit: ℙ using Plots -# using CairoMakie @testset "plot tests" begin ψ = InfiniteMPS([ℙ^2], [ℙ^5]) From f583b6f317cc421e27a5b8fffd34a92b6f394ec0 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 7 May 2026 18:26:38 +0200 Subject: [PATCH 05/28] get makie working --- Project.toml | 11 +-- ext/MPSKitMakieExt.jl | 161 ++++++++++++++++++++++++------------------ 2 files changed, 98 insertions(+), 74 deletions(-) diff --git a/Project.toml b/Project.toml index d118cc7f9..55fb04ca9 100644 --- a/Project.toml +++ b/Project.toml @@ -26,12 +26,12 @@ VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" [extensions] MPSKitAdaptExt = "Adapt" -MPSKitMakieExt = "Makie" -MPSKitPlotsExt = "RecipesBase" - +MPSKitMakieExt = ["Makie", "LaTeXStrings"] +MPSKitPlotsExt = ["RecipesBase", "LaTeXStrings"] [compat] Accessors = "0.1" @@ -44,17 +44,18 @@ Compat = "3.47, 4.10" DocStringExtensions = "0.9.3" HalfIntegers = "1.6.0" KrylovKit = "0.8.3, 0.9.2, 0.10" +LaTeXStrings = "1" LinearAlgebra = "1.6" LoggingExtras = "~1.0" +Makie = "0.24, 0.25" MatrixAlgebraKit = "0.6" OhMyThreads = "0.7, 0.8" OptimKit = "0.3.1, 0.4" ParallelTestRunner = "2" Pkg = "1" -Plots = "1.40" Printf = "1" Random = "1" -RecipesBase = "1.1" +RecipesBase = "~1.1" TensorKit = "0.16.3" TensorKitManifolds = "0.7" TensorKitTensors = "0.2" diff --git a/ext/MPSKitMakieExt.jl b/ext/MPSKitMakieExt.jl index 0caf1f0b1..24c92ed49 100644 --- a/ext/MPSKitMakieExt.jl +++ b/ext/MPSKitMakieExt.jl @@ -1,8 +1,10 @@ module MPSKitMakieExt -using Makie -using MPSKit -import MPSKit: entanglementplot, transferplot +using Makie, LaTeXStrings +using MPSKit, TensorKit + +#TODO?: add Colors.jl to access this, allows Plots extension to also use these colors +const JLCOLORS = Makie.Colors.JULIA_LOGO_COLORS @recipe(EntanglementPlot, mps) do scene Attributes( @@ -15,16 +17,17 @@ import MPSKit: entanglementplot, transferplot end function Makie.plot!(ep::EntanglementPlot) - + #TODO: still want this style where sectors are separated? mps = ep.mps[] site = ep.site[] + margin = ep.sector_margin[] - (site <= length(mps) && !(isa(mps, FiniteMPS) && site == 0)) || + (isa(mps, FiniteMPS) && (site == 0 || site > length(mps))) && throw(ArgumentError("Invalid site $site for the given mps.")) spectra = entanglement_spectrum(mps, site) - sectors = [] + sectors = sectortype(mps)[] spectrum = Vector{Vector{Float64}}() for (c, b) in pairs(spectra) @@ -38,57 +41,55 @@ function Makie.plot!(ep::EntanglementPlot) push!(sectors, c) end - # Sort sectors + # Sort sectors according to provided method if length(spectrum) > 1 order = sortperm(spectrum; by = ep.sortby[], rev = true) spectrum = spectrum[order] sectors = sectors[order] end - ax = ep.axis + ax = Makie.current_axis() # Axis styling - ax.title = "Entanglement Spectrum" - ax.xlabel = "χ = $(round(Int, dim(left_virtualspace(mps, site))))" - ax.yscale = log10 - ax.xticklabelrotation = π / 4 - ax.xticklabelalign = (:center, :top) + ax.title = L"\text{Entanglement Spectrum}" + ax.titlesize = 24 + + ax.xlabel = latexstring("\$\\chi\$ = $(round(Int, dim(left_virtualspace(mps, site))))") #TODO: still want this? + ax.xlabelsize = 24 + ax.xticks = (1:length(sectors), ep.sector_formatter[].(sectors)) + ax.xticklabelsize = 16 + ax.xticklabelrotation = 45 + ax.xticklabelalign = (:right, :top) + xlims!(ax, 1, length(sectors) + 1) + + ax.ylabel = L"\log(\lambda)" + ax.ylabelsize = 24 + bottom = floor(Int, log10(minimum(spectra))) + ax.yticks = (bottom:2:0, latexstring.(collect(bottom:2:0))) + ax.yticklabelsize = 16 + ylims!(ax, bottom, 0 + 1.0e-1) # Plot data for (i, (partial_spectrum, sector)) in enumerate(zip(spectrum, sectors)) - n_spectrum = length(partial_spectrum) - if n_spectrum == 1 x = [i + 0.5] else - x = collect( - range( - i + float(ep.sector_margin[]), - i + 1 - float(ep.sector_margin[]); - length = n_spectrum - ) - ) + x = collect(range(i + float(margin), i + 1 - float(margin); length = n_spectrum)) end - - scatter!(ep, x, partial_spectrum) + scatter!(ep, x, log10.(partial_spectrum), color = JLCOLORS[mod1(i, length(JLCOLORS))]) end - ax.xticks = ( - 1:length(sectors), - ep.sector_formatter[].(sectors), - ) - - xlims!(ax, 1, length(sectors) + 1) - ylims!(ax, nothing, 1 + 1.0e-1) - return ep end +MPSKit.entanglementplot(args...; kwargs...) = entanglementplot(args...; kwargs...) + #------------------------------------------------------------ @recipe(TransferPlot, mps) do scene Attributes( + below = nothing, sectors = nothing, transferkwargs = NamedTuple(), thetaorigin = 0.0, @@ -97,56 +98,78 @@ end end function Makie.plot!(tp::TransferPlot) - + #TODO: consider radial plot mps = tp.mps[] - - sectors = tp.sectors[] - transferkwargs = tp.transferkwargs[] + below = tp.below[] === nothing ? mps : tp.below[] + sectors = tp.sectors[] === nothing ? [leftunit(mps)] : tp.sectors[] + transferkwargs = NamedTuple( # weird convert thing + k => (v isa Observable ? v[] : v) for (k, v) in pairs(tp.transferkwargs[]) + ) thetaorigin = tp.thetaorigin[] sector_formatter = tp.sector_formatter[] - if sectors === nothing - sectors = [leftunit(mps)] - end - - # axis configuration matches old Plots recipe - ax = tp.axis - ax.title = "Transfer Spectrum" - ax.xlabel = "θ" - ax.ylabel = "r" - ax.xlimits = (thetaorigin, thetaorigin + 2π) - ax.ylimits = (nothing, 1.05) - ax.xticks = ( - range(0, 2π; length = 7), - [ - "$(rationalize(x / π, tol = 0.05))π" - for x in range(0, 2π; length = 7) - ], - ) + ax = Makie.current_axis() + ax.title = L"\text{Transfer Spectrum}" + ax.titlesize = 24 + ax.xlabel = L"\theta" + ax.xlabelsize = 24 + ax.xticklabelsize = 16 + ax.ylabel = L"r" + ax.ylabelsize = 24 + ax.yticklabelsize = 16 + + ax.xticks = pitick(0, 2pi, 4; mode = :latex) + ax.yticks = (range(0, 1.0; length = 6), latexstring.(range(0, 1.0; length = 6))) ax.xgridvisible = true ax.ygridvisible = true - # same as framestyle --> :zerolines - ax.leftspinevisible = false - ax.bottomspinevisible = false - hlines!(ax, 0, linewidth = 1) - vlines!(ax, 0, linewidth = 1) - - below = length(tp.args) == 1 ? mps : tp.args[2][] - - for sector in sectors - spectrum = transfer_spectrum( - mps; below = below, sector = sector, - transferkwargs... - ) + ax.leftspinevisible = true + ax.rightspinevisible = false + ax.bottomspinevisible = true + ax.topspinevisible = false + @show transferkwargs + for (i, sector) in enumerate(sectors) + spectrum = transfer_spectrum(mps; below = below, sector = sector, transferkwargs...) θ = mod2pi.(angle.(spectrum) .+ thetaorigin) .- thetaorigin r = abs.(spectrum) - - scatter!(tp, θ, r; label = sector_formatter(sector)) + scatter!(tp, θ, r; label = sector_formatter(sector), color = JLCOLORS[mod1(i, length(JLCOLORS))]) end + xlims!(ax, thetaorigin - 0.1, thetaorigin + 2π + 0.1) + ylims!(ax, nothing, 1.05) + Legend(Makie.current_figure()[1, 1], tp.plots, [sector_formatter(s) for s in sectors]; tellwidth = false, halign = :center, valign = :top) return tp end +MPSKit.transferplot(args...; kwargs...) = transferplot(args...; kwargs...) + +# utility for plotting + +function pitick(start, stop, denom; mode = :latex) + a = Int(cld(start, π / denom)) + b = Int(fld(stop, π / denom)) + tick = range(a * π / denom, b * π / denom; step = π / denom) + ticklabel = piticklabel.((a:b) .// denom, Val(mode)) + return tick, ticklabel +end + +function piticklabel(x::Rational, ::Val{:text}) + iszero(x) && return "0" + S = x < 0 ? "-" : "" + n, d = abs(numerator(x)), denominator(x) + N = n == 1 ? "" : repr(n) + d == 1 && return S * N * "π" + return S * N * "π/" * repr(d) +end + +function piticklabel(x::Rational, ::Val{:latex}) + iszero(x) && return L"0" + S = x < 0 ? "-" : "" + n, d = abs(numerator(x)), denominator(x) + N = n == 1 ? "" : repr(n) + d == 1 && return L"%$S%$N\pi" + return L"%$S\frac{%$N\pi}{%$d}" +end + end From babffbf43a27c146261994caec3c6a34528d8517 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 19 May 2026 18:13:33 +0200 Subject: [PATCH 06/28] get plots working --- Project.toml | 11 +++--- ext/MPSKitPlotsExt.jl | 84 +++++++++++++++++++++++++++++-------------- 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/Project.toml b/Project.toml index 55fb04ca9..381f14982 100644 --- a/Project.toml +++ b/Project.toml @@ -10,6 +10,7 @@ Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721" KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" +LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36" MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" @@ -26,12 +27,11 @@ VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" -LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" [extensions] MPSKitAdaptExt = "Adapt" -MPSKitMakieExt = ["Makie", "LaTeXStrings"] -MPSKitPlotsExt = ["RecipesBase", "LaTeXStrings"] +MPSKitMakieExt = "Makie" +MPSKitPlotsExt = "RecipesBase" [compat] Accessors = "0.1" @@ -55,7 +55,7 @@ ParallelTestRunner = "2" Pkg = "1" Printf = "1" Random = "1" -RecipesBase = "~1.1" +RecipesBase = "1" TensorKit = "0.16.3" TensorKitManifolds = "0.7" TensorKitTensors = "0.2" @@ -69,6 +69,7 @@ julia = "1.10" [extras] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" @@ -80,4 +81,4 @@ TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" cuTENSOR = "011b41b2-24ef-40a8-b3eb-fa098493e9e1" [targets] -test = ["Aqua", "Adapt", "CUDA", "cuTENSOR", "Pkg", "Test", "TestExtras", "Plots", "Combinatorics", "ParallelTestRunner", "TensorKitTensors"] +test = ["Aqua", "Adapt", "CairoMakie", "CUDA", "cuTENSOR", "Pkg", "Test", "TestExtras", "Plots", "Combinatorics", "ParallelTestRunner", "TensorKitTensors"] diff --git a/ext/MPSKitPlotsExt.jl b/ext/MPSKitPlotsExt.jl index ae4bd6ada..e94a456b7 100644 --- a/ext/MPSKitPlotsExt.jl +++ b/ext/MPSKitPlotsExt.jl @@ -1,7 +1,7 @@ module MPSKitPlotsExt -using RecipesBase -using MPSKit +using RecipesBase, LaTeXStrings +using MPSKit, TensorKit @userplot EntanglementPlot @@ -10,12 +10,12 @@ using MPSKit sector_margin = 1 // 10, sector_formatter = string ) mps = h.args[1] - (site <= length(mps) && !(isa(mps, FiniteMPS) && site == 0)) || + (isa(mps, FiniteMPS) && (site == 0 || site > length(mps))) && throw(ArgumentError("Invalid site $site for the given mps.")) spectra = entanglement_spectrum(mps, site) - sectors = [] - spectrum = [] + sectors = sectortype(mps)[] + spectrum = Vector{Vector{Float64}}() for (c, b) in pairs(spectra) if expand_symmetry # Duplicate entries according to the quantum dimension. b′ = repeat(b, dim(c)) @@ -35,6 +35,21 @@ using MPSKit for (i, (partial_spectrum, sector)) in enumerate(zip(spectrum, sectors)) @series begin + title --> "Entanglement Spectrum" + legend --> false + grid --> :xy + widen --> true + bottom_margin -->(10, :mm) + + xguide --> latexstring("\$\\chi\$ = $(round(Int, dim(left_virtualspace(mps, site))))") + xticks --> (1:length(sectors), sector_formatter.(sectors)) + xtickfonthalign --> :center + xtick_direction --> :out + xrotation --> 45 + xlims --> (1, length(sectors) + 1) + + ylims --> (-Inf, 1 + 1.0e-1) + yscale --> :log10 seriestype := :scatter label := sector_formatter(sector) n_spectrum = length(partial_spectrum) @@ -49,25 +64,11 @@ using MPSKit end end - title --> "Entanglement Spectrum" - legend --> false - grid --> :xy - widen --> true - - xguide --> "χ = $(dim(left_virtualspace(mps, site)))" - xticks --> (1:length(sectors), sector_formatter.(sectors)) - xtickfonthalign --> :center - xtick_direction --> :out - xrotation --> 45 - xlims --> (1, length(sectors) + 1) - - ylims --> (-Inf, 1 + 1.0e-1) - yscale --> :log10 - label := nothing - - return [] + return nothing end +MPSKit.entanglementplot(args...; kwargs...) = entanglementplot(args...; kwargs...) + #----------------------------------------------------------------------------- @userplot TransferPlot @@ -80,6 +81,7 @@ end sectors = [leftunit(h.args[1])] end + ticks, ticklabels = pitick(0, 2pi, 4; mode = :latex) for sector in sectors below = length(h.args) == 1 ? h.args[1] : h.args[2] spectrum = transfer_spectrum( @@ -88,13 +90,13 @@ end ) @series begin - yguide --> "r" + yguide --> L"r" ylims --> (-Inf, 1.05) - xguide --> "θ" + xguide --> L"\theta" xlims --> (thetaorigin, thetaorigin + 2pi) - xticks --> range(0, 2pi; length = 7) - xformatter --> x -> "$(rationalize(x / π, tol = 0.05))π" + xticks --> ticks + xformatter --> x -> ticklabels[findfirst(==(x), ticks)] xwiden --> true seriestype := :scatter markershape --> :auto @@ -111,4 +113,34 @@ end return nothing end +MPSKit.transferplot(args...; kwargs...) = transferplot(args...; kwargs...) + +# utility for plotting + +function pitick(start, stop, denom; mode = :latex) + a = Int(cld(start, π / denom)) + b = Int(fld(stop, π / denom)) + tick = range(a * π / denom, b * π / denom; step = π / denom) + ticklabel = piticklabel.((a:b) .// denom, Val(mode)) + return tick, ticklabel +end + +function piticklabel(x::Rational, ::Val{:text}) + iszero(x) && return "0" + S = x < 0 ? "-" : "" + n, d = abs(numerator(x)), denominator(x) + N = n == 1 ? "" : repr(n) + d == 1 && return S * N * "π" + return S * N * "π/" * repr(d) +end + +function piticklabel(x::Rational, ::Val{:latex}) + iszero(x) && return L"0" + S = x < 0 ? "-" : "" + n, d = abs(numerator(x)), denominator(x) + N = n == 1 ? "" : repr(n) + d == 1 && return L"%$S%$N\pi" + return L"%$S\frac{%$N\pi}{%$d}" +end + end From d66b9ce973c502e2bdfc475f952fd24270c1301d Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 19 May 2026 18:13:52 +0200 Subject: [PATCH 07/28] minor --- ext/MPSKitMakieExt.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/MPSKitMakieExt.jl b/ext/MPSKitMakieExt.jl index 24c92ed49..91b2f15b5 100644 --- a/ext/MPSKitMakieExt.jl +++ b/ext/MPSKitMakieExt.jl @@ -54,7 +54,7 @@ function Makie.plot!(ep::EntanglementPlot) ax.title = L"\text{Entanglement Spectrum}" ax.titlesize = 24 - ax.xlabel = latexstring("\$\\chi\$ = $(round(Int, dim(left_virtualspace(mps, site))))") #TODO: still want this? + ax.xlabel = latexstring("\$\\chi\$ = $(round(Int, dim(left_virtualspace(mps, site))))") # still want this? ax.xlabelsize = 24 ax.xticks = (1:length(sectors), ep.sector_formatter[].(sectors)) ax.xticklabelsize = 16 @@ -127,7 +127,6 @@ function Makie.plot!(tp::TransferPlot) ax.rightspinevisible = false ax.bottomspinevisible = true ax.topspinevisible = false - @show transferkwargs for (i, sector) in enumerate(sectors) spectrum = transfer_spectrum(mps; below = below, sector = sector, transferkwargs...) From 97729ac0fe92acebf0a4e539e52352a028b8423c Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 19 May 2026 18:14:48 +0200 Subject: [PATCH 08/28] update docstrings --- src/utility/plotting.jl | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/utility/plotting.jl b/src/utility/plotting.jl index 91cdaa89d..9bbf91a6f 100644 --- a/src/utility/plotting.jl +++ b/src/utility/plotting.jl @@ -1,5 +1,5 @@ """ - entanglementplot(state; site=0[, kwargs...]) + entanglementplot(state; site = 0[, kwargs...]) Plot the [entanglement spectrum](@ref entanglement_spectrum) of a given MPS `state`. @@ -7,44 +7,38 @@ Plot the [entanglement spectrum](@ref entanglement_spectrum) of a given MPS `sta - `state`: the MPS for which to compute the entanglement spectrum. # Keyword Arguments -- `site::Int=0`: MPS index for multisite unit cells. The spectrum is computed for the bond +- `site::Int = 0`: MPS index for multisite unit cells. The spectrum is computed for the bond between `site` and `site + 1`. -- `expand_symmetry::Logical=false`: add quantum dimension degeneracies. -- `sortby=maximum`: the method of sorting the sectors. -- `sector_margin=1//10`: the amount of whitespace between sectors. -- `sector_formatter=string`: how to convert sectors to strings. -- `kwargs...`: other kwargs are passed on to the plotting backend. +- `expand_symmetry::Logical = false`: add quantum dimension degeneracies. +- `sortby = maximum`: the method of sorting the sectors. +- `sector_margin = 1//10`: the amount of whitespace between sectors. +- `sector_formatter = string`: how to convert sectors to strings. !!! note - You will need to manually import [Plots.jl](https://github.com/JuliaPlots/Plots.jl) to - be able to use this function. MPSKit.jl defines its plots based on - [RecipesBase.jl](https://github.com/JuliaPlots/Plots.jl/tree/v2/RecipesBase), but the - user still has to add `using Plots` to be able to actually produce the plots. - + You will need to manually import any plotting backend of [Makie.jl](https://github.com/MakieOrg/Makie.jl) or + [Plots.jl](https://github.com/JuliaPlots/Plots.jl) to be able to use this function. """ function entanglementplot end """ - transferplot(above, below=above; sectors=[], transferkwargs=(;)[, kwargs...]) + transferplot(above, below = above; sectors = [], transferkwargs = (;)) Plot the partial transfer matrix spectrum of two InfiniteMPS's. # Arguments - `above::InfiniteMPS`: above mps for [`transfer_spectrum`](@ref). -- `below::InfiniteMPS=above`: below mps for [`transfer_spectrum`](@ref). +- `below::InfiniteMPS = above`: below mps for [`transfer_spectrum`](@ref). # Keyword Arguments -- `sectors=[]`: vector of sectors for which to compute the spectrum. +- `sectors = []`: vector of sectors for which to compute the spectrum. If nothing is passed, the spectrum is computed for the trivial sector. - `transferkwargs`: kwargs for call to [`transfer_spectrum`](@ref). -- `kwargs`: other kwargs are passed on to the plotting backend. -- `thetaorigin=0`: origin of the angle range. -- `sector_formatter=string`: how to convert sectors to strings. +- `thetaorigin = 0`: origin of the angle range. +- `sector_formatter = string`: how to convert sectors to strings. !!! note - You will need to manually import [Plots.jl](https://github.com/JuliaPlots/Plots.jl) to - be able to use this function. MPSKit.jl defines its plots based on - [RecipesBase.jl](https://github.com/JuliaPlots/Plots.jl/tree/v2/RecipesBase), but the - user still has to add `using Plots` to be able to actually produce the plots. - + You will need to manually import any plotting backend of [Makie.jl](https://github.com/MakieOrg/Makie.jl) or + [Plots.jl](https://github.com/JuliaPlots/Plots.jl) to be able to use this function. """ function transferplot end + +#TODO: expand on the docstring From 59779ca2ea7fee9dbcfdac7b112079d78828b920 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 20 May 2026 09:26:42 +0200 Subject: [PATCH 09/28] remove todo --- src/utility/plotting.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utility/plotting.jl b/src/utility/plotting.jl index 9bbf91a6f..6049724ec 100644 --- a/src/utility/plotting.jl +++ b/src/utility/plotting.jl @@ -40,5 +40,3 @@ Plot the partial transfer matrix spectrum of two InfiniteMPS's. [Plots.jl](https://github.com/JuliaPlots/Plots.jl) to be able to use this function. """ function transferplot end - -#TODO: expand on the docstring From ed6054a3ae1089b570e48e1a4d0a1ab19a2c8be7 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 21 May 2026 16:56:11 +0200 Subject: [PATCH 10/28] get makie plot kwargs working --- ext/MPSKitMakieExt.jl | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/ext/MPSKitMakieExt.jl b/ext/MPSKitMakieExt.jl index 91b2f15b5..14ae4aac6 100644 --- a/ext/MPSKitMakieExt.jl +++ b/ext/MPSKitMakieExt.jl @@ -83,7 +83,16 @@ function Makie.plot!(ep::EntanglementPlot) return ep end -MPSKit.entanglementplot(args...; kwargs...) = entanglementplot(args...; kwargs...) +function MPSKit.entanglementplot(args...; plotkwargs = (;), kwargs...) + p = entanglementplot(args...; kwargs...) + ax = p.axis + + # overwrite user-provided axis attributes + for (k, v) in pairs(plotkwargs) + setproperty!(ax, k, v) + end + return p +end #------------------------------------------------------------ @@ -141,7 +150,16 @@ function Makie.plot!(tp::TransferPlot) return tp end -MPSKit.transferplot(args...; kwargs...) = transferplot(args...; kwargs...) +function MPSKit.transferplot(args...; plotkwargs = (;), kwargs...) + p = transferplot(args...; kwargs...) + ax = p.axis + + # overwrite user-provided axis attributes + for (k, v) in pairs(plotkwargs) + setproperty!(ax, k, v) + end + return p +end # utility for plotting From a6bf25feb34cd098f079f976f1544f1432b73be9 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Thu, 21 May 2026 16:58:17 +0200 Subject: [PATCH 11/28] potential project.toml test fix --- test/Project.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/Project.toml b/test/Project.toml index 0df62d5b5..95f19a969 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -4,10 +4,10 @@ name = "MPSKitTests" Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" BlockTensorKit = "5f87ffc2-9cf1-4a46-8172-465d160bd8cd" -CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" MPSKit = "bb1c41ca-d63c-52ed-829e-0820dda26502" MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" @@ -20,6 +20,9 @@ TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" cuTENSOR = "011b41b2-24ef-40a8-b3eb-fa098493e9e1" +[weakdeps] +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" + [sources] MPSKit = {path = ".."} From b0ff4661a94e5ee9af0e75e43cf0bf2faeff53f2 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 22 May 2026 17:52:18 +0200 Subject: [PATCH 12/28] remove latexstrings dep in plots.jl, but keep in makie for now --- Project.toml | 4 ++-- ext/MPSKitPlotsExt.jl | 41 ++++++----------------------------------- 2 files changed, 8 insertions(+), 37 deletions(-) diff --git a/Project.toml b/Project.toml index 3fa964d22..c0d197d53 100644 --- a/Project.toml +++ b/Project.toml @@ -10,7 +10,6 @@ Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721" KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" -LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36" MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" @@ -27,10 +26,11 @@ VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" [extensions] MPSKitAdaptExt = "Adapt" -MPSKitMakieExt = "Makie" +MPSKitMakieExt = ["Makie", "LaTeXStrings"] MPSKitPlotsExt = "RecipesBase" [workspace] diff --git a/ext/MPSKitPlotsExt.jl b/ext/MPSKitPlotsExt.jl index e94a456b7..6c19fd707 100644 --- a/ext/MPSKitPlotsExt.jl +++ b/ext/MPSKitPlotsExt.jl @@ -1,6 +1,6 @@ module MPSKitPlotsExt -using RecipesBase, LaTeXStrings +using RecipesBase using MPSKit, TensorKit @userplot EntanglementPlot @@ -41,7 +41,7 @@ using MPSKit, TensorKit widen --> true bottom_margin -->(10, :mm) - xguide --> latexstring("\$\\chi\$ = $(round(Int, dim(left_virtualspace(mps, site))))") + xguide --> "χ = $(round(Int, dim(left_virtualspace(mps, site))))" xticks --> (1:length(sectors), sector_formatter.(sectors)) xtickfonthalign --> :center xtick_direction --> :out @@ -81,7 +81,6 @@ MPSKit.entanglementplot(args...; kwargs...) = entanglementplot(args...; kwargs.. sectors = [leftunit(h.args[1])] end - ticks, ticklabels = pitick(0, 2pi, 4; mode = :latex) for sector in sectors below = length(h.args) == 1 ? h.args[1] : h.args[2] spectrum = transfer_spectrum( @@ -90,13 +89,13 @@ MPSKit.entanglementplot(args...; kwargs...) = entanglementplot(args...; kwargs.. ) @series begin - yguide --> L"r" + yguide --> "r" ylims --> (-Inf, 1.05) - xguide --> L"\theta" + xguide --> "θ" xlims --> (thetaorigin, thetaorigin + 2pi) - xticks --> ticks - xformatter --> x -> ticklabels[findfirst(==(x), ticks)] + xticks --> range(0, 2pi; length = 7) + xformatter --> x -> "$(rationalize(x / π, tol = 0.05))π" xwiden --> true seriestype := :scatter markershape --> :auto @@ -115,32 +114,4 @@ end MPSKit.transferplot(args...; kwargs...) = transferplot(args...; kwargs...) -# utility for plotting - -function pitick(start, stop, denom; mode = :latex) - a = Int(cld(start, π / denom)) - b = Int(fld(stop, π / denom)) - tick = range(a * π / denom, b * π / denom; step = π / denom) - ticklabel = piticklabel.((a:b) .// denom, Val(mode)) - return tick, ticklabel -end - -function piticklabel(x::Rational, ::Val{:text}) - iszero(x) && return "0" - S = x < 0 ? "-" : "" - n, d = abs(numerator(x)), denominator(x) - N = n == 1 ? "" : repr(n) - d == 1 && return S * N * "π" - return S * N * "π/" * repr(d) -end - -function piticklabel(x::Rational, ::Val{:latex}) - iszero(x) && return L"0" - S = x < 0 ? "-" : "" - n, d = abs(numerator(x)), denominator(x) - N = n == 1 ? "" : repr(n) - d == 1 && return L"%$S%$N\pi" - return L"%$S\frac{%$N\pi}{%$d}" -end - end From 5be4fc49208bd637267c5ca0936496d2a5cb8e1c Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Sun, 24 May 2026 18:15:33 +0200 Subject: [PATCH 13/28] docstrings --- src/utility/plotting.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/utility/plotting.jl b/src/utility/plotting.jl index 6049724ec..b26446d4b 100644 --- a/src/utility/plotting.jl +++ b/src/utility/plotting.jl @@ -9,10 +9,11 @@ Plot the [entanglement spectrum](@ref entanglement_spectrum) of a given MPS `sta # Keyword Arguments - `site::Int = 0`: MPS index for multisite unit cells. The spectrum is computed for the bond between `site` and `site + 1`. -- `expand_symmetry::Logical = false`: add quantum dimension degeneracies. +- `expand_symmetry::Bool = false`: add quantum dimension degeneracies. - `sortby = maximum`: the method of sorting the sectors. - `sector_margin = 1//10`: the amount of whitespace between sectors. - `sector_formatter = string`: how to convert sectors to strings. +- `plotkwargs = (; )`: Relevant to Makie. Kwargs for the underlying plot, e.g. `plotkwargs = (; title = "custom title", xlabel = L"latexstring", xticks = (1:2, ["a", "b"]))`. For Plots, these kwargs can be passed directly to `entanglementplot` instead of via `plotkwargs`. !!! note You will need to manually import any plotting backend of [Makie.jl](https://github.com/MakieOrg/Makie.jl) or @@ -21,7 +22,7 @@ Plot the [entanglement spectrum](@ref entanglement_spectrum) of a given MPS `sta function entanglementplot end """ - transferplot(above, below = above; sectors = [], transferkwargs = (;)) + transferplot(above, below = above; sectors = [], transferkwargs = (;), plotkwargs = (;)) Plot the partial transfer matrix spectrum of two InfiniteMPS's. @@ -31,7 +32,8 @@ Plot the partial transfer matrix spectrum of two InfiniteMPS's. # Keyword Arguments - `sectors = []`: vector of sectors for which to compute the spectrum. If nothing is passed, the spectrum is computed for the trivial sector. -- `transferkwargs`: kwargs for call to [`transfer_spectrum`](@ref). +- `transferkwargs`: kwargs for call to [`transfer_spectrum`](@ref). This needs to be passed as e.g. `transferkwargs = (; num_vals = 10)`. +- `plotkwargs = (; )`: Relevant to Makie. Kwargs for the underlying plot, e.g. `plotkwargs = (; title = "custom title", xlabel = L"latexstring", xticks = (1:2, ["a", "b"]))`. For Plots, these kwargs can be passed directly to `transferplot` instead of via `plotkwargs`. - `thetaorigin = 0`: origin of the angle range. - `sector_formatter = string`: how to convert sectors to strings. From 7d90389a32c4a58bafb1c5c2063c5be04c2fe06f Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Sun, 24 May 2026 18:15:44 +0200 Subject: [PATCH 14/28] fix makie tests --- test/misc/makie.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/misc/makie.jl b/test/misc/makie.jl index 4b786cfe7..407e53942 100644 --- a/test/misc/makie.jl +++ b/test/misc/makie.jl @@ -13,6 +13,6 @@ using CairoMakie @testset "plot tests" begin ψ = InfiniteMPS([ℙ^2], [ℙ^5]) - @test transferplot(ψ) isa CairoMakie.Plot - @test entanglementplot(ψ) isa CairoMakie.Plot + @test transferplot(ψ) isa Makie.FigureAxisPlot + @test entanglementplot(ψ) isa Makie.FigureAxisPlot end From 1595a47b390e599efcc6bc2093224bb797f08e10 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Sun, 24 May 2026 18:16:06 +0200 Subject: [PATCH 15/28] i have no clue how to fix aqua tests --- Project.toml | 4 +++- test/Project.toml | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index c0d197d53..675c1137e 100644 --- a/Project.toml +++ b/Project.toml @@ -63,6 +63,8 @@ julia = "1.10" [extras] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" @@ -74,4 +76,4 @@ TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" cuTENSOR = "011b41b2-24ef-40a8-b3eb-fa098493e9e1" [targets] -test = ["Aqua", "Adapt", "CUDA", "cuTENSOR", "Pkg", "Test", "TestExtras", "Plots", "Combinatorics", "ParallelTestRunner", "TensorKitTensors"] +test = ["Aqua", "Adapt", "CUDA", "cuTENSOR", "Pkg", "Test", "TestExtras", "Plots", "Makie", "CairoMakie", "Combinatorics", "ParallelTestRunner", "TensorKitTensors"] diff --git a/test/Project.toml b/test/Project.toml index 95f19a969..a23113c40 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -7,6 +7,7 @@ BlockTensorKit = "5f87ffc2-9cf1-4a46-8172-465d160bd8cd" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" MPSKit = "bb1c41ca-d63c-52ed-829e-0820dda26502" MatrixAlgebraKit = "6c742aac-3347-4629-af66-fc926824e5e4" @@ -20,14 +21,12 @@ TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" cuTENSOR = "011b41b2-24ef-40a8-b3eb-fa098493e9e1" -[weakdeps] -CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" - [sources] MPSKit = {path = ".."} [compat] Aqua = "0.8.9" +CairoMakie = "0.15" CUDA = "5.9" Combinatorics = "1" Makie = "0.24, 0.25" From 14c37e2813925721d16bdbaceea89cc4cb09361d Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 27 May 2026 11:48:06 +0200 Subject: [PATCH 16/28] remove extras and targets --- Project.toml | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/Project.toml b/Project.toml index 675c1137e..70408e28f 100644 --- a/Project.toml +++ b/Project.toml @@ -58,22 +58,4 @@ TensorKit = "0.16.5" TensorKitManifolds = "0.7, 0.8" TensorOperations = "5.5.1" VectorInterface = "0.2, 0.3, 0.4, 0.5" -julia = "1.10" - -[extras] -Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" -CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" -Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" -CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" -Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" -ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" -Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -TensorKitTensors = "41b62e7d-e9d1-4e23-942c-79a97adf954b" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" -cuTENSOR = "011b41b2-24ef-40a8-b3eb-fa098493e9e1" - -[targets] -test = ["Aqua", "Adapt", "CUDA", "cuTENSOR", "Pkg", "Test", "TestExtras", "Plots", "Makie", "CairoMakie", "Combinatorics", "ParallelTestRunner", "TensorKitTensors"] +julia = "1.10" \ No newline at end of file From c2d9cbd0f23550e66b53c3ae2e853bd47b6381e9 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Sun, 6 Sep 2026 18:05:07 +0200 Subject: [PATCH 17/28] restore 2-arg form of transferplot --- ext/MPSKitMakieExt.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ext/MPSKitMakieExt.jl b/ext/MPSKitMakieExt.jl index 5be971dbb..2c9ff9bf3 100644 --- a/ext/MPSKitMakieExt.jl +++ b/ext/MPSKitMakieExt.jl @@ -93,9 +93,8 @@ end #------------------------------------------------------------ -@recipe(TransferPlot, mps) do scene +@recipe(TransferPlot, above, below) do scene Attributes( - below = nothing, sectors = nothing, transferkwargs = NamedTuple(), thetaorigin = 0.0, @@ -105,8 +104,8 @@ end function Makie.plot!(tp::TransferPlot) #TODO: consider radial plot - mps = tp.mps[] - below = tp.below[] === nothing ? mps : tp.below[] + mps = tp.above[] + below = tp.below[] sectors = tp.sectors[] transferkwargs = NamedTuple( # weird convert thing k => (v isa Observable ? v[] : v) for (k, v) in pairs(tp.transferkwargs[]) @@ -157,8 +156,8 @@ function Makie.plot!(tp::TransferPlot) return tp end -function MPSKit.transferplot(args...; plotkwargs = (;), kwargs...) - p = transferplot(args...; kwargs...) +function MPSKit.transferplot(above, below = above; plotkwargs = (;), kwargs...) + p = transferplot(above, below; kwargs...) ax = p.axis # overwrite user-provided axis attributes From 8422b3bfb729d096285bca23cb1b339781ac1279 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Sun, 6 Sep 2026 20:01:44 +0200 Subject: [PATCH 18/28] in-place methods easily defined for plots.jl --- ext/MPSKitPlotsExt.jl | 2 ++ src/MPSKit.jl | 1 + src/utility/plotting.jl | 6 ++++++ 3 files changed, 9 insertions(+) diff --git a/ext/MPSKitPlotsExt.jl b/ext/MPSKitPlotsExt.jl index 853a2ead2..c61e745e8 100644 --- a/ext/MPSKitPlotsExt.jl +++ b/ext/MPSKitPlotsExt.jl @@ -66,6 +66,7 @@ using MPSKit, TensorKit end MPSKit.entanglementplot(args...; kwargs...) = entanglementplot(args...; kwargs...) +MPSKit.entanglementplot!(args...; kwargs...) = entanglementplot!(args...; kwargs...) #----------------------------------------------------------------------------- @@ -111,5 +112,6 @@ MPSKit.entanglementplot(args...; kwargs...) = entanglementplot(args...; kwargs.. end MPSKit.transferplot(args...; kwargs...) = transferplot(args...; kwargs...) +MPSKit.transferplot!(args...; kwargs...) = transferplot!(args...; kwargs...) end diff --git a/src/MPSKit.jl b/src/MPSKit.jl index 5103600b0..04eef7fa5 100644 --- a/src/MPSKit.jl +++ b/src/MPSKit.jl @@ -50,6 +50,7 @@ export correlation_length, marek_gap, transfer_spectrum export entropy, entanglement_spectrum export open_boundary_conditions, periodic_boundary_conditions export entanglementplot, transferplot +export entanglementplot!, transferplot! export r_LL, l_LL, r_RR, l_RR, r_RL, r_LR, l_RL, l_LR # TODO: rename # unexported diff --git a/src/utility/plotting.jl b/src/utility/plotting.jl index 7d9515a86..951cd65b4 100644 --- a/src/utility/plotting.jl +++ b/src/utility/plotting.jl @@ -20,8 +20,11 @@ Plot the entanglement spectrum (see [`entanglement_spectrum`](@ref)) of a given !!! note You will need to manually import any plotting backend of [Makie.jl](https://github.com/MakieOrg/Makie.jl) or [Plots.jl](https://github.com/JuliaPlots/Plots.jl) to be able to use this function. + +See also [`entanglementplot!`](@ref) for plotting into an existing figure. """ function entanglementplot end +function entanglementplot! end """ transferplot(above, below = above; sectors = nothing, transferkwargs = (;), plotkwargs = (;)) @@ -45,5 +48,8 @@ Plot the partial transfer matrix spectrum of two InfiniteMPS's. !!! note You will need to manually import any plotting backend of [Makie.jl](https://github.com/MakieOrg/Makie.jl) or [Plots.jl](https://github.com/JuliaPlots/Plots.jl) to be able to use this function. + +See also [`transferplot!`](@ref) for plotting into an existing figure. """ function transferplot end +function transferplot! end From 5343d2e1760cf695652fe265ee1391b3165b4c14 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Sun, 6 Sep 2026 20:42:00 +0200 Subject: [PATCH 19/28] annoying workaround for makie in-place methods --- ext/MPSKitMakieExt.jl | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/ext/MPSKitMakieExt.jl b/ext/MPSKitMakieExt.jl index 2c9ff9bf3..b982100f1 100644 --- a/ext/MPSKitMakieExt.jl +++ b/ext/MPSKitMakieExt.jl @@ -6,6 +6,20 @@ using MPSKit, TensorKit #TODO?: add Colors.jl to access this, allows Plots extension to also use these colors const JLCOLORS = Makie.Colors.JULIA_LOGO_COLORS +# cannot use current_axis() when supporting in-place method +# workaround: have it point at the target axis temporarily +#TODO: remove once the recipes publish their axis attributes instead of setting them +function with_current_axis(f, target) + target isa Makie.AbstractAxis || return f() + previous = Makie.current_axis() + Makie.current_axis!(target) + try + return f() + finally + isnothing(previous) || Makie.current_axis!(previous) + end +end + @recipe(EntanglementPlot, mps) do scene Attributes( site = 0, @@ -91,6 +105,15 @@ function MPSKit.entanglementplot(args...; plotkwargs = (;), kwargs...) return p end +function MPSKit.entanglementplot!(state::MPSKit.AbstractMPS, args...; kwargs...) + return entanglementplot!(state, args...; kwargs...) +end +function MPSKit.entanglementplot!(target, state::MPSKit.AbstractMPS, args...; kwargs...) + return with_current_axis(target) do + entanglementplot!(target, state, args...; kwargs...) + end +end + #------------------------------------------------------------ @recipe(TransferPlot, above, below) do scene @@ -152,7 +175,11 @@ function Makie.plot!(tp::TransferPlot) xlims!(ax, thetaorigin - 0.1, thetaorigin + 2π + 0.1) ylims!(ax, nothing, 1.05) - Legend(Makie.current_figure()[1, 1], tp.plots, [sector_formatter(s) for s in plotted_sectors]; tellwidth = false, halign = :center, valign = :top) + if !isempty(plotted_sectors) # cannot use current_figure() when supporting in-place method + axislegend( + ax, tp.plots, [sector_formatter(s) for s in plotted_sectors]; position = :ct + ) + end return tp end @@ -167,6 +194,15 @@ function MPSKit.transferplot(above, below = above; plotkwargs = (;), kwargs...) return p end +function MPSKit.transferplot!(above::MPSKit.AbstractMPS, below = above; kwargs...) + return transferplot!(above, below; kwargs...) +end +function MPSKit.transferplot!(target, above::MPSKit.AbstractMPS, below = above; kwargs...) + return with_current_axis(target) do + transferplot!(target, above, below; kwargs...) + end +end + # utility for plotting function pitick(start, stop, denom; mode = :latex) From 794e0044c4f64c3259961fb54814b7290da51d97 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Sun, 6 Sep 2026 22:11:40 +0200 Subject: [PATCH 20/28] safety net on 0 schmidt value, remove ambiguity, get plotkwargs working for in-place methods --- ext/MPSKitMakieExt.jl | 58 +++++++++++++++++++++++++++---------------- ext/MPSKitPlotsExt.jl | 2 +- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/ext/MPSKitMakieExt.jl b/ext/MPSKitMakieExt.jl index b982100f1..1284d8e8c 100644 --- a/ext/MPSKitMakieExt.jl +++ b/ext/MPSKitMakieExt.jl @@ -20,6 +20,15 @@ function with_current_axis(f, target) end end +# overwrite user-provided axis attributes +function apply_plotkwargs!(ax, plotkwargs) + ax isa Makie.AbstractAxis || return ax + for (k, v) in pairs(plotkwargs) + setproperty!(ax, k, v) + end + return ax +end + @recipe(EntanglementPlot, mps) do scene Attributes( site = 0, @@ -75,7 +84,8 @@ function Makie.plot!(ep::EntanglementPlot) ax.ylabel = L"\log(\lambda)" ax.ylabelsize = 24 - bottom = floor(Int, log10(minimum(spectra))) + smallest = minimum(Iterators.filter(>(0), Iterators.flatten(spectrum)); init = 1.0) # safety net + bottom = floor(Int, log10(smallest)) ax.yticks = (bottom:2:0, latexstring.(collect(bottom:2:0))) ax.yticklabelsize = 16 ylims!(ax, bottom, 0 + 1.0e-1) @@ -96,22 +106,21 @@ end function MPSKit.entanglementplot(args...; plotkwargs = (;), kwargs...) p = entanglementplot(args...; kwargs...) - ax = p.axis - - # overwrite user-provided axis attributes - for (k, v) in pairs(plotkwargs) - setproperty!(ax, k, v) - end + apply_plotkwargs!(p.axis, plotkwargs) return p end -function MPSKit.entanglementplot!(state::MPSKit.AbstractMPS, args...; kwargs...) - return entanglementplot!(state, args...; kwargs...) +function MPSKit.entanglementplot!(state::MPSKit.AbstractMPS; plotkwargs = (;), kwargs...) + p = entanglementplot!(state; kwargs...) + apply_plotkwargs!(Makie.current_axis(), plotkwargs) + return p end -function MPSKit.entanglementplot!(target, state::MPSKit.AbstractMPS, args...; kwargs...) - return with_current_axis(target) do - entanglementplot!(target, state, args...; kwargs...) +function MPSKit.entanglementplot!(target, state::MPSKit.AbstractMPS; plotkwargs = (;), kwargs...) + p = with_current_axis(target) do + entanglementplot!(target, state; kwargs...) end + apply_plotkwargs!(target, plotkwargs) + return p end #------------------------------------------------------------ @@ -185,22 +194,27 @@ end function MPSKit.transferplot(above, below = above; plotkwargs = (;), kwargs...) p = transferplot(above, below; kwargs...) - ax = p.axis - - # overwrite user-provided axis attributes - for (k, v) in pairs(plotkwargs) - setproperty!(ax, k, v) - end + apply_plotkwargs!(p.axis, plotkwargs) return p end -function MPSKit.transferplot!(above::MPSKit.AbstractMPS, below = above; kwargs...) - return transferplot!(above, below; kwargs...) +function MPSKit.transferplot!( + above::MPSKit.AbstractMPS, below::MPSKit.AbstractMPS = above; + plotkwargs = (;), kwargs... + ) + p = transferplot!(above, below; kwargs...) + apply_plotkwargs!(Makie.current_axis(), plotkwargs) + return p end -function MPSKit.transferplot!(target, above::MPSKit.AbstractMPS, below = above; kwargs...) - return with_current_axis(target) do +function MPSKit.transferplot!( + target, above::MPSKit.AbstractMPS, below::MPSKit.AbstractMPS = above; + plotkwargs = (;), kwargs... + ) + p = with_current_axis(target) do transferplot!(target, above, below; kwargs...) end + apply_plotkwargs!(target, plotkwargs) + return p end # utility for plotting diff --git a/ext/MPSKitPlotsExt.jl b/ext/MPSKitPlotsExt.jl index c61e745e8..99a201429 100644 --- a/ext/MPSKitPlotsExt.jl +++ b/ext/MPSKitPlotsExt.jl @@ -37,7 +37,7 @@ using MPSKit, TensorKit legend --> false grid --> :xy widen --> true - bottom_margin -->(10, :mm) + bottom_margin --> (10, :mm) xguide --> "χ = $(dim(MPSKit._firstspace(mps.C[site])))" xticks --> (1:length(sectors), sector_formatter.(sectors)) From 7b9501ce97e8f1ea8de8850f29c7750d9753a952 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Sun, 6 Sep 2026 22:16:17 +0200 Subject: [PATCH 21/28] add in-place and symmetric mps tests --- test/misc/makie.jl | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/misc/makie.jl b/test/misc/makie.jl index 407e53942..a7df3120a 100644 --- a/test/misc/makie.jl +++ b/test/misc/makie.jl @@ -14,5 +14,50 @@ using CairoMakie @testset "plot tests" begin ψ = InfiniteMPS([ℙ^2], [ℙ^5]) @test transferplot(ψ) isa Makie.FigureAxisPlot + @test transferplot(ψ, ψ) isa Makie.FigureAxisPlot + @test transferplot(ψ; transferkwargs = (; howmany = 3)) isa Makie.FigureAxisPlot + + @test entanglementplot(ψ) isa Makie.FigureAxisPlot + + # mutating plots + fig = Figure() + ax = Axis(fig[1, 1]) + @test entanglementplot!(ax, ψ) isa Makie.Plot + @test transferplot!(Axis(fig[1, 2]), ψ) isa Makie.Plot + @test transferplot!(Axis(fig[1, 3]), ψ, ψ) isa Makie.Plot + + # no target -> default to current axis + fig1 = Figure() + Axis(fig1[1, 1]) + @test entanglementplot!(ψ) isa Makie.Plot + @test transferplot!(ψ) isa Makie.Plot + @test transferplot!(ψ, ψ) isa Makie.Plot + + # detect plotkwargs in targeted axis + fig3 = Figure() + ax3 = Axis(fig3[1, 1]) + entanglementplot!(ax3, ψ; plotkwargs = (; title = "custom")) + @test ax3.title[] == "custom" + + # plotting into a non-current axis must not style the current one + fig2 = Figure() + target = Axis(fig2[1, 1]) + current = Axis(fig2[1, 2]) # created last, so this is the current axis + entanglementplot!(target, ψ) + @test target.title[] != "" + @test current.title[] == "" +end + +@testset "graded plots" begin + ψ = InfiniteMPS([Z2Space(0 => 1, 1 => 1)], [Z2Space(0 => 4, 1 => 4)]) + @test entanglementplot(ψ) isa Makie.FigureAxisPlot + @test entanglementplot(ψ; site = 1) isa Makie.FigureAxisPlot + + @test transferplot(ψ) isa Makie.FigureAxisPlot + @test transferplot(ψ, ψ) isa Makie.FigureAxisPlot + + # restrict sectors + triv = unit(sectortype(ψ)) + @test transferplot(ψ; sectors = [triv]) isa Makie.FigureAxisPlot end From cc74519117dcc512680b998f8c7e5850145dc3c9 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 8 Sep 2026 11:04:23 +0200 Subject: [PATCH 22/28] have the plots backend also work with plotkwargs --- ext/MPSKitPlotsExt.jl | 8 ++++---- src/utility/plotting.jl | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/MPSKitPlotsExt.jl b/ext/MPSKitPlotsExt.jl index 99a201429..15afce54a 100644 --- a/ext/MPSKitPlotsExt.jl +++ b/ext/MPSKitPlotsExt.jl @@ -65,8 +65,8 @@ using MPSKit, TensorKit return nothing end -MPSKit.entanglementplot(args...; kwargs...) = entanglementplot(args...; kwargs...) -MPSKit.entanglementplot!(args...; kwargs...) = entanglementplot!(args...; kwargs...) +MPSKit.entanglementplot(args...; plotkwargs = (;), kwargs...) = entanglementplot(args...; kwargs..., plotkwargs...) +MPSKit.entanglementplot!(args...; plotkwargs = (;), kwargs...) = entanglementplot!(args...; kwargs..., plotkwargs...) #----------------------------------------------------------------------------- @@ -111,7 +111,7 @@ MPSKit.entanglementplot!(args...; kwargs...) = entanglementplot!(args...; kwargs return nothing end -MPSKit.transferplot(args...; kwargs...) = transferplot(args...; kwargs...) -MPSKit.transferplot!(args...; kwargs...) = transferplot!(args...; kwargs...) +MPSKit.transferplot(args...; plotkwargs = (;), kwargs...) = transferplot(args...; kwargs..., plotkwargs...) +MPSKit.transferplot!(args...; plotkwargs = (;), kwargs...) = transferplot!(args...; kwargs..., plotkwargs...) end diff --git a/src/utility/plotting.jl b/src/utility/plotting.jl index 951cd65b4..fb1a9cfd7 100644 --- a/src/utility/plotting.jl +++ b/src/utility/plotting.jl @@ -15,7 +15,7 @@ Plot the entanglement spectrum (see [`entanglement_spectrum`](@ref)) of a given - `sortby = maximum`: the method of sorting the sectors. - `sector_margin = 1//10`: the amount of whitespace between sectors. - `sector_formatter = string`: how to convert sectors to strings. -- `plotkwargs = (; )`: Relevant to Makie. Kwargs for the underlying plot, e.g. `plotkwargs = (; title = "custom title", xlabel = L"latexstring", xticks = (1:2, ["a", "b"]))`. For Plots, these kwargs can be passed directly to `entanglementplot` instead of via `plotkwargs`. +- `plotkwargs = (; )`: kwargs for the underlying plot, e.g. `plotkwargs = (; title = "custom title", xlabel = L"\text{custom label}", xticks = (1:2, ["a", "b"]))`. !!! note You will need to manually import any plotting backend of [Makie.jl](https://github.com/MakieOrg/Makie.jl) or @@ -41,7 +41,7 @@ Plot the partial transfer matrix spectrum of two InfiniteMPS's. - `sectors = nothing`: restrict the spectrum to the given sectors; by default all sectors of the transfer space are included. - `transferkwargs`: kwargs for call to [`transfer_spectrum`](@ref). -- `plotkwargs = (; )`: Relevant to Makie. Kwargs for the underlying plot, e.g. `plotkwargs = (; title = "custom title", xlabel = L"latexstring", xticks = (1:2, ["a", "b"]))`. For Plots, these kwargs can be passed directly to `transferplot` instead of via `plotkwargs`. +- `plotkwargs = (; )`: kwargs for the underlying plot, e.g. `plotkwargs = (; title = "custom title", xlabel = L"latexstring", xticks = (1:2, ["a", "b"]))`. - `thetaorigin = 0`: origin of the angle range. - `sector_formatter = string`: how to convert sectors to strings. From 6f88ab49a7102c02e3022ca76f08a4a23159ff08 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 8 Sep 2026 17:47:24 +0200 Subject: [PATCH 23/28] allow moving legend in makie transferplots --- ext/MPSKitMakieExt.jl | 5 ++++- src/utility/plotting.jl | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ext/MPSKitMakieExt.jl b/ext/MPSKitMakieExt.jl index 1284d8e8c..4c7fd1dfd 100644 --- a/ext/MPSKitMakieExt.jl +++ b/ext/MPSKitMakieExt.jl @@ -131,6 +131,7 @@ end transferkwargs = NamedTuple(), thetaorigin = 0.0, sector_formatter = string, + legend_position = :ct, ) end @@ -144,6 +145,7 @@ function Makie.plot!(tp::TransferPlot) ) thetaorigin = tp.thetaorigin[] sector_formatter = tp.sector_formatter[] + legend_position = tp.legend_position[] kwargs = transferkwargs if sectors !== nothing && get(kwargs, :howmany, 20) isa Int @@ -186,7 +188,8 @@ function Makie.plot!(tp::TransferPlot) ylims!(ax, nothing, 1.05) if !isempty(plotted_sectors) # cannot use current_figure() when supporting in-place method axislegend( - ax, tp.plots, [sector_formatter(s) for s in plotted_sectors]; position = :ct + ax, tp.plots, [sector_formatter(s) for s in plotted_sectors]; + position = legend_position ) end return tp diff --git a/src/utility/plotting.jl b/src/utility/plotting.jl index fb1a9cfd7..6fc83f879 100644 --- a/src/utility/plotting.jl +++ b/src/utility/plotting.jl @@ -27,7 +27,7 @@ function entanglementplot end function entanglementplot! end """ - transferplot(above, below = above; sectors = nothing, transferkwargs = (;), plotkwargs = (;)) + transferplot(above, below = above; sectors = nothing, transferkwargs = (;), plotkwargs = (;), legend_position = :ct) Plot the partial transfer matrix spectrum of two InfiniteMPS's. @@ -44,6 +44,8 @@ Plot the partial transfer matrix spectrum of two InfiniteMPS's. - `plotkwargs = (; )`: kwargs for the underlying plot, e.g. `plotkwargs = (; title = "custom title", xlabel = L"latexstring", xticks = (1:2, ["a", "b"]))`. - `thetaorigin = 0`: origin of the angle range. - `sector_formatter = string`: how to convert sectors to strings. +- `legend_position = :ct`: Makie only, the `position` passed to `axislegend`. + For Plots, use the standard `legend` attribute instead (e.g. `legend = :topright`) in `plotkwargs`. !!! note You will need to manually import any plotting backend of [Makie.jl](https://github.com/MakieOrg/Makie.jl) or From 259e9ee480409cb3fad0d5ff27468ef8e944b3a9 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Tue, 8 Sep 2026 21:09:43 +0200 Subject: [PATCH 24/28] add to changelog --- docs/src/changelog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/src/changelog.md b/docs/src/changelog.md index eeae4041f..0abd69fc8 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -38,6 +38,12 @@ When releasing a new version, move the "Unreleased" changes to a new version sec by `MPSKit.default_allocator`, instead of leaving them to the garbage collector (two-site DMRG: -64% allocations, -57% GC time, -23% wall time). Disable with `MPSKit.Defaults.set_buffering!(false)`. ([#467](https://github.com/QuantumKitHub/MPSKit.jl/pull/467)) +- Makie.jl support for `entanglementplot` and `transferplot`, through a new `MPSKitMakieExt` + package extension. Loading any Makie backend (e.g. `using CairoMakie`) alongside MPSKit + enables both plot functions, as an alternative to Plots.jl. Backend-specific styling can be + passed via the `plotkwargs` keyword. ([#428](https://github.com/QuantumKitHub/MPSKit.jl/pull/428)) +- `entanglementplot!` and `transferplot!`, the mutating counterparts of the plotting functions, + which add a spectrum to an existing figure instead of creating a new one. ([#428](https://github.com/QuantumKitHub/MPSKit.jl/pull/428)) ### Changed From 4f62d651762ab7fa43ee8d7644bc8b495f13543b Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Wed, 9 Sep 2026 12:13:20 +0200 Subject: [PATCH 25/28] custom workers for plot backends --- test/runtests.jl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index ef29c29ad..d79e4ae20 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -41,4 +41,10 @@ const init_code = quote const fast_tests = $fast end -ParallelTestRunner.runtests(MPSKit, args; testsuite, init_worker_code, init_code) +# custom workers for different plot backends +function test_worker(name, init_worker_code) + name in ("misc/plots", "misc/makie") && return addworker(; init_worker_code) + return nothing +end + +ParallelTestRunner.runtests(MPSKit, args; testsuite, init_worker_code, init_code, test_worker) From 676e778b6ca5190946b1848b97a6cffacc15ba7d Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 18 Sep 2026 14:21:49 +0200 Subject: [PATCH 26/28] fix bad merge --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index f28fd38de..377ddb3d8 100644 --- a/Project.toml +++ b/Project.toml @@ -36,7 +36,7 @@ MPSKitMakieExt = ["Makie", "LaTeXStrings"] MPSKitPlotsExt = "RecipesBase" [workspace] -projects = ["test", "docs"] +projects = ["test", "docs", "examples"] [compat] Accessors = "0.1" From 0418d74bea1250459a073549719a6c4c5aaf0daa Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 18 Sep 2026 14:23:40 +0200 Subject: [PATCH 27/28] warnings on 0 schmidt value, same for plots --- ext/MPSKitMakieExt.jl | 15 ++++++++------- ext/MPSKitPlotsExt.jl | 16 +++++++++++----- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/ext/MPSKitMakieExt.jl b/ext/MPSKitMakieExt.jl index 4c7fd1dfd..1ed5ab65e 100644 --- a/ext/MPSKitMakieExt.jl +++ b/ext/MPSKitMakieExt.jl @@ -40,7 +40,6 @@ end end function Makie.plot!(ep::EntanglementPlot) - #TODO: still want this style where sectors are separated? mps = ep.mps[] site = ep.site[] margin = ep.sector_margin[] @@ -54,13 +53,18 @@ function Makie.plot!(ep::EntanglementPlot) if ep.expand_symmetry[] b′ = repeat(b, dim(c)) sort!(b′; rev = true) - push!(spectrum, b′) else - push!(spectrum, b) + b′ = collect(b) end + push!(spectrum, b′) push!(sectors, c) end + if any(v -> any(<=(0), v), spectrum) + @warn "Entanglement spectrum contains vanishing Schmidt values. These are omitted from the plot." + foreach(v -> filter!(>(0), v), spectrum) + end + # Sort sectors according to provided method if length(spectrum) > 1 order = sortperm(spectrum; by = ep.sortby[], rev = true) @@ -71,10 +75,7 @@ function Makie.plot!(ep::EntanglementPlot) ax = Makie.current_axis() # Axis styling - ax.title = L"\text{Entanglement Spectrum}" - ax.titlesize = 24 - ax.xlabel = latexstring("\$\\chi\$ = $(dim(MPSKit._firstspace(mps.C[site])))") # still want this? ax.xlabelsize = 24 ax.xticks = (1:length(sectors), ep.sector_formatter[].(sectors)) ax.xticklabelsize = 16 @@ -84,7 +85,7 @@ function Makie.plot!(ep::EntanglementPlot) ax.ylabel = L"\log(\lambda)" ax.ylabelsize = 24 - smallest = minimum(Iterators.filter(>(0), Iterators.flatten(spectrum)); init = 1.0) # safety net + smallest = minimum(Iterators.flatten(spectrum); init = 1.0) # spectrum is already > 0 bottom = floor(Int, log10(smallest)) ax.yticks = (bottom:2:0, latexstring.(collect(bottom:2:0))) ax.yticklabelsize = 16 diff --git a/ext/MPSKitPlotsExt.jl b/ext/MPSKitPlotsExt.jl index 15afce54a..5b18d6220 100644 --- a/ext/MPSKitPlotsExt.jl +++ b/ext/MPSKitPlotsExt.jl @@ -18,35 +18,41 @@ using MPSKit, TensorKit if expand_symmetry # Duplicate entries according to the quantum dimension. b′ = repeat(b, dim(c)) sort!(b′; rev = true) - push!(spectrum, b′) else - push!(spectrum, b) + b′ = collect(b) end + push!(spectrum, b′) push!(sectors, c) end + if any(v -> any(<=(0), v), spectrum) + @warn "Entanglement spectrum contains vanishing Schmidt values. These are omitted from the plot." + foreach(v -> filter!(>(0), v), spectrum) + end + if length(spectrum) > 1 order = sortperm(spectrum; by = sortby, rev = true) spectrum = spectrum[order] sectors = sectors[order] end + smallest = minimum(Iterators.flatten(spectrum); init = 1.0) # spectrum is already > 0 + bottom = floor(Int, log10(smallest)) + for (i, (partial_spectrum, sector)) in enumerate(zip(spectrum, sectors)) @series begin - title --> "Entanglement Spectrum" legend --> false grid --> :xy widen --> true bottom_margin --> (10, :mm) - xguide --> "χ = $(dim(MPSKit._firstspace(mps.C[site])))" xticks --> (1:length(sectors), sector_formatter.(sectors)) xtickfonthalign --> :center xtick_direction --> :out xrotation --> 45 xlims --> (1, length(sectors) + 1) - ylims --> (-Inf, 1 + 1.0e-1) + ylims --> (exp10(bottom), 1 + 1.0e-1) yscale --> :log10 seriestype := :scatter label := sector_formatter(sector) From 6c3b445ebf4e7cfd010e81ae4987a866b7f51953 Mon Sep 17 00:00:00 2001 From: Boris De Vos Date: Fri, 18 Sep 2026 15:50:55 +0200 Subject: [PATCH 28/28] work with p.attributes to style axis with :axis_info node --- ext/MPSKitMakieExt.jl | 320 +++++++++++++++++++++++------------------- ext/MPSKitPlotsExt.jl | 1 - test/misc/makie.jl | 4 +- 3 files changed, 180 insertions(+), 145 deletions(-) diff --git a/ext/MPSKitMakieExt.jl b/ext/MPSKitMakieExt.jl index 1ed5ab65e..34341dd0f 100644 --- a/ext/MPSKitMakieExt.jl +++ b/ext/MPSKitMakieExt.jl @@ -6,199 +6,234 @@ using MPSKit, TensorKit #TODO?: add Colors.jl to access this, allows Plots extension to also use these colors const JLCOLORS = Makie.Colors.JULIA_LOGO_COLORS -# cannot use current_axis() when supporting in-place method -# workaround: have it point at the target axis temporarily -#TODO: remove once the recipes publish their axis attributes instead of setting them -function with_current_axis(f, target) - target isa Makie.AbstractAxis || return f() - previous = Makie.current_axis() - Makie.current_axis!(target) - try - return f() - finally - isnothing(previous) || Makie.current_axis!(previous) - end +sector_color(i::Integer) = JLCOLORS[mod1(i, length(JLCOLORS))] + +convert_kwargs(kwargs::NamedTuple) = kwargs +function convert_kwargs(kwargs) # weird convert thing + return NamedTuple(Symbol(k) => (v isa Observable ? v[] : v) for (k, v) in pairs(kwargs)) end -# overwrite user-provided axis attributes -function apply_plotkwargs!(ax, plotkwargs) +# the recipes publish the axis attributes they want as an `:axis_info` node instead of `current_axis()` +function apply_axis_info!(ax, plot, plotkwargs = (;)) ax isa Makie.AbstractAxis || return ax - for (k, v) in pairs(plotkwargs) - setproperty!(ax, k, v) + function apply!(info) + for (k, v) in pairs(info) + setproperty!(ax, k, v) + end + # user-provided attributes take precedence + for (k, v) in pairs(plotkwargs) + setproperty!(ax, k, v) + end + return nothing end + node = plot.attributes[:axis_info] + apply!(node[]) + on(apply!, node) # keep the axis in sync when the inputs change return ax end -@recipe(EntanglementPlot, mps) do scene - Attributes( - site = 0, - expand_symmetry = false, - sortby = maximum, - sector_margin = 1 // 10, - sector_formatter = string, - ) +@recipe EntanglementPlot (mps,) begin + site = 0 + expand_symmetry = false + sortby = maximum + sector_margin = 1 // 10 + sector_formatter = string + markersize = 12 + marker = :circle end function Makie.plot!(ep::EntanglementPlot) - mps = ep.mps[] - site = ep.site[] - margin = ep.sector_margin[] - - spectra = entanglement_spectrum(mps, site) + # this closure only reruns when one of the inputs changes + map!(ep.attributes, [:mps, :site, :expand_symmetry, :sortby], :spectrum_data) do mps, site, expand_symmetry, sortby + spectra = entanglement_spectrum(mps, site) + + sectors = sectortype(mps)[] + spectrum = Vector{Vector{Float64}}() + for (c, b) in pairs(spectra) + if expand_symmetry + b′ = repeat(collect(b), dim(c)) + sort!(b′; rev = true) + else + b′ = collect(b) + end + push!(spectrum, b′) + push!(sectors, c) + end - sectors = sectortype(mps)[] - spectrum = Vector{Vector{Float64}}() + if any(v -> any(<=(0), v), spectrum) + @warn "Entanglement spectrum contains vanishing Schmidt values. These are omitted from the plot." + foreach(v -> filter!(>(0), v), spectrum) + end - for (c, b) in pairs(spectra) - if ep.expand_symmetry[] - b′ = repeat(b, dim(c)) - sort!(b′; rev = true) - else - b′ = collect(b) + # Sort sectors according to provided method + if length(spectrum) > 1 + order = sortperm(spectrum; by = sortby, rev = true) + spectrum = spectrum[order] + sectors = sectors[order] end - push!(spectrum, b′) - push!(sectors, c) + return (; sectors, spectrum) end - if any(v -> any(<=(0), v), spectrum) - @warn "Entanglement spectrum contains vanishing Schmidt values. These are omitted from the plot." - foreach(v -> filter!(>(0), v), spectrum) + # styling + # only reruns when spectrum_data or sector_margin changes + map!(ep.attributes, [:spectrum_data, :sector_margin], :positions) do data, margin + points = Point2d[] + for (i, partial_spectrum) in enumerate(data.spectrum) + n_spectrum = length(partial_spectrum) + xs = if n_spectrum == 1 + range(i + 0.5, i + 0.5; length = 1) + else + range(i + float(margin), i + 1 - float(margin); length = n_spectrum) + end + for (x, λ) in zip(xs, partial_spectrum) + push!(points, Point2d(x, log10(λ))) + end + end + return points end - # Sort sectors according to provided method - if length(spectrum) > 1 - order = sortperm(spectrum; by = ep.sortby[], rev = true) - spectrum = spectrum[order] - sectors = sectors[order] + # only reruns when spectrum_data changes + map!(ep.attributes, [:spectrum_data], :colors) do data + colors = typeof(sector_color(1))[] + for (i, partial_spectrum) in enumerate(data.spectrum) + append!(colors, fill(sector_color(i), length(partial_spectrum))) + end + return colors end - ax = Makie.current_axis() - - # Axis styling - - ax.xlabelsize = 24 - ax.xticks = (1:length(sectors), ep.sector_formatter[].(sectors)) - ax.xticklabelsize = 16 - ax.xticklabelrotation = 45 - ax.xticklabelalign = (:right, :top) - xlims!(ax, 1, length(sectors) + 1) - - ax.ylabel = L"\log(\lambda)" - ax.ylabelsize = 24 - smallest = minimum(Iterators.flatten(spectrum); init = 1.0) # spectrum is already > 0 - bottom = floor(Int, log10(smallest)) - ax.yticks = (bottom:2:0, latexstring.(collect(bottom:2:0))) - ax.yticklabelsize = 16 - ylims!(ax, bottom, 0 + 1.0e-1) - - # Plot data - for (i, (partial_spectrum, sector)) in enumerate(zip(spectrum, sectors)) - n_spectrum = length(partial_spectrum) - if n_spectrum == 1 - x = [i + 0.5] + map!(ep.attributes, [:mps, :site, :spectrum_data, :sector_formatter], :axis_info) do mps, site, data, sector_formatter + nsectors = length(data.sectors) + bottom = if isempty(data.spectrum) + -1 else - x = collect(range(i + float(margin), i + 1 - float(margin); length = n_spectrum)) + smallest = minimum(Iterators.flatten(data.spectrum); init = 1.0) # spectrum is already > 0 + floor(Int, log10(smallest)) end - scatter!(ep, x, log10.(partial_spectrum), color = JLCOLORS[mod1(i, length(JLCOLORS))]) + return (; + xticks = (1:nsectors, sector_formatter.(data.sectors)), + xticklabelsize = 16, + xticklabelrotation = 45.0, + xticklabelalign = (:right, :top), + ylabel = L"\log(\lambda)", + ylabelsize = 24, + yticks = (bottom:2:0, latexstring.(collect(bottom:2:0))), + yticklabelsize = 16, + limits = ((1, nsectors + 1), (bottom, 0 + 1.0e-1)), + ) end + scatter!(ep, ep.positions; color = ep.colors, markersize = ep.markersize, marker = ep.marker) return ep end function MPSKit.entanglementplot(args...; plotkwargs = (;), kwargs...) p = entanglementplot(args...; kwargs...) - apply_plotkwargs!(p.axis, plotkwargs) + apply_axis_info!(p.axis, p.plot, plotkwargs) return p end function MPSKit.entanglementplot!(state::MPSKit.AbstractMPS; plotkwargs = (;), kwargs...) p = entanglementplot!(state; kwargs...) - apply_plotkwargs!(Makie.current_axis(), plotkwargs) + apply_axis_info!(Makie.current_axis(), p, plotkwargs) return p end function MPSKit.entanglementplot!(target, state::MPSKit.AbstractMPS; plotkwargs = (;), kwargs...) - p = with_current_axis(target) do - entanglementplot!(target, state; kwargs...) - end - apply_plotkwargs!(target, plotkwargs) + p = entanglementplot!(target, state; kwargs...) + apply_axis_info!(target, p, plotkwargs) return p end #------------------------------------------------------------ -@recipe(TransferPlot, above, below) do scene - Attributes( - sectors = nothing, - transferkwargs = NamedTuple(), - thetaorigin = 0.0, - sector_formatter = string, - legend_position = :ct, - ) +@recipe TransferPlot (above, below) begin + sectors = nothing + transferkwargs = NamedTuple() + thetaorigin = 0.0 + sector_formatter = string + legend_position = :ct + markersize = 12 + marker = :circle end function Makie.plot!(tp::TransferPlot) #TODO: consider radial plot - mps = tp.above[] - below = tp.below[] - sectors = tp.sectors[] - transferkwargs = NamedTuple( # weird convert thing - k => (v isa Observable ? v[] : v) for (k, v) in pairs(tp.transferkwargs[]) - ) - thetaorigin = tp.thetaorigin[] - sector_formatter = tp.sector_formatter[] - legend_position = tp.legend_position[] - - kwargs = transferkwargs - if sectors !== nothing && get(kwargs, :howmany, 20) isa Int - howmany = Dict(c => get(kwargs, :howmany, 20) for c in sectors) - kwargs = (; kwargs..., howmany) + # this only reruns when one of the inputs changes + map!(tp.attributes, [:above, :below, :sectors, :transferkwargs], :spectrum_data) do above, below, sectors, transferkwargs + kwargs = convert_kwargs(transferkwargs) + if sectors !== nothing && get(kwargs, :howmany, 20) isa Int + # restrict the computation to the requested sectors + howmany = Dict(c => get(kwargs, :howmany, 20) for c in sectors) + kwargs = (; kwargs..., howmany) + end + spectra = transfer_spectrum(above, below; kwargs...) + + data = Pair{sectortype(above), Vector{complex(scalartype(above))}}[] + for (sector, spectrum) in pairs(spectra) + sectors === nothing || sector in sectors || continue + push!(data, sector => collect(spectrum)) + end + return data end - spectra = transfer_spectrum(mps, below; kwargs...) - ax = Makie.current_axis() - ax.title = L"\text{Transfer Spectrum}" - ax.titlesize = 24 - ax.xlabel = L"\theta" - ax.xlabelsize = 24 - ax.xticklabelsize = 16 - ax.ylabel = L"r" - ax.ylabelsize = 24 - ax.yticklabelsize = 16 - - ax.xticks = pitick(0, 2pi, 4; mode = :latex) - ax.yticks = (range(0, 1.0; length = 6), latexstring.(range(0, 1.0; length = 6))) - ax.xgridvisible = true - ax.ygridvisible = true - - ax.leftspinevisible = true - ax.rightspinevisible = false - ax.bottomspinevisible = true - ax.topspinevisible = false - - plotted_sectors = sectortype(mps)[] - for (sector, spectrum) in pairs(spectra) - sectors === nothing || sector in sectors || continue - push!(plotted_sectors, sector) - i = length(plotted_sectors) - θ = mod2pi.(angle.(spectrum) .+ thetaorigin) .- thetaorigin - r = abs.(spectrum) - scatter!(tp, θ, r; label = sector_formatter(sector), color = JLCOLORS[mod1(i, length(JLCOLORS))]) + map!(tp.attributes, [:spectrum_data, :thetaorigin], :positions) do data, thetaorigin + points = Point2d[] + for (_, spectrum) in data, λ in spectrum + θ = mod2pi(angle(λ) + thetaorigin) - thetaorigin + push!(points, Point2d(θ, abs(λ))) + end + return points + end + + map!(tp.attributes, [:spectrum_data], :colors) do data + colors = typeof(sector_color(1))[] + for (i, (_, spectrum)) in enumerate(data) + append!(colors, fill(sector_color(i), length(spectrum))) + end + return colors end - xlims!(ax, thetaorigin - 0.1, thetaorigin + 2π + 0.1) - ylims!(ax, nothing, 1.05) - if !isempty(plotted_sectors) # cannot use current_figure() when supporting in-place method - axislegend( - ax, tp.plots, [sector_formatter(s) for s in plotted_sectors]; - position = legend_position + map!(tp.attributes, [:thetaorigin], :axis_info) do thetaorigin + return (; + xlabel = L"\theta", + xlabelsize = 24, + xticks = pitick(0, 2pi, 4; mode = :latex), + xticklabelsize = 16, + ylabel = L"r", + ylabelsize = 24, + yticks = (range(0, 1.0; length = 6), latexstring.(range(0, 1.0; length = 6))), + yticklabelsize = 16, + xgridvisible = true, + ygridvisible = true, + leftspinevisible = true, + rightspinevisible = false, + bottomspinevisible = true, + topspinevisible = false, + limits = ((thetaorigin - 0.1, thetaorigin + 2π + 0.1), (nothing, 1.05)), ) end + + map!(tp.attributes, [:spectrum_data, :sector_formatter], :legend_entries) do data, sector_formatter + return [(sector_formatter(sector), sector_color(i)) for (i, (sector, _)) in enumerate(data)] + end + + scatter!(tp, tp.positions; color = tp.colors, markersize = tp.markersize, marker = tp.marker) return tp end +function add_sector_legend!(ax, plot, legend_position) + ax isa Makie.AbstractAxis || return nothing + entries = plot.attributes[:legend_entries][] + isempty(entries) && return nothing + elements = [MarkerElement(; color, marker = :circle, markersize = 12) for (_, color) in entries] + # cannot use current_figure() when supporting in-place method + axislegend(ax, elements, [label for (label, _) in entries]; position = legend_position) + return nothing +end + function MPSKit.transferplot(above, below = above; plotkwargs = (;), kwargs...) p = transferplot(above, below; kwargs...) - apply_plotkwargs!(p.axis, plotkwargs) + apply_axis_info!(p.axis, p.plot, plotkwargs) + add_sector_legend!(p.axis, p.plot, p.plot.legend_position[]) return p end @@ -207,17 +242,18 @@ function MPSKit.transferplot!( plotkwargs = (;), kwargs... ) p = transferplot!(above, below; kwargs...) - apply_plotkwargs!(Makie.current_axis(), plotkwargs) + ax = Makie.current_axis() + apply_axis_info!(ax, p, plotkwargs) + add_sector_legend!(ax, p, p.legend_position[]) return p end function MPSKit.transferplot!( target, above::MPSKit.AbstractMPS, below::MPSKit.AbstractMPS = above; plotkwargs = (;), kwargs... ) - p = with_current_axis(target) do - transferplot!(target, above, below; kwargs...) - end - apply_plotkwargs!(target, plotkwargs) + p = transferplot!(target, above, below; kwargs...) + apply_axis_info!(target, p, plotkwargs) + add_sector_legend!(target, p, p.legend_position[]) return p end diff --git a/ext/MPSKitPlotsExt.jl b/ext/MPSKitPlotsExt.jl index 5b18d6220..464ab2efd 100644 --- a/ext/MPSKitPlotsExt.jl +++ b/ext/MPSKitPlotsExt.jl @@ -109,7 +109,6 @@ MPSKit.entanglementplot!(args...; plotkwargs = (;), kwargs...) = entanglementplo end end - title --> "Transfer Spectrum" legend --> false grid --> :xy framestyle --> :zerolines diff --git a/test/misc/makie.jl b/test/misc/makie.jl index a7df3120a..cdd39968e 100644 --- a/test/misc/makie.jl +++ b/test/misc/makie.jl @@ -43,8 +43,8 @@ using CairoMakie fig2 = Figure() target = Axis(fig2[1, 1]) current = Axis(fig2[1, 2]) # created last, so this is the current axis - entanglementplot!(target, ψ) - @test target.title[] != "" + entanglementplot!(target, ψ; plotkwargs = (; title = "custom")) + @test target.title[] == "custom" @test current.title[] == "" end