Add an efficient LazyEmbed / `embed_lazy` API
unitaryhack
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 条评论