From 08bb5679ed2006c1713dba35ac2b022431dafaa3 Mon Sep 17 00:00:00 2001 From: Orjan Ameye Date: Thu, 6 Aug 2026 15:21:43 +0200 Subject: [PATCH] feat: enhance SLH display with detailed descriptions for S, L, and H --- src/SLH.jl | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ test/test_SLH.jl | 53 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/src/SLH.jl b/src/SLH.jl index 34bc2d0..78954e2 100644 --- a/src/SLH.jl +++ b/src/SLH.jl @@ -202,6 +202,69 @@ Return the Hamiltonian `H` of an SLH object. hamiltonian(G::SLH) = G.hamiltonian +# ────────────────────────────────────────────── +# Display +# ────────────────────────────────────────────── + +# `S`, `L` and `H` hold expressions that grow without bound, so they are described +# rather than printed; the accessors give the operators themselves. + +_is_symbolic(x) = x isa SQA.QField || x isa BasicSymbolic || x isa Symbolics.Num +_is_symbolic(x::Complex) = _is_symbolic(real(x)) || _is_symbolic(imag(x)) + +function _is_identity(S::AbstractMatrix) + for j in axes(S, 2), i in axes(S, 1) + x = S[i, j] + _is_time_dep(x) && return false + (i == j ? _isunit(x) : iszero(x)) || return false + end + return true +end + +_nterms(x) = x isa SQA.QAdd && hasproperty(x, :arguments) ? length(x.arguments) : 1 + +_plural(n::Int, noun::AbstractString) = string(n, " ", noun, n == 1 ? "" : "s") + +_dims(x) = string(size(x, 1), "×", size(x, 2)) + +function _describe(x) + _is_time_dep(x) && return "time-dependent" + iszero(x) && return "0" + x isa SQA.QField && return string("symbolic, ", _plural(_nterms(x), "term")) + x isa QuantumOpticsBase.AbstractOperator && return string("numeric, ", _dims(x)) + _is_symbolic(x) && return "symbolic" + return sprint(show, x) # a plain number describes itself +end + +function _describe_scattering(S::AbstractMatrix) + _is_identity(S) && return string(_dims(S), " identity") + any(_is_time_dep, S) && return string(_dims(S), " time-dependent") + return string(_dims(S), " ", _is_symbolic(first(S)) ? "symbolic" : string(eltype(S))) +end + +function _describe_lindblad(L::AbstractVector) + n = length(L) + any(_is_time_dep, L) && return _plural(n, "time-dependent jump operator") + x = first(L) + numeric = x isa QuantumOpticsBase.AbstractOperator + adjective = numeric ? "numeric " : _is_symbolic(x) ? "symbolic " : "" + desc = _plural(n, adjective * "jump operator") + notes = numeric ? [_dims(x)] : String[] + nzero = count(iszero, L) + nzero == 0 || push!(notes, nzero == n ? "all zero" : string(nzero, " zero")) + return isempty(notes) ? desc : string(desc, " (", join(notes, ", "), ")") +end + +Base.show(io::IO, ::SLH{N}) where {N} = + print(io, "SLH{", N, "} with ", N, N == 1 ? " port" : " ports") + +function Base.show(io::IO, ::MIME"text/plain", G::SLH) + show(io, G) + print(io, "\n S = ", _describe_scattering(scattering(G))) + print(io, "\n L = ", _describe_lindblad(lindblad(G))) + return print(io, "\n H = ", _describe(hamiltonian(G))) +end + # ────────────────────────────────────────────── # Equality # ────────────────────────────────────────────── diff --git a/test/test_SLH.jl b/test/test_SLH.jl index b4792b7..ab1105e 100644 --- a/test/test_SLH.jl +++ b/test/test_SLH.jl @@ -95,6 +95,59 @@ using Test @test iszero(simplify(lindblad(G_cas)[2] - (gv2' * av2))) end + @testset "show" begin + @test sprint(show, G_u) == "SLH{1} with 1 port" + @test sprint(show, MIME("text/plain"), G_u) == + "SLH{1} with 1 port\n" * + " S = 1×1 identity\n" * + " L = 1 symbolic jump operator\n" * + " H = 0" + + @test sprint(show, MIME("text/plain"), G_u ⊞ G_c) == + "SLH{2} with 2 ports\n" * + " S = 2×2 identity\n" * + " L = 2 symbolic jump operators\n" * + " H = symbolic, 1 term" + + # no equation ever reaches the output, whatever the system size + M = 6 + @variables s[1:M, 1:M]::Complex + ops = [gu * au, √(γ) * c, gv * av, gu * au * c' * av, c' * c * av, au * av' * c] + G_big = SLH([s[i, j] for i = 1:M, j = 1:M], ops, sum(ops[i]' * ops[i] for i = 1:M)) + @test sprint(show, MIME("text/plain"), G_big) == + "SLH{6} with 6 ports\n" * + " S = 6×6 symbolic\n" * + " L = 6 symbolic jump operators\n" * + " H = symbolic, 8 terms" + + # passive component: nothing couples to the ports + @variables r::Real τ::Real + @test sprint(show, MIME("text/plain"), SLH([r τ; τ -r], [0, 0], 0)) == + "SLH{2} with 2 ports\n" * + " S = 2×2 symbolic\n" * + " L = 2 jump operators (all zero)\n" * + " H = 0" + + L_f = FunctionWrapper{typeof(gu' * au),Tuple{Float64}}(t -> gu' * au) + @test sprint(show, MIME("text/plain"), SLH(1, L_f, 0 * au)) == + "SLH{1} with 1 port\n" * + " S = 1×1 identity\n" * + " L = 1 time-dependent jump operator\n" * + " H = time-dependent" + + bc = FockBasis(4) + a_op = destroy(bc) + @test sprint( + show, + MIME("text/plain"), + SLH(1, sparse(a_op), sparse(dagger(a_op) * a_op)), + ) == + "SLH{1} with 1 port\n" * + " S = 1×1 identity\n" * + " L = 1 numeric jump operator (5×5)\n" * + " H = numeric, 5×5" + end + @testset "numeric type stability" begin bc = FockBasis(4) a_op = destroy(bc)