PennyLaneAI / PennyLaneAI/catalyst

adjoint-lowering aborts with IRMapping assertion instead of emitting a diagnostic

Open
#3,217 0 comments 0 reactions 0 assignees View on GitHub

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-loweringto-pprppr-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:

  1. _check_no_measurementsMid-circuit measurements cannot be used within an adjoint() or ctrl() region.
    (catalyst/api_extensions/quantum_operators.py:911)
  2. verify_operations / _inv_op_checkerMidCircuitMeasure is not invertible on 'null.qubit' device
  3. adjoint-loweringUnhandled operation in adjoint region on quantum.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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.