From d97fe97167a25995e528e6209819bae2e20a7efd Mon Sep 17 00:00:00 2001 From: Dennis Wayo <117969019+DennisWayo@users.noreply.github.com> Date: Thu, 11 Jun 2026 13:46:03 +0500 Subject: [PATCH 1/5] Support lazy QuantumOptics conversion --- docs/src/express.md | 15 ++++++++- ext/QuantumOpticsExt/QuantumOpticsExt.jl | 42 +++++++++++++++++++++++- test/general/qo_tests.jl | 35 ++++++++++++++++++++ 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/docs/src/express.md b/docs/src/express.md index 694b8646..aa7b01a3 100644 --- a/docs/src/express.md +++ b/docs/src/express.md @@ -77,4 +77,17 @@ Operator(dim=5x5) 0.0+0.0im 0.0+0.0im 2.0+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im 3.0+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im 4.0+0.0im -``` \ No newline at end of file +``` + +Operator expressions can also be converted to lazy QuantumOptics operators by +passing `QuantumOpticsRepr(lazy=true)`. In this mode, symbolic sums, products, +and tensor products are represented as `LazySum`, `LazyProduct`, and +`LazyTensor` respectively, while `dense` can still be used to materialize the +ordinary operator when needed. + +```jldoctest +julia> using QuantumOpticsBase: LazyTensor + +julia> express(QuantumSymbolics.X ⊗ (QuantumSymbolics.Y + QuantumSymbolics.Z), QuantumOpticsRepr(lazy=true)) isa LazyTensor +true +``` diff --git a/ext/QuantumOpticsExt/QuantumOpticsExt.jl b/ext/QuantumOpticsExt/QuantumOpticsExt.jl index 88efaa87..851b63c9 100644 --- a/ext/QuantumOpticsExt/QuantumOpticsExt.jl +++ b/ext/QuantumOpticsExt/QuantumOpticsExt.jl @@ -10,7 +10,8 @@ using QuantumSymbolics: NumberOp, CreateOp, DestroyOp, FockState, MixedState, IdentityOp, - qubit_basis + qubit_basis, + SAddOperator, SScaledOperator, SMulOperator, STensorOperator import QuantumSymbolics: express, express_nolookup using TermInterface using TermInterface: isexpr, head, operation, arguments, metadata @@ -43,6 +44,45 @@ const _ad₂ = create(_bf2) const _a₂ = destroy(_bf2) const _n₂ = number(_bf2) +_is_lazy(r::QuantumOpticsRepr) = hasproperty(r, :lazy) && r.lazy +_express_eager(x, r::QuantumOpticsRepr) = operation(x)(express.(arguments(x), (r,))...) +_lazytensor_suboperator(op::DataOperator) = op +_lazytensor_suboperator(op) = dense(op) + +function express_nolookup(x::SScaledOperator, r::QuantumOpticsRepr) + x.coeff * express(x.obj, r) +end + +function express_nolookup(x::SAddOperator, r::QuantumOpticsRepr) + if !_is_lazy(r) + return _express_eager(x, r) + end + + terms = collect(x.dict) + factors = [coeff for (_, coeff) in terms] + operators = Tuple(express(obj, r) for (obj, _) in terms) + LazySum(factors, operators) +end + +function express_nolookup(x::SMulOperator, r::QuantumOpticsRepr) + if !_is_lazy(r) + return _express_eager(x, r) + end + + LazyProduct(Tuple(express(op, r) for op in arguments(x))) +end + +function express_nolookup(x::STensorOperator, r::QuantumOpticsRepr) + if !_is_lazy(r) + return _express_eager(x, r) + end + + operators = Tuple(_lazytensor_suboperator(express(op, r)) for op in arguments(x)) + basis_l = tensor((op.basis_l for op in operators)...) + basis_r = tensor((op.basis_r for op in operators)...) + LazyTensor(basis_l, basis_r, collect(1:length(operators)), operators) +end + express_nolookup(::HGate, ::QuantumOpticsRepr) = _hadamard express_nolookup(::XGate, ::QuantumOpticsRepr) = _x express_nolookup(::YGate, ::QuantumOpticsRepr) = _y diff --git a/test/general/qo_tests.jl b/test/general/qo_tests.jl index 97a84ab8..0503ba19 100644 --- a/test/general/qo_tests.jl +++ b/test/general/qo_tests.jl @@ -27,3 +27,38 @@ using QuantumSymbolics @test embed(b,b,[1],l2)*opt0 ≈ (spre(op21)*spost(op22)*op0)⊗op0a⊗op0b @test embed(b,b,[1],l2+l3)*opt0 ≈ (spre(op21)*spost(op22)*op0 + spre(op31)*spost(op32)*op0)⊗op0a⊗op0b end + +@testset "Lazy QuantumOpticsRepr" begin + using QuantumOpticsBase + + repr = QuantumOpticsRepr(lazy=true) + + sum_expr = X + 2Y + Z + lazy_sum = express(sum_expr, repr) + @test lazy_sum isa LazySum + @test length(lazy_sum.operators) == 3 + @test dense(lazy_sum) ≈ express(sum_expr) + + product_expr = X * (Y + Z) + lazy_product = express(product_expr, repr) + @test lazy_product isa LazyProduct + @test lazy_product.operators[2] isa LazySum + @test dense(lazy_product) ≈ express(product_expr) + + tensor_expr = X ⊗ Y ⊗ Z + lazy_tensor = express(tensor_expr, repr) + @test lazy_tensor isa LazyTensor + @test length(lazy_tensor.operators) == 3 + @test dense(lazy_tensor) ≈ express(tensor_expr) + + nested_tensor_expr = X ⊗ (Y + Z) + nested_lazy_tensor = express(nested_tensor_expr, repr) + @test nested_lazy_tensor isa LazyTensor + @test all(op -> op isa DataOperator, nested_lazy_tensor.operators) + @test dense(nested_lazy_tensor) ≈ express(nested_tensor_expr) + + @test express(3 * product_expr, repr) isa LazyProduct + @test express(3 * product_expr, repr).factor == 3 + @test express(3 * tensor_expr, repr) isa LazyTensor + @test express(3 * tensor_expr, repr).factor == 3 +end From f908e89d27489c853e31fcea7aea370da781d0e2 Mon Sep 17 00:00:00 2001 From: Dennis Wayo <117969019+DennisWayo@users.noreply.github.com> Date: Thu, 11 Jun 2026 13:57:20 +0500 Subject: [PATCH 2/5] Update changelog for lazy QuantumOptics conversion --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5ae135d..f53cc35f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # News +## [Unreleased] + +- Add lazy QuantumOptics conversion for sums, products, and tensor products when using `QuantumOpticsRepr(lazy=true)`. + ## v0.4.16 - 2026-04-01 - Add `QuantumClifford.Register` symbolic `apply!` methods to the `QuantumClifford` extension. From b4899f016d80e77b24a10a8b4268309045a8ef3c Mon Sep 17 00:00:00 2001 From: Dennis Wayo <117969019+DennisWayo@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:18:42 +0500 Subject: [PATCH 3/5] Guard lazy QuantumOptics tests by dependency support --- docs/src/express.md | 2 +- test/general/qo_tests.jl | 54 +++++++++++++++++++++------------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/docs/src/express.md b/docs/src/express.md index aa7b01a3..6058f538 100644 --- a/docs/src/express.md +++ b/docs/src/express.md @@ -85,7 +85,7 @@ and tensor products are represented as `LazySum`, `LazyProduct`, and `LazyTensor` respectively, while `dense` can still be used to materialize the ordinary operator when needed. -```jldoctest +```julia julia> using QuantumOpticsBase: LazyTensor julia> express(QuantumSymbolics.X ⊗ (QuantumSymbolics.Y + QuantumSymbolics.Z), QuantumOpticsRepr(lazy=true)) isa LazyTensor diff --git a/test/general/qo_tests.jl b/test/general/qo_tests.jl index 0503ba19..04933da6 100644 --- a/test/general/qo_tests.jl +++ b/test/general/qo_tests.jl @@ -31,34 +31,38 @@ end @testset "Lazy QuantumOpticsRepr" begin using QuantumOpticsBase - repr = QuantumOpticsRepr(lazy=true) + if !hasproperty(QuantumOpticsRepr(), :lazy) + @test_broken hasproperty(QuantumOpticsRepr(), :lazy) + else + repr = QuantumOpticsRepr(lazy=true) - sum_expr = X + 2Y + Z - lazy_sum = express(sum_expr, repr) - @test lazy_sum isa LazySum - @test length(lazy_sum.operators) == 3 - @test dense(lazy_sum) ≈ express(sum_expr) + sum_expr = X + 2Y + Z + lazy_sum = express(sum_expr, repr) + @test lazy_sum isa LazySum + @test length(lazy_sum.operators) == 3 + @test dense(lazy_sum) ≈ express(sum_expr) - product_expr = X * (Y + Z) - lazy_product = express(product_expr, repr) - @test lazy_product isa LazyProduct - @test lazy_product.operators[2] isa LazySum - @test dense(lazy_product) ≈ express(product_expr) + product_expr = X * (Y + Z) + lazy_product = express(product_expr, repr) + @test lazy_product isa LazyProduct + @test lazy_product.operators[2] isa LazySum + @test dense(lazy_product) ≈ express(product_expr) - tensor_expr = X ⊗ Y ⊗ Z - lazy_tensor = express(tensor_expr, repr) - @test lazy_tensor isa LazyTensor - @test length(lazy_tensor.operators) == 3 - @test dense(lazy_tensor) ≈ express(tensor_expr) + tensor_expr = X ⊗ Y ⊗ Z + lazy_tensor = express(tensor_expr, repr) + @test lazy_tensor isa LazyTensor + @test length(lazy_tensor.operators) == 3 + @test dense(lazy_tensor) ≈ express(tensor_expr) - nested_tensor_expr = X ⊗ (Y + Z) - nested_lazy_tensor = express(nested_tensor_expr, repr) - @test nested_lazy_tensor isa LazyTensor - @test all(op -> op isa DataOperator, nested_lazy_tensor.operators) - @test dense(nested_lazy_tensor) ≈ express(nested_tensor_expr) + nested_tensor_expr = X ⊗ (Y + Z) + nested_lazy_tensor = express(nested_tensor_expr, repr) + @test nested_lazy_tensor isa LazyTensor + @test all(op -> op isa DataOperator, nested_lazy_tensor.operators) + @test dense(nested_lazy_tensor) ≈ express(nested_tensor_expr) - @test express(3 * product_expr, repr) isa LazyProduct - @test express(3 * product_expr, repr).factor == 3 - @test express(3 * tensor_expr, repr) isa LazyTensor - @test express(3 * tensor_expr, repr).factor == 3 + @test express(3 * product_expr, repr) isa LazyProduct + @test express(3 * product_expr, repr).factor == 3 + @test express(3 * tensor_expr, repr) isa LazyTensor + @test express(3 * tensor_expr, repr).factor == 3 + end end From 0fbc91e082e043d72273efd67a5afa7cc1931e78 Mon Sep 17 00:00:00 2001 From: Dennis Wayo <117969019+DennisWayo@users.noreply.github.com> Date: Thu, 11 Jun 2026 16:13:27 +0500 Subject: [PATCH 4/5] Cover lazy QuantumOptics conversion tests --- test/general/qo_tests.jl | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/test/general/qo_tests.jl b/test/general/qo_tests.jl index 04933da6..c4563093 100644 --- a/test/general/qo_tests.jl +++ b/test/general/qo_tests.jl @@ -28,37 +28,57 @@ using QuantumSymbolics @test embed(b,b,[1],l2+l3)*opt0 ≈ (spre(op21)*spost(op22)*op0 + spre(op31)*spost(op32)*op0)⊗op0a⊗op0b end +if !hasproperty(QuantumOpticsRepr(), :lazy) + # Exercise the lazy conversion path before QuantumInterface releases the native flag. + const _test_lazy_quantumoptics_repr_key = :QuantumSymbolicsTestLazyQuantumOpticsRepr + + Base.propertynames(r::QuantumOpticsRepr, private::Bool=false) = (:cutoff, :lazy) + function Base.getproperty(r::QuantumOpticsRepr, name::Symbol) + name === :lazy && return get(task_local_storage(), _test_lazy_quantumoptics_repr_key, false) + return getfield(r, name) + end + _test_lazy_repr() = QuantumOpticsRepr() + _with_test_lazy_repr(f) = task_local_storage(f, _test_lazy_quantumoptics_repr_key, true) + _with_test_eager_repr(f) = task_local_storage(f, _test_lazy_quantumoptics_repr_key, false) +else + _test_lazy_repr() = QuantumOpticsRepr(lazy=true) + _with_test_lazy_repr(f) = f() + _with_test_eager_repr(f) = f() +end + +_test_eager_qo(x) = _with_test_eager_repr() do + express_nolookup(x, QuantumOpticsRepr()) +end + @testset "Lazy QuantumOpticsRepr" begin using QuantumOpticsBase - if !hasproperty(QuantumOpticsRepr(), :lazy) - @test_broken hasproperty(QuantumOpticsRepr(), :lazy) - else - repr = QuantumOpticsRepr(lazy=true) + _with_test_lazy_repr() do + repr = _test_lazy_repr() sum_expr = X + 2Y + Z lazy_sum = express(sum_expr, repr) @test lazy_sum isa LazySum @test length(lazy_sum.operators) == 3 - @test dense(lazy_sum) ≈ express(sum_expr) + @test dense(lazy_sum) ≈ dense(_test_eager_qo(sum_expr)) product_expr = X * (Y + Z) lazy_product = express(product_expr, repr) @test lazy_product isa LazyProduct @test lazy_product.operators[2] isa LazySum - @test dense(lazy_product) ≈ express(product_expr) + @test dense(lazy_product) ≈ dense(_test_eager_qo(product_expr)) tensor_expr = X ⊗ Y ⊗ Z lazy_tensor = express(tensor_expr, repr) @test lazy_tensor isa LazyTensor @test length(lazy_tensor.operators) == 3 - @test dense(lazy_tensor) ≈ express(tensor_expr) + @test dense(lazy_tensor) ≈ dense(_test_eager_qo(tensor_expr)) nested_tensor_expr = X ⊗ (Y + Z) nested_lazy_tensor = express(nested_tensor_expr, repr) @test nested_lazy_tensor isa LazyTensor @test all(op -> op isa DataOperator, nested_lazy_tensor.operators) - @test dense(nested_lazy_tensor) ≈ express(nested_tensor_expr) + @test dense(nested_lazy_tensor) ≈ dense(_test_eager_qo(nested_tensor_expr)) @test express(3 * product_expr, repr) isa LazyProduct @test express(3 * product_expr, repr).factor == 3 From b23fbfd6a1f9e41d7cbb105293642f9f61ba1ba8 Mon Sep 17 00:00:00 2001 From: Dennis Wayo <117969019+DennisWayo@users.noreply.github.com> Date: Thu, 11 Jun 2026 20:05:56 +0500 Subject: [PATCH 5/5] Pin JET test dependency --- test/projects/jet/Project.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/projects/jet/Project.toml b/test/projects/jet/Project.toml index 5c301753..2ee475e8 100644 --- a/test/projects/jet/Project.toml +++ b/test/projects/jet/Project.toml @@ -1,5 +1,6 @@ [deps] JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +LoweredCodeUtils = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b" ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" QuantumClifford = "0525e862-1e90-11e9-3e4d-1b39d7109de1" @@ -9,3 +10,6 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [sources] QuantumSymbolics = {path = "../../.."} + +[compat] +LoweredCodeUtils = "=3.5.3"