ITADN

Add an efficient LazyEmbed / `embed_lazy` API

#523OpenKrastanov 创建于 2026-05-12
unitaryhack
K
Krastanovcommented
This issue is part of **unitaryHACK26**. You have to be [registered](https://unitaryhack.dev/register/) to complete this issue. Learn more about the PR submission process [here](https://unitaryhack.dev/hacker-guide/) and about unitaryHACK rules [here](https://unitary.design/rules/)! **A note about AI Slop:** while we are open to collaboration with LLMs for unitaryHACK, fully AI-generated PRs are not acceptable. It is at the maintainers' discretion whether or not LLM-generated PRs are the right fit for the issues, and those that appear fully AI-generated may be immediately rejected. Read unitaryHACK's full AI Policy [here](https://unitaryhack.dev/ai-guide/). Note from the maintainer: If you have not contributed before to open source projects, no AI agent use is permitted for this issue. --- ## Context `QuantumOpticsBase.jl` has two related concepts today: - `embed(...)`, which embeds an operator into a larger composite basis. - `LazyTensor`, which can represent local tensor-product structure without materializing the full matrix. For dense and sparse operators, `embed(...)` currently builds the embedded operator eagerly. That is often much more expensive than necessary when the embedded operator will be applied to a state or used as one term in a Hamiltonian. For example, embedding a one-site operator in a many-site spin chain should not need to allocate the full `2^n x 2^n` sparse matrix if a lazy representation can do the same application directly. There was an old starting-point PR for this idea: - https://github.com/qojulia/QuantumOpticsBase.jl/pull/88/changes That PR proposed a small `embed_lazy` helper: ```julia embed_lazy(b::Basis, i, op::AbstractOperator) = LazyTensor(b, i, op) function embed_lazy(b::Basis, i, op::LazySum) _embed_ops(b, i, ops::Tuple) = ((embed_lazy(b, i, o) for o in ops)...,) _embed_ops(b, i, ops) = [embed_lazy(b, i, o) for o in ops] LazySum(b, b, op.factors, _embed_ops(b, i, op.operators)) end embed_lazy(b::Basis, indices, op::LazyTensor) = LazyTensor(b, b, indices, op.operators, op.factor) ``` This issue is to turn that idea into a robust, tested, documented API. Relevant current code: - Eager `embed` implementation for `DataOperator`: - https://github.com/qojulia/QuantumOpticsBase.jl/blob/master/src/operators.jl - Current `LazySum` embed support: - https://github.com/qojulia/QuantumOpticsBase.jl/blob/master/src/operators_lazysum.jl - Current `LazyTensor` implementation: - https://github.com/qojulia/QuantumOpticsBase.jl/blob/master/src/operators_lazytensor.jl - Lazy operator documentation: - https://github.com/qojulia/QuantumOptics.jl/blob/master/docs/src/quantumobjects/operators.md#lazy-operators ## Why this matters Local operators embedded into composite Hilbert spaces are a common pattern in quantum optics and spin-chain simulations. The eager `embed` path is correct and useful, but it can allocate large sparse matrices that are avoidable for many workflows. An efficient lazy embedding API would let users write: ```julia H = embed_lazy(basis, 1, sx) + embed_lazy(basis, 2, sz) ``` and get a structure-preserving lazy operator suitable for multiplication by kets, use inside `LazySum`, and eventual dense/sparse conversion only when explicitly requested. ## Minimal target example After this change, the following should work: ```julia using Test using QuantumOpticsBase b0 = SpinBasis(1//2) b = tensor(b0, b0, b0, b0) sx = sigmax(b0) sz = sigmaz(b0) op_eager = embed(b, 2, sx) op_lazy = embed_lazy(b, 2, sx) psi = Ket(b, randn(ComplexF64, length(b))) @test op_lazy isa LazyTensor @test dense(op_lazy) == dense(op_eager) @test op_lazy * psi == op_eager * psi local_h = sx + 0.5 * sz lazy_h = embed_lazy(b, 3, local_h) @test lazy_h isa LazySum @test dense(lazy_h) == dense(embed(b, 3, local_h)) @test lazy_h * psi == embed(b, 3, local_h) * psi ``` ## Scope In scope: - Add a public API for lazy embedding. The exact name can be decided by maintainers, but `embed_lazy` is the historical name from PR #88 and is a good starting point. - Export the public API from `QuantumOpticsBase.jl`. - Implement lazy embedding for: - `AbstractOperator` / `DataOperator`, returning a `LazyTensor` when possible, - `LazyTensor`, re-embedding the existing lazy tensor into the larger basis, - `LazySum`, preserving the `LazySum` and lazily embedding each term, - `TimeDependentSum`, if straightforward, preserving coefficients while lazily embedding the static operator terms. - Support both single-index and multi-index embedding, matching the existing `embed` behavior where possible. - Preserve basis checks and error behavior: incompatible bases should fail clearly rather than silently constructing invalid lazy operators. - Add tests showing that lazy embedding agrees with eager `embed` under `dense(...)` and under application to `Ket`. - Add documentation near the existing lazy-operator docs. - Add at least small allocation/time checks or benchmarks that demonstrate the lazy path avoids materializing the full embedded matrix for representative local operators. Out of scope: - Changing the default behavior of `embed(...)`. - Replacing `LazyTensor`, `LazySum`, or `LazyProduct`. - Supporting every possible `AbstractOperator` subtype if the subtype cannot be represented lazily without materialization. - GPU-specific behavior. ## API considerations Please address these points in the PR: - Should the public function be named `embed_lazy`, `lazyembed`, or should there be a `LazyEmbed` constructor/type? - Should `embed_lazy` live next to `embed` in the docs, or in the lazy-operator section? - Should `embed_lazy(basis_l, basis_r, indices, op)` be supported in addition to `embed_lazy(basis, indices, op)`? - What should happen for a `LazyProduct`? It may be enough to document that this is unsupported initially, or to embed each factor lazily when bases make that unambiguous. The implementation should prefer a simple, maintainable API over broad coverage. It is acceptable to support the common local-operator and `LazySum` cases first. ## Acceptance criteria - `embed_lazy` or the chosen public API exists and is exported. - Embedding a one-site dense or sparse operator into a composite basis returns a lazy operator, not an eager embedded sparse matrix. - Embedding a `LazySum` preserves a `LazySum` of lazy embedded terms. - Embedding a `LazyTensor` into a larger composite basis preserves the existing suboperators and factor. - The lazy result agrees with eager `embed` for representative dense conversion and `Ket` application tests. - Tests cover single-index and multi-index embedding, dense and sparse local operators, `LazySum`, and at least one incompatible-basis failure. - Documentation includes a short example and explains how this differs from `embed(...)` and from directly constructing `LazyTensor`. ## Difficulty Intermediate. The core implementation can be small, but the hard part is making the API precise, preserving existing basis semantics, and adding enough tests to avoid another under-specified lazy embedding helper.
1 条评论