PennyLaneAI / PennyLaneAI/catalyst
adjoint-lowering aborts with IRMapping assertion instead of emitting a diagnostic
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 234
- Forks
- 84
- Avg merge
- 2d 15h
- Merged PRs (30d)
- 66
Description
Summary
adjoint-lowering aborts with an LLVM assertion (SIGABRT) instead of emitting a diagnostic, when applied to a module containing non-trivial adjoint regions.
In a reduced case the same pass correctly reports error: Unhandled operation in adjoint region, so the abort looks like a missing bail-out path rather than an intended rejection.
Crash
catalyst failed with error code -6:
Assertion failed: (result && "expected 'from' to be contained within the map"),
function lookup, file IRMapping.h, line 74.
Stack dump:
9 (anonymous namespace)::AdjointGenerator::generateImpl(mlir::Region&, mlir::OpBuilder&) + 5728
10 catalyst::quantum::generateAdjointReversePass(mlir::Region&, mlir::OpBuilder&, mlir::IRMapping&, catalyst::quantum::QuantumCache&) + 40
11 (anonymous namespace)::AdjointSingleOpRewritePattern::matchAndRewrite(catalyst::quantum::AdjointOp, mlir::PatternRewriter&) const + 748
16 catalyst::quantum::AdjointLoweringPass::runOnOperation() + 644
The pipeline is
Quantum(canonicalize;builtin.module(apply-transform-sequence);inline-nested-module)
with the QNode decorated by @qp.transform(pass_name="adjoint-lowering").
Reproducer
The circuit is qp.TrotterVibronic (no4_anthracene, 16 grid points, 1 Trotter step) decomposed with
catalyst_decompose(target_gates={"TemporaryAND": 500, "Adjoint(TemporaryAND)": 1} | Clifford | ctrl-flow | measurements),
then adjoint-lowering → to-ppr → ppr-to-ppm, evaluated via qp.specs(circuit, level="all-mlir")().
TrotterVibronic emits lazy adjoint(...) wrappers around subroutines that themselves contain
for_loop/cond regions (adjoint(SignedOutSquare(...)), adjoint(SignedOutMultiplier(...)),
adjoint(_aqft(...)) in pennylane/templates/subroutines/time_evolution/trotter_vibronic.py),
so the adjoint regions contain nested control flow.
I can share the full workflow script if useful; it is not yet reduced to a standalone snippet, since a
smaller hand-written case takes the clean-diagnostic path instead:
import pennylane as qp
from catalyst.device.decomposition import catalyst_decompose
qp.decomposition.enable_graph()
gates = {"TemporaryAND": 500, "Hadamard": 10, "S": 10, "Adjoint(S)": 10, "CNOT": 50,
"PauliX": 1, "GlobalPhase": 0, "ForLoop": 1, "Cond": 1, "HybridAdjoint": 1,
"MidCircuitMeasure": 1}
pipelines = [("Quantum", ["canonicalize",
"builtin.module(apply-transform-sequence)",
"inline-nested-module"])]
@qp.qjit(capture=False, target="mlir", pipelines=pipelines)
@qp.transform(pass_name="adjoint-lowering")
@catalyst_decompose(capabilities=None, target_gates=gates)
@qp.qnode(qp.device("null.qubit", wires=4))
def circuit():
def body():
@qp.for_loop(2)
def loop(i):
qp.TemporaryAND([0, 1, 2])
qp.adjoint(qp.TemporaryAND([0, 1, 2]))
loop()
qp.adjoint(body)()
return qp.expval(qp.Z(0))
qp.specs(circuit, level="all-mlir")()
This one fails cleanly with
error: Unhandled operation in adjoint region
%36:2 = "quantum.measure"(%35) : (!quantum.bit) -> (i1, !quantum.bit)
(reaching the pass requires relaxing the frontend is_invertible check, since
verify_operations otherwise rejects the measurement first).
Expected
adjoint-lowering should emit a diagnostic (like Unhandled operation in adjoint region) rather than
tripping the IRMapping assertion and aborting the compiler.
Related question
Is measurement-based uncompute inside adjoint regions intended to be supported at all? Catalyst
currently rejects it in three separate places:
_check_no_measurements—Mid-circuit measurements cannot be used within an adjoint() or ctrl() region.
(catalyst/api_extensions/quantum_operators.py:911)verify_operations/_inv_op_checker—MidCircuitMeasure is not invertible on 'null.qubit' deviceadjoint-lowering—Unhandled operation in adjoint regiononquantum.measure
This matters for TemporaryAND (elbow) uncompute, whose cheap form is measurement-based. When such an
elbow ends up inside an adjoint region the only alternatives are the unitary Clifford+T uncompute
(worse T counts) or keeping Adjoint(TemporaryAND) opaque, which lowers to a Toffoli and is then
rejected by to-ppr (Unsupported gate for PBC conversion). Guidance on the intended path here would
be very welcome.
Versions
- pennylane
0.46.0.dev(editable,Xanadu/pennylane) - pennylane-catalyst (editable,
Xanadu/catalyst) - macOS arm64, Python 3.13
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at AdjointGenerator::generateImpl and generateAdjointReversePass in the crash stack, then compare their handling with the clean “Unhandled operation in adjoint region” path. Review IRMapping::lookup and the adjoint-lowering pass entry point, along with the measurement checks in quantum_operators.py. Done means the reproducer emits a diagnostic instead of triggering the IRMapping assertion or SIGABRT.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100