diff --git a/include/cudaq/Optimizer/InitAllPasses.h b/include/cudaq/Optimizer/InitAllPasses.h index 91724d36a40..2279e20e0e9 100644 --- a/include/cudaq/Optimizer/InitAllPasses.h +++ b/include/cudaq/Optimizer/InitAllPasses.h @@ -21,6 +21,7 @@ inline void registerCudaqPassesAndPipelines() { // CUDA-Q pipelines opt::registerAggressiveEarlyInliningPipeline(); + opt::registerPhasePolynomialOptimizationPipeline(); opt::registerUnrollingPipeline(); opt::registerClassicalOptimizationPipeline(); opt::registerToExecutionManagerCCPipeline(); diff --git a/include/cudaq/Optimizer/Transforms/Passes.h b/include/cudaq/Optimizer/Transforms/Passes.h index f3ccfc5baa9..7eb9822f205 100644 --- a/include/cudaq/Optimizer/Transforms/Passes.h +++ b/include/cudaq/Optimizer/Transforms/Passes.h @@ -25,6 +25,7 @@ void addAggressiveEarlyInlining(mlir::OpPassManager &pm, bool fatalCheck = false); void registerAggressiveEarlyInliningPipeline(); +void registerPhasePolynomialOptimizationPipeline(); void registerUnrollingPipeline(); void registerClassicalOptimizationPipeline(); void registerMappingPipeline(); diff --git a/include/cudaq/Optimizer/Transforms/Passes.td b/include/cudaq/Optimizer/Transforms/Passes.td index b819ad3d7d0..363eefb7cfc 100644 --- a/include/cudaq/Optimizer/Transforms/Passes.td +++ b/include/cudaq/Optimizer/Transforms/Passes.td @@ -865,6 +865,18 @@ def ObserveAnsatz : Pass<"observe-ansatz", "mlir::func::FuncOp"> { ]; } +def PhasePolynomialRotationMerging: Pass<"phase-polynomial-rotation-merging", "mlir::func::FuncOp"> { + let summary = "Perform phase polynomial based rotation merging."; + + let dependentDialects = ["cudaq::cc::CCDialect", "quake::QuakeDialect"]; +} + +def PhasePolynomialPreprocess: Pass<"phase-polynomial-preprocess", "mlir::ModuleOp"> { + let summary = "Isolate subcircuits representable by a single phase polynomial."; + + let dependentDialects = ["cudaq::cc::CCDialect", "quake::QuakeDialect"]; +} + def PromoteRefToVeqAlloc : Pass<"promote-qubit-allocation"> { let summary = "Promote single qubit allocations."; let description = [{ diff --git a/lib/Optimizer/Transforms/CMakeLists.txt b/lib/Optimizer/Transforms/CMakeLists.txt index cf2fc80ed62..51f645b7c51 100644 --- a/lib/Optimizer/Transforms/CMakeLists.txt +++ b/lib/Optimizer/Transforms/CMakeLists.txt @@ -48,6 +48,8 @@ add_cudaq_library(OptTransforms DependencyAnalysis.cpp MultiControlDecomposition.cpp ObserveAnsatz.cpp + PhasePolynomialRotationMerging.cpp + PhasePolynomialPreprocess.cpp PruneCtrlRelations.cpp PySynthCallableBlockArgs.cpp QuakeAddMetadata.cpp @@ -57,6 +59,7 @@ add_cudaq_library(OptTransforms RegToMem.cpp ReplaceStateWithKernel.cpp SROA.cpp + Subcircuit.cpp StatePreparation.cpp UnitarySynthesis.cpp UpdateRegisterNames.cpp diff --git a/lib/Optimizer/Transforms/PhasePolynomialPreprocess.cpp b/lib/Optimizer/Transforms/PhasePolynomialPreprocess.cpp new file mode 100644 index 00000000000..c522e7f1deb --- /dev/null +++ b/lib/Optimizer/Transforms/PhasePolynomialPreprocess.cpp @@ -0,0 +1,278 @@ +/******************************************************************************* + * Copyright (c) 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "PassDetails.h" +#include "Subcircuit.h" +#include "cudaq/Optimizer/Builder/Factory.h" +#include "cudaq/Optimizer/Dialect/CC/CCOps.h" +#include "cudaq/Optimizer/Dialect/Quake/QuakeOps.h" +#include "cudaq/Optimizer/Transforms/Passes.h" +#include "mlir/Transforms/Passes.h" + +namespace cudaq::opt { +#define GEN_PASS_DEF_PHASEPOLYNOMIALPREPROCESS +#include "cudaq/Optimizer/Transforms/Passes.h.inc" +} // namespace cudaq::opt + +#define DEBUG_TYPE "phase-polynomial-preprocess" + +using namespace mlir; + +namespace { +class PhasePolynomialPreprocessPass + : public cudaq::opt::impl::PhasePolynomialPreprocessBase< + PhasePolynomialPreprocessPass> { + using PhasePolynomialPreprocessBase::PhasePolynomialPreprocessBase; + + // TODO: I think this could potentially be generalized nicely + class WireStepper { + Value old_wire; + Value new_wire; + Subcircuit *subcircuit; + bool stopped = false; + + public: + WireStepper(Subcircuit *circuit, Value initial, Value arg) { + subcircuit = circuit; + old_wire = initial; + new_wire = arg; + } + + bool isStopped() { + return stopped || + (stopped = subcircuit->getTerminalWires().contains(old_wire)); + } + + Value getNewWire() { return new_wire; } + + Value getOldWire() { return old_wire; } + + void step(DenseMap &cloned, OpBuilder &builder, + std::function addFuncArg) { + // TODO: Something more elegant here would be nice. + // The problem is that the old_wire may have two uses, + // one in the original block, and one in the new function by the cloned + // op. We want to ignore the cloned op here. + Operation *op = nullptr; + size_t opnum = 0; + for (auto &use : old_wire.getUses()) { + if (use.getOwner()->hasAttr("clone")) + continue; + op = use.getOwner(); + opnum = use.getOperandNumber(); + } + + assert(op); + + if (cloned.count(op) == 1) { + cloned[op]->setOperand(opnum, new_wire); + assert(old_wire.hasOneUse()); + old_wire = getNextResult(old_wire); + new_wire = getNextResult(new_wire); + return; + } + + // Make sure all dependencies have been cloned + for (auto dependency : op->getOperands()) { + if (!isa(dependency.getType())) + continue; + auto dop = dependency.getDefiningOp(); + if (cloned.count(dop) != 1 && + !subcircuit->getInitialWires().contains(dependency)) + return; + } + + auto clone = builder.clone(*op); + clone->setOperand(opnum, new_wire); + clone->setAttr("clone", builder.getUnitAttr()); + + // Make classical values arguments to the function, + // to allow non-constant rotation angles + builder.setInsertionPointToStart(clone->getBlock()); + for (size_t i = 0; i < clone->getNumOperands(); i++) { + auto dependency = clone->getOperand(i); + if (!isa(dependency.getType())) { + auto new_arg = addFuncArg(dependency); + clone->setOperand(i, new_arg); + } + } + builder.setInsertionPointAfter(clone); + + cloned[op] = clone; + assert(old_wire.hasOneUse()); + old_wire = getNextResult(old_wire); + new_wire = getNextResult(new_wire); + } + }; + + void removeOld(Subcircuit &subcircuit, + SmallVector &removal_order, Operation *next) { + if (!subcircuit.getOps().contains(next) || + std::find(removal_order.begin(), removal_order.end(), next) != + removal_order.end()) + return; + + for (auto result : next->getResults()) + for (auto *user : result.getUsers()) + removeOld(subcircuit, removal_order, user); + + removal_order.push_back(next); + } + + void shiftAfter(Operation *pivot, Operation *to_shift) { + if (pivot->isBeforeInBlock(to_shift)) + return; + to_shift->moveAfter(pivot); + for (auto user : to_shift->getUsers()) + shiftAfter(to_shift, user); + } + + void moveToFunc(Subcircuit *subcircuit, size_t subcircuit_num) { + auto module = getOperation(); + SmallVector types(subcircuit->getInitialWires().size(), + quake::WireType::get(module.getContext())); + auto name = std::string("subcircuit") + std::to_string(subcircuit_num); + auto fun = cudaq::opt::factory::createFunction(name, types, types, module); + fun.setPrivate(); + auto entry = fun.addEntryBlock(); + OpBuilder builder(fun); + fun.getOperation()->setAttr("subcircuit", builder.getUnitAttr()); + fun.getOperation()->setAttr( + "num_cnots", builder.getUI32IntegerAttr(subcircuit->numCNots())); + + DenseMap cloned; + + // Need to keep ordering to match returns with arguments + SmallVector args; + SmallVector steppers; + for (auto wire : subcircuit->getInitialWires()) { + args.push_back(wire); + steppers.push_back( + new WireStepper(subcircuit, wire, fun.getArgument(steppers.size()))); + } + + auto add_arg_fun = [&](Value v) { + auto idx = args.size(); + args.push_back(v); + fun.insertArgument(idx, v.getType(), {}, v.getDefiningOp()->getLoc()); + return fun.getArgument(idx); + }; + + builder.setInsertionPointToStart(entry); + while (true) { + auto stepped = false; + for (auto stepper : steppers) { + if (stepper->isStopped()) + continue; + stepped = true; + stepper->step(cloned, builder, add_arg_fun); + } + + if (!stepped) + break; + } + + SmallVector new_wires; + for (size_t i = 0; i < steppers.size(); i++) + new_wires.push_back(steppers[i]->getNewWire()); + + builder.create(fun.getLoc(), new_wires); + + auto cnot = subcircuit->getStart(); + auto latest = cnot; + for (auto arg : args) { + if (!isa(arg.getType())) + continue; + auto dop = arg.getDefiningOp(); + if (dop && latest->isBeforeInBlock(dop)) + latest = dop; + } + builder.setInsertionPointAfter(latest); + + fun.walk([&](Operation *op) { + op->removeAttr("clone"); + op->removeAttr("processed"); + }); + + auto call = builder.create(cnot->getLoc(), types, + fun.getSymNameAttr(), args); + for (size_t i = 0; i < steppers.size(); i++) + steppers[i]->getOldWire().replaceAllUsesWith(call.getResult(i)); + + for (auto user : call->getUsers()) + shiftAfter(call, user); + + for (auto stepper : steppers) + delete stepper; + } + +public: + void runOnOperation() override { + auto module = getOperation(); + size_t i = 0; + SetVector subcircuits; + for (auto &op : module) { + if (auto func = dyn_cast(op)) { + func.walk([&](quake::XOp op) { + if (!::isControlledOp(op) || ::processed(op)) + return; + + auto *subcircuit = new Subcircuit(op); + moveToFunc(subcircuit, i++); + // Add the subcircuit to erase from the function after we + // finish walking it, as we don't want to erase ops from a + // function we are currently walking + subcircuits.insert(subcircuit); + }); + } + } + + for (auto *subcircuit : subcircuits) { + for (auto op : subcircuit->getOps()) { + op->dropAllUses(); + op->erase(); + } + delete subcircuit; + } + } +}; +} // namespace + +static void createPhasePolynomialOptPipeline(OpPassManager &pm) { + pm.addNestedPass(createCanonicalizerPass()); + pm.addNestedPass(createCSEPass()); + // opt::LoopUnrollOptions luo; + // luo.threshold = 2048; + // pm.addNestedPass(opt::createLoopUnroll(luo)); + // pm.addNestedPass(createCanonicalizerPass()); + // pm.addNestedPass(createCSEPass()); + pm.addNestedPass(cudaq::opt::createFactorQuantumAllocations()); + pm.addNestedPass(cudaq::opt::createMemToReg()); + pm.addNestedPass(createCanonicalizerPass()); + pm.addNestedPass(createCSEPass()); + pm.addPass(cudaq::opt::createPhasePolynomialPreprocess()); + pm.addNestedPass( + cudaq::opt::createPhasePolynomialRotationMerging()); + pm.addNestedPass(cudaq::opt::createQuakeSimplify()); + pm.addNestedPass(createCanonicalizerPass()); + pm.addNestedPass(createCSEPass()); + cudaq::opt::addAggressiveEarlyInlining(pm); + pm.addNestedPass(cudaq::opt::createRegToMem()); + pm.addNestedPass(createCanonicalizerPass()); + pm.addNestedPass(createCSEPass()); + pm.addNestedPass(cudaq::opt::createCombineQuantumAllocations()); + pm.addNestedPass(createCanonicalizerPass()); + pm.addNestedPass(createCSEPass()); +} + +void cudaq::opt::registerPhasePolynomialOptimizationPipeline() { + PassPipelineRegistration<>( + "phase-polynomial-opt-pipeline", + "Apply phase polynomial based rotation merging.", + [](OpPassManager &pm) { createPhasePolynomialOptPipeline(pm); }); +} diff --git a/lib/Optimizer/Transforms/PhasePolynomialRotationMerging.cpp b/lib/Optimizer/Transforms/PhasePolynomialRotationMerging.cpp new file mode 100644 index 00000000000..253c705b9cc --- /dev/null +++ b/lib/Optimizer/Transforms/PhasePolynomialRotationMerging.cpp @@ -0,0 +1,339 @@ +/******************************************************************************* + * Copyright (c) 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "PassDetails.h" +#include "Subcircuit.h" +#include "cudaq/Optimizer/Builder/Factory.h" +#include "cudaq/Optimizer/Dialect/CC/CCOps.h" +#include "cudaq/Optimizer/Dialect/Quake/QuakeOps.h" +#include "cudaq/Optimizer/Transforms/Passes.h" +#include "mlir/Transforms/Passes.h" + +namespace cudaq::opt { +#define GEN_PASS_DEF_PHASEPOLYNOMIALROTATIONMERGING +#include "cudaq/Optimizer/Transforms/Passes.h.inc" +} // namespace cudaq::opt + +#define DEBUG_TYPE "phase-polynomial-rotation-merging" + +using namespace mlir; + +namespace { +class PhasePolynomialRotationMergingPass + : public cudaq::opt::impl::PhasePolynomialRotationMergingBase< + PhasePolynomialRotationMergingPass> { + using PhasePolynomialRotationMergingBase::PhasePolynomialRotationMergingBase; + + struct PhaseVariable { + public: + size_t idx; + // TODO: do we really need the initial_wire here? + // I think it's just useful for debugging + Value initial_wire; + PhaseVariable(size_t index, Value wire) : idx(index), initial_wire(wire) {} + + bool operator==(PhaseVariable other) { return idx == other.idx; } + }; + + class Phase { + SetVector vars; + bool isInverted; + + public: + Phase() : isInverted(false) {} + + Phase(PhaseVariable *var) : isInverted(false) { vars.insert(var); } + + bool operator==(Phase other) { + for (auto var : vars) + if (!other.vars.contains(var)) + return false; + for (auto var : other.vars) + if (!vars.contains(var)) + return false; + return isInverted == other.isInverted; + } + + static Phase combine(Phase &p1, Phase &p2) { + Phase new_phase = Phase(); + for (auto var : p1.vars) + new_phase.vars.insert(var); + for (auto var : p2.vars) + if (new_phase.vars.contains(var)) + new_phase.vars.remove(var); + else + new_phase.vars.insert(var); + new_phase.isInverted = (p1.isInverted != p2.isInverted); + return new_phase; + } + + static Phase invert(Phase &p1) { + auto new_phase = Phase(); + for (auto var : p1.vars) + new_phase.vars.insert(var); + new_phase.isInverted = !p1.isInverted; + return new_phase; + } + + void dump() { + llvm::outs() << "Phase: "; + if (isInverted) + llvm::outs() << "!"; + llvm::outs() << "{"; + auto first = true; + for (auto var : vars) { + if (!first) + llvm::outs() << " ^ "; + llvm::outs() << var->idx; + first = false; + } + llvm::outs() << "}\n"; + } + + std::optional getIntRepresentation() { + int64_t sum = 0; + for (auto var : vars) { + if (var->idx > sizeof(int64_t) - 1) + return std::nullopt; + sum += 1 << var->idx; + } + } + }; + + class PhaseStorage { + SmallVector phases; + SmallVector rotations; + + void combineRotations(size_t prev_idx, quake::RzOp rzop) { + auto old_rzop = rotations[prev_idx]; + auto builder = OpBuilder(old_rzop); + auto rot_arg1 = old_rzop.getOperand(0); + auto rot_arg2 = rzop.getOperand(0); + auto new_rot_arg = + builder.create(old_rzop.getLoc(), rot_arg1, rot_arg2); + old_rzop->setOperand(0, new_rot_arg.getResult()); + rzop.getResult(0).replaceAllUsesWith(rzop.getOperand(1)); + rzop.erase(); + } + + public: + /// @brief registers a new rotation op for the given phase + /// @returns true if the rotation was combined, false otherwise + bool addOrCombineRotationForPhase(quake::RzOp op, Phase phase) { + for (size_t i = 0; i < phases.size(); i++) + if (phases[i] == phase) { + combineRotations(i, op); + return true; + } + + phases.push_back(phase); + rotations.push_back(op); + return false; + } + }; + + class PhaseStepper { + Value wire; + Subcircuit *subcircuit; + PhaseStorage *store; + Phase current_phase; + + public: + class StepperContainer { + SmallVector steppers; + PhaseStorage *store; + SmallVector vars; + + PhaseStepper *getStepperForValue(Value v) { + for (auto *stepper : steppers) + if (stepper->wire == v) + return stepper; + return nullptr; + } + + public: + // Caller is responsible for cleaning up circuit + StepperContainer(Subcircuit *circuit) { + store = new PhaseStorage(); + size_t i = 0; + for (auto wire : circuit->getInitialWires()) { + auto *new_var = new PhaseVariable(i++, wire); + // StepperContainer is responsible for cleaning up PhaseSteppers + steppers.push_back(new PhaseStepper(circuit, store, wire, new_var)); + // StepperContainer is responsible for cleaning up PhaseVariables + vars.push_back(new_var); + } + } + + ~StepperContainer() { + delete store; + for (auto stepper : steppers) + delete stepper; + for (auto var : vars) + delete var; + } + + static bool isPhaseInvariant(Block *b) { + llvm::outs() << "Inspecting "; + b->dump(); + + auto subcircuit = Subcircuit::constructFromBlock(b); + + if (!subcircuit) + return false; + + llvm::outs() << "Valid subcircuit!\n"; + + auto stepper = StepperContainer(subcircuit); + + while (!stepper.isStopped()) + stepper.stepAll(); + + for (size_t i = 0; i < stepper.steppers.size(); i++) + if (stepper.steppers[i]->current_phase != stepper.vars[i]) + return false; + + return true; + } + + bool isStopped() { + for (auto *stepper : steppers) + if (!stepper->isStopped()) + return false; + return true; + } + + void stepAll() { + if (isStopped()) + return; + for (auto *stepper : steppers) + stepper->step(this); + } + + std::optional maybeGetControlPhase(quake::OperatorInterface opi) { + assert(isControlledOp(opi.getOperation())); + auto control = opi.getControls().front(); + auto *stepper = getStepperForValue(control); + if (stepper) + return stepper->current_phase; + return std::nullopt; + } + + /// @brief handles a swap between two wires, swapping their phases + /// @returns `true` if the swap has been handled and stepping can + /// continue, `false` otherwise + bool maybeHandleSwap(quake::SwapOp swap) { + auto wire0 = swap.getTarget(0); + auto wire1 = swap.getTarget(1); + if (wireVisited(wire0) || wireVisited(wire1)) + return true; + + auto stepper0 = getStepperForValue(wire0); + auto stepper1 = getStepperForValue(wire1); + if (!stepper0 || !stepper1) + return false; + + auto tmp = stepper0->current_phase; + stepper0->current_phase = stepper1->current_phase; + stepper1->current_phase = tmp; + return true; + } + + bool wireVisited(Value wire) { + auto next_result = getNextResult(wire); + // Wait until target wire stepper steps to ensure + // control phase is available + return !!getStepperForValue(next_result); + } + }; + + PhaseStepper(Subcircuit *circuit, PhaseStorage *store, Value initial, + PhaseVariable *var) { + subcircuit = circuit; + this->store = store; + wire = initial; + current_phase = Phase(var); + } + + bool isStopped() { + Operation *op = *wire.getUsers().begin(); + assert(op); + // Have to have explicit check for termination point + // because rotation merging may have removed old termination + // point + return isTerminationPoint(op); + } + + void step(StepperContainer *container) { + if (isStopped()) + return; + assert(wire.hasOneUse()); + + Operation *op = *wire.getUsers().begin(); + assert(op); + auto opi = dyn_cast(op); + assert(opi); + + if (isControlledOp(op)) { + // Controlled not, and we are the target, so update phase + if (opi.getTarget(0) == wire) { + auto phase_opt = container->maybeGetControlPhase(opi); + // Wait until we have the phase for the other wire + if (!phase_opt.has_value()) + return; + current_phase = Phase::combine(current_phase, phase_opt.value()); + } else { + // Wait until the target has visited the operation so it can + // access our phase (the control phase) + if (!container->wireVisited(opi.getTarget(0))) + return; + } + } else if (isa(op) && opi.getControls().size() == 0) { + // Simple not, invert phase + // AXIS-SPECIFIC: Would want to handle y and z gates here too + current_phase = Phase::invert(current_phase); + } else if (auto rzop = dyn_cast(op)) { + if (store->addOrCombineRotationForPhase(rzop, current_phase)) + return; + } else if (auto swap = dyn_cast(op)) { + if (!container->maybeHandleSwap(swap)) + return; + } + + wire = getNextResult(wire); + } + }; + +public: + void runOnOperation() override { + auto func = getOperation(); + + if (!func.getOperation()->hasAttr("subcircuit")) + return; + + auto subcircuit = Subcircuit::constructFromFunc(func); + if (!subcircuit) + return; + auto container = PhaseStepper::StepperContainer(subcircuit); + + while (!container.isStopped()) + container.stepAll(); + delete subcircuit; + + // func.walk([&](Operation *op){ + // if (auto loop = dyn_cast(op)) + // if + // (PhaseStepper::StepperContainer::isPhaseInvariant(&loop.getLoopBody().front())) + // { + // llvm::outs() << "Phase invariant!: "; + // loop.dump(); + // } + // }); + } +}; +} // namespace diff --git a/lib/Optimizer/Transforms/Subcircuit.cpp b/lib/Optimizer/Transforms/Subcircuit.cpp new file mode 100644 index 00000000000..3248a6421e3 --- /dev/null +++ b/lib/Optimizer/Transforms/Subcircuit.cpp @@ -0,0 +1,182 @@ +/******************************************************************************* + * Copyright (c) 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "Subcircuit.h" +#include "cudaq/Optimizer/Dialect/CC/CCOps.h" +#include "cudaq/Optimizer/Dialect/Quake/QuakeOps.h" + +using namespace mlir; + +#define RAW(X) quake::X +#define RAW_MEASURE_OPS MEASURE_OPS(RAW) +#define RAW_GATE_OPS GATE_OPS(RAW) +#define RAW_QUANTUM_OPS QUANTUM_OPS(RAW) +// AXIS-SPECIFIC: Defines which operations break a circuit into subcircuits +#define CIRCUIT_BREAKERS(MACRO) \ + MACRO(YOp), MACRO(ZOp), MACRO(HOp), MACRO(R1Op), MACRO(RxOp), \ + MACRO(PhasedRxOp), MACRO(RyOp), MACRO(U2Op), MACRO(U3Op) +#define RAW_CIRCUIT_BREAKERS CIRCUIT_BREAKERS(RAW) + +unsigned calculateSkip(Operation *op) { + auto i = 0; + for (auto type : op->getOperandTypes()) { + if (isa(type)) + return i; + i++; + } + + return i; +} + +Value getNextOperand(Value v) { + auto result = dyn_cast(v); + auto op = result.getDefiningOp(); + auto skip = calculateSkip(op); + auto operandIDX = result.getResultNumber() + skip; + return op->getOperand(operandIDX); +} + +OpResult getNextResult(Value v) { + assert(v.hasOneUse()); + auto correspondingOperand = v.getUses().begin(); + auto op = correspondingOperand.getUser(); + auto skip = calculateSkip(op); + auto resultIDX = correspondingOperand.getOperand()->getOperandNumber() - skip; + return op->getResult(resultIDX); +} + +bool processed(Operation *op) { return op->hasAttr("processed"); } + +void markProcessed(Operation *op) { + op->setAttr("processed", OpBuilder(op).getUnitAttr()); +} + +// AXIS-SPECIFIC: could allow controlled y and z here +bool isControlledOp(Operation *op) { + return isa(op) && op->getNumOperands() == 2; +} + +bool isTerminationPoint(Operation *op) { + // TODO: it may be cleaner to only accept non-null input to + // ensure the null case is explicitly handled by users + if (!op) + return true; + + if (!isQuakeOperation(op)) + return true; + + if (isa(op)) + return true; + + if (isa(op)) + return true; + + auto opi = dyn_cast(op); + + if (!opi) + return true; + + // Only allow single control + if (opi.getControls().size() > 1) + return true; + return false; +} + +/// @brief Constructs a subcircuit with a phase polynomial starting from a +/// cnot +Subcircuit::Subcircuit(Operation *cnot) { + calculateInitialSubcircuit(cnot); + pruneSubcircuit(); + for (auto *op : ops) + markProcessed(op); + start = cnot; + + for (auto wire : termination_points) + if (isAfterTerminationPoint(wire)) + initial_wires.insert(wire); + else + terminal_wires.insert(wire); +} + +Subcircuit *Subcircuit::constructFromBlock(Block *b) { + // First, some validation + auto subcircuit = new Subcircuit(); + // Construct the subcircuit + for (auto &op : b->getOperations()) { + auto *opp = &op; + if (opp == b->getTerminator()) + continue; + // Ensure circuit only contains valid operations + if (isTerminationPoint(opp)) + return nullptr; + subcircuit->ops.insert(opp); + } + for (auto arg : b->getArguments()) + if (isa(arg.getType())) + subcircuit->initial_wires.insert(arg); + for (auto ret : b->getTerminator()->getOperands()) + subcircuit->terminal_wires.insert(ret); + return subcircuit; +} + +Subcircuit *Subcircuit::constructFromFunc(func::FuncOp subcircuit_func) { + // First, some validation + if (!subcircuit_func.getOperation()->hasAttr("subcircuit")) + return nullptr; + if (subcircuit_func.getBlocks().size() != 1) + return nullptr; + auto &body_block = subcircuit_func.getRegion().getBlocks().front(); + auto subcircuit = new Subcircuit(); + // Construct the subcircuit + for (auto &op : body_block) { + auto *opp = &op; + if (opp == body_block.getTerminator()) + continue; + // Ensure circuit only contains valid operations + if (isTerminationPoint(opp)) + return nullptr; + subcircuit->ops.insert(opp); + } + for (auto arg : body_block.getArguments()) + if (isa(arg.getType())) + subcircuit->initial_wires.insert(arg); + for (auto ret : body_block.getTerminator()->getOperands()) + subcircuit->terminal_wires.insert(ret); + return subcircuit; +} + +SetVector Subcircuit::getInitialWires() { return initial_wires; } + +SetVector Subcircuit::getTerminalWires() { return terminal_wires; } + +bool Subcircuit::isInSubcircuit(Operation *op) { return ops.contains(op); } + +// TODO: would be nice to make Subcircuit iterable directly +SetVector Subcircuit::getOps() { return ops; } + +/// @brief returns the number of wires in the subcircuit +size_t Subcircuit::numWires() { return getInitialWires().size(); } + +/// @brief returns the number of two-qubit operations in the subcircuit +size_t Subcircuit::numCNots() { + size_t num_cnots = 0; + for (auto *op : ops) + if (isControlledOp(op)) + num_cnots++; + return num_cnots; +} + +Operation *Subcircuit::getStart() { return start; } diff --git a/lib/Optimizer/Transforms/Subcircuit.h b/lib/Optimizer/Transforms/Subcircuit.h new file mode 100644 index 00000000000..44a07b99640 --- /dev/null +++ b/lib/Optimizer/Transforms/Subcircuit.h @@ -0,0 +1,232 @@ +#pragma once + +#include "cudaq/Optimizer/Dialect/CC/CCOps.h" +#include "cudaq/Optimizer/Dialect/Quake/QuakeOps.h" + +using namespace mlir; + +#define RAW(X) quake::X +#define RAW_MEASURE_OPS MEASURE_OPS(RAW) +#define RAW_GATE_OPS GATE_OPS(RAW) +#define RAW_QUANTUM_OPS QUANTUM_OPS(RAW) +// AXIS-SPECIFIC: Defines which operations break a circuit into subcircuits +#define CIRCUIT_BREAKERS(MACRO) \ + MACRO(YOp), MACRO(ZOp), MACRO(HOp), MACRO(R1Op), MACRO(RxOp), \ + MACRO(PhasedRxOp), MACRO(RyOp), MACRO(U2Op), MACRO(U3Op) +#define RAW_CIRCUIT_BREAKERS CIRCUIT_BREAKERS(RAW) + +unsigned calculateSkip(Operation *op); + +Value getNextOperand(Value v); + +OpResult getNextResult(Value v); + +bool processed(Operation *op); + +void markProcessed(Operation *op); + +// AXIS-SPECIFIC: could allow controlled y and z here +bool isControlledOp(Operation *op); + +bool isTerminationPoint(Operation *op); + +class Subcircuit { +protected: + SetVector ops; + SetVector initial_wires; + SetVector terminal_wires; + Operation *start; + // TODO: these three are really intermediate state + // for constructing the subcircuit, it would be nice + // to turn them into shared arguments instead + SetVector termination_points; + SetVector anchor_points; + SetVector seen; + + bool isAfterTerminationPoint(Value wire) { + return isTerminationPoint(wire.getDefiningOp()); + } + + void addAnchorPoint(Value v) { anchor_points.insert(v); } + + void addTerminationPoint(Value v) { termination_points.insert(v); } + + void calculateSubcircuitForQubitForward(OpResult v) { + seen.insert(v); + if (!v.hasOneUse()) { + addTerminationPoint(v); + return; + } + Operation *op = v.getUses().begin().getUser(); + + if (isTerminationPoint(op)) { + addTerminationPoint(v); + return; + } + + ops.insert(op); + + auto nextResult = getNextResult(v); + + // Controlled not, figure out whether we are tracking the control + // or target, and add an anchor point to the other qubit + if (op->getResults().size() > 1) { + auto control = op->getResult(0); + auto target = op->getResult(1); + // Is this the control or target qubit? + if (nextResult == control) + // Tracking the control qubit + addAnchorPoint(target); + else + // Tracking the target qubit + addAnchorPoint(control); + } + + calculateSubcircuitForQubitForward(nextResult); + } + + void calculateSubcircuitForQubitBackward(Value v) { + seen.insert(v); + Operation *op = v.getDefiningOp(); + + if (isTerminationPoint(op)) { + addTerminationPoint(v); + return; + } + + ops.insert(op); + + auto nextOperand = getNextOperand(v); + + // Controlled not, figure out whether we are tracking the control + // or target, and add an anchor point to the other qubit + // Use getResults() as Rz has two operands but only one result + if (op->getResults().size() > 1) { + auto control = op->getOperand(0); + auto target = op->getOperand(1); + // Is this the control or target qubit? + if (nextOperand == control) + // Tracking the control qubit + addAnchorPoint(target); + else + // Tracking the target qubit + addAnchorPoint(control); + } + + calculateSubcircuitForQubitBackward(nextOperand); + } + + void calculateInitialSubcircuit(Operation *op) { + // AXIS-SPECIFIC: This could be any controlled operation + auto cnot = dyn_cast(op); + assert(cnot && cnot.getWires().size() == 2); + + auto result = cnot->getResult(0); + auto operand = cnot->getOperand(0); + ops.insert(cnot); + anchor_points.insert(cnot->getResult(1)); + calculateSubcircuitForQubitForward(result); + calculateSubcircuitForQubitBackward(operand); + + while (!anchor_points.empty()) { + auto next = anchor_points.back(); + anchor_points.pop_back(); + if (seen.contains(next)) + continue; + calculateSubcircuitForQubitForward(dyn_cast(next)); + calculateSubcircuitForQubitBackward(next); + } + } + + // Prune operations after a termination point from the subcircuit + void pruneWire(Value wire, SetVector &pruned) { + if (!wire.hasOneUse()) + return; + Operation *op = wire.getUses().begin().getUser(); + + if (pruned.contains(op)) + return; + + if (termination_points.contains(wire)) + termination_points.remove(wire); + + ops.remove(op); + pruned.insert(op); + + // TODO: According to the paper, if the op is a CNot and the wire we are + // pruning along is the target, then we do not have to prune along the + // control wire. However, this prevents placing each subcircuit in a + // separate block, so it is currently not supported auto opi = + // dyn_cast(op); assert(opi); auto controls = + // opi.getControls(); if (controls.size() > 0 && + // std::find(controls.begin(), controls.end(), wire) == controls.end()) + // { pruneSubcircuit(opi.getWires()[1]); return; + // } + + for (auto result : op->getResults()) + pruneWire(result, pruned); + // Adjust termination border + for (auto operand : op->getOperands()) + if (operand.getDefiningOp() && ops.contains(operand.getDefiningOp())) + addTerminationPoint(operand); + else if (termination_points.contains(operand) && + isAfterTerminationPoint(operand)) + termination_points.remove(operand); + } + + void pruneSubcircuit() { + // The termination boundary should be defined by the first + // termination point seen along each wire in the subcircuit + // (this means that it is important to build subcircuits + // by inspecting controlled gates in topological order) + std::vector sorted; + SetVector pruned; + for (auto wire : termination_points) + if (!isAfterTerminationPoint(wire) && wire.hasOneUse()) + sorted.push_back(wire); + + auto cmp = [](Value v1, Value v2) { + if (v1.getDefiningOp() == v2.getDefiningOp()) + return dyn_cast(v1).getResultNumber() >= + dyn_cast(v2).getResultNumber(); + return !v1.getDefiningOp()->isBeforeInBlock(v2.getDefiningOp()); + }; + + std::sort(sorted.begin(), sorted.end(), cmp); + + for (size_t i = 0; i < sorted.size(); i++) + pruneWire(sorted[i], pruned); + } + + Subcircuit() {} + +public: + /// @brief Constructs a subcircuit with a phase polynomial starting from a + /// cnot + Subcircuit(Operation *cnot); + + /// @brief Reconstructs a subcircuit from a subcircuit function + /// @returns A newly allocated subcircuit if the function defines + /// a valid subcircuit, `nullptr` otherwise. + static Subcircuit *constructFromFunc(func::FuncOp subcircuit_func); + + /// @brief Reconstructs a subcircuit from a block + static Subcircuit *constructFromBlock(Block *b); + + SetVector getInitialWires(); + + SetVector getTerminalWires(); + + bool isInSubcircuit(Operation *op); + + // TODO: would be nice to make Subcircuit iterable directly + SetVector getOps(); + + /// @brief returns the number of wires in the subcircuit + size_t numWires(); + + /// @brief returns the number of two-qubit operations in the subcircuit + size_t numCNots(); + + Operation *getStart(); +}; diff --git a/test/Quake/phase_polynomial_preprocess.qke b/test/Quake/phase_polynomial_preprocess.qke new file mode 100644 index 00000000000..e1f70fa81f2 --- /dev/null +++ b/test/Quake/phase_polynomial_preprocess.qke @@ -0,0 +1,280 @@ +// ========================================================================== // +// Copyright (c) 2025 NVIDIA Corporation & Affiliates. // +// All rights reserved. // +// // +// This source code and the accompanying materials are made available under // +// the terms of the Apache License 2.0 which accompanies this distribution. // +// ========================================================================== // + +// RUN: cudaq-opt --phase-polynomial-preprocess -split-input-file %s | FileCheck %s + +func.func @kernel1() { + %cst = arith.constant 1.000000e+00 : f64 + %0 = quake.null_wire + %1 = quake.null_wire + %2:2 = quake.x [%0] %1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %3 = quake.h %2#0 : (!quake.wire) -> !quake.wire + %4 = quake.rz (%cst) %2#1 : (f64, !quake.wire) -> !quake.wire + %5:2 = quake.x [%3] %4 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + return +} + +// CHECK-LABEL: func.func private @subcircuit1(%arg0: !quake.wire, %arg1: !quake.wire) -> (!quake.wire, !quake.wire) attributes {num_cnots = 1 : ui32, subcircuit} { +// CHECK: %0:2 = quake.x [%arg0] %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: cc.return %0#0, %0#1 : !quake.wire, !quake.wire +// CHECK: } +// CHECK-LABEL: func.func private @subcircuit0(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: f64) -> (!quake.wire, !quake.wire) attributes {num_cnots = 1 : ui32, subcircuit} { +// CHECK: %0:2 = quake.x [%arg0] %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %1 = quake.rz (%arg2) %0#1 : (f64, !quake.wire) -> !quake.wire +// CHECK: cc.return %0#0, %1 : !quake.wire, !quake.wire +// CHECK: } +// CHECK-LABEL: func.func @kernel1() { +// CHECK: %cst = arith.constant 1.000000e+00 : f64 +// CHECK: %0 = quake.null_wire +// CHECK: %1 = quake.null_wire +// CHECK: %2:2 = call @subcircuit0(%0, %1, %cst) : (!quake.wire, !quake.wire, f64) -> (!quake.wire, !quake.wire) +// CHECK: %3 = quake.h %2#0 : (!quake.wire) -> !quake.wire +// CHECK: %4:2 = call @subcircuit1(%3, %2#1) : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: return +// CHECK: } + +// ----- + +func.func @kernel2() { + %cst = arith.constant 1.000000e+00 : f64 + %0 = quake.null_wire + %1 = quake.null_wire + %2 = quake.null_wire + %3 = quake.h %0 : (!quake.wire) -> !quake.wire + %4 = quake.h %1 : (!quake.wire) -> !quake.wire + %5 = quake.h %2 : (!quake.wire) -> !quake.wire + %6 = quake.rz (%cst) %4 : (f64, !quake.wire) -> !quake.wire + %7 = quake.rz (%cst) %5 : (f64, !quake.wire) -> !quake.wire + %8:2 = quake.x [%6] %3 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %9 = quake.rz (%cst) %8#1 : (f64, !quake.wire) -> !quake.wire + %10:2 = quake.x [%8#0] %7 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %11:2 = quake.x [%9] %10#0 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %12 = quake.h %10#1 : (!quake.wire) -> !quake.wire + %13:2 = quake.x [%11#1] %12 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %14:2 = quake.x [%11#0] %13#0 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %15 = quake.rz (%cst) %14#1 : (f64, !quake.wire) -> !quake.wire + %16 = quake.h %14#0 : (!quake.wire) -> !quake.wire + %17 = quake.h %15 : (!quake.wire) -> !quake.wire + return +} + +// CHECK-LABEL: func.func private @subcircuit1(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: !quake.wire, %arg3: f64) -> (!quake.wire, !quake.wire, !quake.wire) attributes {num_cnots = 2 : ui32, subcircuit} { +// CHECK: %0:2 = quake.x [%arg0] %arg2 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %1:2 = quake.x [%arg1] %0#0 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %2 = quake.rz (%arg3) %1#1 : (f64, !quake.wire) -> !quake.wire +// CHECK: cc.return %2, %1#0, %0#1 : !quake.wire, !quake.wire, !quake.wire +// CHECK: } +// CHECK-LABEL: func.func private @subcircuit0(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: !quake.wire, %arg3: f64, %arg4: f64, %arg5: f64) -> (!quake.wire, !quake.wire, !quake.wire) attributes {num_cnots = 3 : ui32, subcircuit} { +// CHECK: %0 = quake.rz (%arg3) %arg0 : (f64, !quake.wire) -> !quake.wire +// CHECK: %1:2 = quake.x [%0] %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %2 = quake.rz (%arg4) %arg2 : (f64, !quake.wire) -> !quake.wire +// CHECK: %3 = quake.rz (%arg5) %1#1 : (f64, !quake.wire) -> !quake.wire +// CHECK: %4:2 = quake.x [%1#0] %2 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %5:2 = quake.x [%3] %4#0 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: cc.return %5#1, %5#0, %4#1 : !quake.wire, !quake.wire, !quake.wire +// CHECK: } +// CHECK-LABEL: func.func @kernel2() { +// CHECK: %cst = arith.constant 1.000000e+00 : f64 +// CHECK: %0 = quake.null_wire +// CHECK: %1 = quake.null_wire +// CHECK: %2 = quake.null_wire +// CHECK: %3 = quake.h %0 : (!quake.wire) -> !quake.wire +// CHECK: %4 = quake.h %1 : (!quake.wire) -> !quake.wire +// CHECK: %5 = quake.h %2 : (!quake.wire) -> !quake.wire +// CHECK: %6:3 = call @subcircuit0(%4, %3, %5, %cst, %cst, %cst) : (!quake.wire, !quake.wire, !quake.wire, f64, f64, f64) -> (!quake.wire, !quake.wire, !quake.wire) +// CHECK: %7 = quake.h %6#2 : (!quake.wire) -> !quake.wire +// CHECK: %8:3 = call @subcircuit1(%6#0, %6#1, %7, %cst) : (!quake.wire, !quake.wire, !quake.wire, f64) -> (!quake.wire, !quake.wire, !quake.wire) +// CHECK: %9 = quake.h %8#1 : (!quake.wire) -> !quake.wire +// CHECK: %10 = quake.h %8#0 : (!quake.wire) -> !quake.wire +// CHECK: return +// CHECK: } + +// ----- + +func.func @kernel3() { + %cst = arith.constant 1.000000e+00 : f64 + %true = arith.constant true + %0 = quake.null_wire + %1 = quake.null_wire + %2:2 = quake.x [%0] %1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %3 = quake.h %2#0 : (!quake.wire) -> !quake.wire + %4:2 = cc.if(%true) ((%arg0 = %2#1, %arg1 = %3)) -> (!quake.wire, !quake.wire) { + %5 = quake.rz (%cst) %arg0 : (f64, !quake.wire) -> !quake.wire + %6:2 = quake.x [%arg1] %5 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + cc.continue %6#0, %6#1 : !quake.wire, !quake.wire + } else { + cc.continue %arg0, %arg1 : !quake.wire, !quake.wire + } + return +} + +// CHECK-LABEL: func.func private @subcircuit1(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: f64) -> (!quake.wire, !quake.wire) attributes {num_cnots = 1 : ui32, subcircuit} { +// CHECK: %0 = quake.rz (%arg2) %arg1 : (f64, !quake.wire) -> !quake.wire +// CHECK: %1:2 = quake.x [%arg0] %0 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: cc.return %1#0, %1#1 : !quake.wire, !quake.wire +// CHECK: } +// CHECK-LABEL: func.func private @subcircuit0(%arg0: !quake.wire, %arg1: !quake.wire) -> (!quake.wire, !quake.wire) attributes {num_cnots = 1 : ui32, subcircuit} { +// CHECK: %0:2 = quake.x [%arg0] %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: cc.return %0#0, %0#1 : !quake.wire, !quake.wire +// CHECK: } +// CHECK-LABEL: func.func @kernel3() { +// CHECK: %cst = arith.constant 1.000000e+00 : f64 +// CHECK: %true = arith.constant true +// CHECK: %0 = quake.null_wire +// CHECK: %1 = quake.null_wire +// CHECK: %2:2 = call @subcircuit0(%0, %1) : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %3 = quake.h %2#0 : (!quake.wire) -> !quake.wire +// CHECK: %4:2 = cc.if(%true) ((%arg0 = %2#1, %arg1 = %3)) -> (!quake.wire, !quake.wire) { +// CHECK: %5:2 = func.call @subcircuit1(%arg1, %arg0, %cst) : (!quake.wire, !quake.wire, f64) -> (!quake.wire, !quake.wire) +// CHECK: cc.continue %5#0, %5#1 : !quake.wire, !quake.wire +// CHECK: } else { +// CHECK: cc.continue %arg0, %arg1 : !quake.wire, !quake.wire +// CHECK: } +// CHECK: return +// CHECK: } + +// ----- + +func.func @kernel4() { + %cst = arith.constant 1.000000e+00 : f64 + %0 = quake.null_wire + %1 = quake.null_wire + %2 = quake.null_wire + %3 = quake.rz (%cst) %0 : (f64, !quake.wire) -> !quake.wire + %4:3 = quake.x [%1, %2] %3 : (!quake.wire, !quake.wire, !quake.wire) -> (!quake.wire, !quake.wire, !quake.wire) + %5:3 = quake.x [%4#0, %4#1] %4#2 : (!quake.wire, !quake.wire, !quake.wire) -> (!quake.wire, !quake.wire, !quake.wire) + %6 = quake.rz (%cst) %5#2 : (f64, !quake.wire) -> !quake.wire + %7:2 = quake.x [%5#0] %5#1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + return +} + +// CHECK-LABEL: func.func private @subcircuit0(%arg0: !quake.wire, %arg1: !quake.wire) -> (!quake.wire, !quake.wire) attributes {num_cnots = 1 : ui32, subcircuit} { +// CHECK: %0:2 = quake.x [%arg0] %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: cc.return %0#0, %0#1 : !quake.wire, !quake.wire +// CHECK: } +// CHECK-LABEL: func.func @kernel4() { +// CHECK: %cst = arith.constant 1.000000e+00 : f64 +// CHECK: %0 = quake.null_wire +// CHECK: %1 = quake.null_wire +// CHECK: %2 = quake.null_wire +// CHECK: %3 = quake.rz (%cst) %0 : (f64, !quake.wire) -> !quake.wire +// CHECK: %4:3 = quake.x [%1, %2] %3 : (!quake.wire, !quake.wire, !quake.wire) -> (!quake.wire, !quake.wire, !quake.wire) +// CHECK: %5:3 = quake.x [%4#0, %4#1] %4#2 : (!quake.wire, !quake.wire, !quake.wire) -> (!quake.wire, !quake.wire, !quake.wire) +// CHECK: %6 = quake.rz (%cst) %5#2 : (f64, !quake.wire) -> !quake.wire +// CHECK: %7:2 = call @subcircuit0(%5#0, %5#1) : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: return +// CHECK: } + +// ----- + +func.func @kernel5() { + %cst = arith.constant 1.000000e+00 : f64 + %0 = quake.null_wire + %1 = quake.null_wire + %2:2 = quake.x [%0] %1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %3 = quake.ry (%cst) %2#0 : (f64, !quake.wire) -> !quake.wire + %4 = quake.rz (%cst) %2#1 : (f64, !quake.wire) -> !quake.wire + %5:2 = quake.x [%3] %4 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + return +} + +// CHECK-LABEL: func.func private @subcircuit1(%arg0: !quake.wire, %arg1: !quake.wire) -> (!quake.wire, !quake.wire) attributes {num_cnots = 1 : ui32, subcircuit} { +// CHECK: %0:2 = quake.x [%arg0] %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: cc.return %0#0, %0#1 : !quake.wire, !quake.wire +// CHECK: } +// CHECK-LABEL: func.func private @subcircuit0(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: f64) -> (!quake.wire, !quake.wire) attributes {num_cnots = 1 : ui32, subcircuit} { +// CHECK: %0:2 = quake.x [%arg0] %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %1 = quake.rz (%arg2) %0#1 : (f64, !quake.wire) -> !quake.wire +// CHECK: cc.return %0#0, %1 : !quake.wire, !quake.wire +// CHECK: } +// CHECK-LABEL: func.func @kernel5() { +// CHECK: %cst = arith.constant 1.000000e+00 : f64 +// CHECK: %0 = quake.null_wire +// CHECK: %1 = quake.null_wire +// CHECK: %2:2 = call @subcircuit0(%0, %1, %cst) : (!quake.wire, !quake.wire, f64) -> (!quake.wire, !quake.wire) +// CHECK: %3 = quake.ry (%cst) %2#0 : (f64, !quake.wire) -> !quake.wire +// CHECK: %4:2 = call @subcircuit1(%3, %2#1) : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: return +// CHECK: } + +// ----- + +// A test of a wire which should immediately terminate +// (and therefore not be included in the subcircuit) +// This is seen in that subcircuit0 has no input for %2 + +func.func @kernel6() { + %cst = arith.constant 1.000000e+00 : f64 + %0 = quake.null_wire + %1 = quake.null_wire + %2 = quake.null_wire + %3:2 = quake.x [%0] %1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %4 = quake.h %3#1 : (!quake.wire) -> !quake.wire + %5:2 = quake.x [%4] %2 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %6:2 = quake.x [%3#0] %5#1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + return +} + +// CHECK-LABEL: func.func private @subcircuit1(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: !quake.wire) -> (!quake.wire, !quake.wire, !quake.wire) attributes {num_cnots = 2 : ui32, subcircuit} { +// CHECK: %0:2 = quake.x [%arg0] %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %1:2 = quake.x [%arg2] %0#1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: cc.return %0#0, %1#1, %1#0 : !quake.wire, !quake.wire, !quake.wire +// CHECK: } +// CHECK-LABEL: func.func private @subcircuit0(%arg0: !quake.wire, %arg1: !quake.wire) -> (!quake.wire, !quake.wire) attributes {num_cnots = 1 : ui32, subcircuit} { +// CHECK: %0:2 = quake.x [%arg0] %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: cc.return %0#0, %0#1 : !quake.wire, !quake.wire +// CHECK: } +// CHECK-LABEL: func.func @kernel6() { +// CHECK: %cst = arith.constant 1.000000e+00 : f64 +// CHECK: %0 = quake.null_wire +// CHECK: %1 = quake.null_wire +// CHECK: %2 = quake.null_wire +// CHECK: %3:2 = call @subcircuit0(%0, %1) : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %4 = quake.h %3#1 : (!quake.wire) -> !quake.wire +// CHECK: %5:3 = call @subcircuit1(%4, %2, %3#0) : (!quake.wire, !quake.wire, !quake.wire) -> (!quake.wire, !quake.wire, !quake.wire) +// CHECK: return +// CHECK: } + +// ----- + +// A check that the call is correctly placed, with the second +// quake.h moved after call @subcircuit0. + +func.func @kernel7() { + %cst = arith.constant 1.000000e+00 : f64 + %0 = quake.null_wire + %1 = quake.null_wire + %2 = quake.null_wire + %3:2 = quake.x [%0] %1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %4 = quake.h %3#0 : (!quake.wire) -> !quake.wire + %5 = quake.h %2 : (!quake.wire) -> !quake.wire + %6:2 = quake.x [%5] %3#1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %7:2 = quake.x [%6#0] %4 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + return +} + +// CHECK-LABEL: func.func private @subcircuit1(%arg0: !quake.wire, %arg1: !quake.wire) -> (!quake.wire, !quake.wire) attributes {num_cnots = 1 : ui32, subcircuit} { +// CHECK: %0:2 = quake.x [%arg0] %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: cc.return %0#0, %0#1 : !quake.wire, !quake.wire +// CHECK: } +// CHECK-LABEL: func.func private @subcircuit0(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: !quake.wire) -> (!quake.wire, !quake.wire, !quake.wire) attributes {num_cnots = 2 : ui32, subcircuit} { +// CHECK: %0:2 = quake.x [%arg0] %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %1:2 = quake.x [%arg2] %0#1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: cc.return %0#0, %1#1, %1#0 : !quake.wire, !quake.wire, !quake.wire +// CHECK: } +// CHECK-LABEL: func.func @kernel7() { +// CHECK: %cst = arith.constant 1.000000e+00 : f64 +// CHECK: %0 = quake.null_wire +// CHECK: %1 = quake.null_wire +// CHECK: %2 = quake.null_wire +// CHECK: %3 = quake.h %2 : (!quake.wire) -> !quake.wire +// CHECK: %4:3 = call @subcircuit0(%0, %1, %3) : (!quake.wire, !quake.wire, !quake.wire) -> (!quake.wire, !quake.wire, !quake.wire) +// CHECK: %5 = quake.h %4#0 : (!quake.wire) -> !quake.wire +// CHECK: %6:2 = call @subcircuit1(%4#2, %5) : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: return +// CHECK: } \ No newline at end of file diff --git a/test/Quake/phase_polynomial_rotation_merging.qke b/test/Quake/phase_polynomial_rotation_merging.qke new file mode 100644 index 00000000000..f037d9b3f0b --- /dev/null +++ b/test/Quake/phase_polynomial_rotation_merging.qke @@ -0,0 +1,151 @@ +// ========================================================================== // +// Copyright (c) 2025 NVIDIA Corporation & Affiliates. // +// All rights reserved. // +// // +// This source code and the accompanying materials are made available under // +// the terms of the Apache License 2.0 which accompanies this distribution. // +// ========================================================================== // + +// RUN: cudaq-opt --phase-polynomial-rotation-merging %s | FileCheck %s + +func.func private @subcircuit0(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: f64) -> (!quake.wire, !quake.wire) attributes {num_cnots = 3 : ui32, subcircuit} { + %0 = quake.rz (%arg2) %arg1 : (f64, !quake.wire) -> !quake.wire + %1:2 = quake.x [%arg0] %0 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %2 = quake.rz (%arg2) %1#1 : (f64, !quake.wire) -> !quake.wire + %3:2 = quake.x [%1#0] %2 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %4 = quake.rz (%arg2) %3#0 : (f64, !quake.wire) -> !quake.wire + %5 = quake.rz (%arg2) %3#1 : (f64, !quake.wire) -> !quake.wire + %6:2 = quake.x [%5] %4 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + cc.return %6#1, %6#0 : !quake.wire, !quake.wire +} + +// CHECK-LABEL: func.func private @subcircuit0(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: f64) -> (!quake.wire, !quake.wire) attributes {num_cnots = 3 : ui32, subcircuit} { +// CHECK: %[[VAL_0:.*]] = arith.addf %arg2, %arg2 : f64 +// CHECK: %[[VAL_1:.*]] = quake.rz (%[[VAL_0]]) %arg1 : (f64, !quake.wire) -> !quake.wire +// CHECK: %[[VAL_2:.*]]:2 = quake.x [%arg0] %[[VAL_1]] : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %[[VAL_3:.*]] = quake.rz (%arg2) %[[VAL_2]]#1 : (f64, !quake.wire) -> !quake.wire +// CHECK: %[[VAL_4:.*]]:2 = quake.x [%[[VAL_2]]#0] %[[VAL_3]] : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %[[VAL_5:.*]] = quake.rz (%arg2) %[[VAL_4]]#0 : (f64, !quake.wire) -> !quake.wire +// CHECK: %[[VAL_6:.*]]:2 = quake.x [%[[VAL_4]]#1] %[[VAL_5]] : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: cc.return %[[VAL_6]]#1, %[[VAL_6]]#0 : !quake.wire, !quake.wire +// CHECK: } + +func.func private @subcircuit1(%arg0: !quake.wire, %arg1: f64, %arg2: f64, %arg3: f64) -> !quake.wire attributes {num_cnots = 0 : ui32, subcircuit} { + %0 = quake.rz (%arg1) %arg0 : (f64, !quake.wire) -> !quake.wire + %1 = quake.x %0 : (!quake.wire) -> !quake.wire + %2 = quake.rz (%arg2) %1 : (f64, !quake.wire) -> !quake.wire + %3 = quake.x %2 : (!quake.wire) -> !quake.wire + %4 = quake.rz (%arg3) %3 : (f64, !quake.wire) -> !quake.wire + cc.return %4 : !quake.wire +} + +// CHECK-LABEL: func.func private @subcircuit1(%arg0: !quake.wire, %arg1: f64, %arg2: f64, %arg3: f64) -> !quake.wire attributes {num_cnots = 0 : ui32, subcircuit} { +// CHECK: %[[VAL_0:.*]] = arith.addf %arg1, %arg3 : f64 +// CHECK: %[[VAL_1:.*]] = quake.rz (%[[VAL_0]]) %arg0 : (f64, !quake.wire) -> !quake.wire +// CHECK: %[[VAL_2:.*]] = quake.x %[[VAL_1]] : (!quake.wire) -> !quake.wire +// CHECK: %[[VAL_3:.*]] = quake.rz (%arg2) %[[VAL_2]] : (f64, !quake.wire) -> !quake.wire +// CHECK: %[[VAL_4:.*]] = quake.x %[[VAL_3]] : (!quake.wire) -> !quake.wire +// CHECK: cc.return %[[VAL_4]] : !quake.wire +// CHECK: } + +// Invalid subcircuit functions shouldn't be touched + +func.func private @subcircuit2(%arg0: !quake.wire, %arg1: f64, %arg2: f64, %arg3: f64) -> !quake.wire attributes {num_cnots = 0 : ui32, subcircuit} { + %0 = quake.rz (%arg1) %arg0 : (f64, !quake.wire) -> !quake.wire + %1 = quake.x %0 : (!quake.wire) -> !quake.wire + %2 = quake.ry (%arg2) %1 : (f64, !quake.wire) -> !quake.wire + %3 = quake.x %2 : (!quake.wire) -> !quake.wire + %4 = quake.rz (%arg3) %3 : (f64, !quake.wire) -> !quake.wire + cc.return %4 : !quake.wire +} + +// CHECK-LABEL: func.func private @subcircuit2(%arg0: !quake.wire, %arg1: f64, %arg2: f64, %arg3: f64) -> !quake.wire attributes {num_cnots = 0 : ui32, subcircuit} { +// CHECK: %0 = quake.rz (%arg1) %arg0 : (f64, !quake.wire) -> !quake.wire +// CHECK: %1 = quake.x %0 : (!quake.wire) -> !quake.wire +// CHECK: %2 = quake.ry (%arg2) %1 : (f64, !quake.wire) -> !quake.wire +// CHECK: %3 = quake.x %2 : (!quake.wire) -> !quake.wire +// CHECK: %4 = quake.rz (%arg3) %3 : (f64, !quake.wire) -> !quake.wire +// CHECK: cc.return %4 : !quake.wire +// CHECK: } + +func.func private @subcircuit3(%arg0: !quake.wire, %arg1: f64, %arg2: f64, %arg3: f64) -> !quake.wire attributes {num_cnots = 0 : ui32} { + %0 = quake.rz (%arg1) %arg0 : (f64, !quake.wire) -> !quake.wire + %1 = quake.x %0 : (!quake.wire) -> !quake.wire + %2 = quake.rz (%arg2) %1 : (f64, !quake.wire) -> !quake.wire + %3 = quake.x %2 : (!quake.wire) -> !quake.wire + %4 = quake.rz (%arg3) %3 : (f64, !quake.wire) -> !quake.wire + cc.return %4 : !quake.wire +} + +// CHECK-LABEL: func.func private @subcircuit3(%arg0: !quake.wire, %arg1: f64, %arg2: f64, %arg3: f64) -> !quake.wire attributes {num_cnots = 0 : ui32} { +// CHECK: %0 = quake.rz (%arg1) %arg0 : (f64, !quake.wire) -> !quake.wire +// CHECK: %1 = quake.x %0 : (!quake.wire) -> !quake.wire +// CHECK: %2 = quake.rz (%arg2) %1 : (f64, !quake.wire) -> !quake.wire +// CHECK: %3 = quake.x %2 : (!quake.wire) -> !quake.wire +// CHECK: %4 = quake.rz (%arg3) %3 : (f64, !quake.wire) -> !quake.wire +// CHECK: cc.return %4 : !quake.wire +// CHECK: } + +func.func private @subcircuit4(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: !quake.wire, %arg3: f64, %arg4: f64) -> (!quake.wire, !quake.wire, !quake.wire) attributes {num_cnots = 2 : ui32, subcircuit} { + %0 = quake.rz (%arg3) %arg2 : (f64, !quake.wire) -> !quake.wire + %1:2 = quake.x [%arg0] %0 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %2:2 = quake.swap %1#0, %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %3:2 = quake.x [%2#1] %1#1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %4 = quake.rz (%arg4) %3#1 : (f64, !quake.wire) -> !quake.wire + cc.return %2#0, %3#0, %4 : !quake.wire, !quake.wire, !quake.wire +} + +// CHECK-LABEL: func.func private @subcircuit4(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: !quake.wire, %arg3: f64, %arg4: f64) -> (!quake.wire, !quake.wire, !quake.wire) attributes {num_cnots = 2 : ui32, subcircuit} { +// CHECK: %0 = arith.addf %arg3, %arg4 : f64 +// CHECK: %1 = quake.rz (%0) %arg2 : (f64, !quake.wire) -> !quake.wire +// CHECK: %2:2 = quake.x [%arg0] %1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %3:2 = quake.swap %2#0, %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %4:2 = quake.x [%3#1] %2#1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: cc.return %3#0, %4#0, %4#1 : !quake.wire, !quake.wire, !quake.wire +// CHECK: } + +func.func private @subcircuit5(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: !quake.wire, %arg3: f64, %arg4: f64) -> (!quake.wire, !quake.wire, !quake.wire) attributes {num_cnots = 2 : ui32, subcircuit} { + %0 = quake.rz (%arg3) %arg2 : (f64, !quake.wire) -> !quake.wire + %1:2 = quake.x [%arg0] %0 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %2:2 = quake.swap %1#0, %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %3:2 = quake.x [%2#1] %1#1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %4 = quake.rz (%arg4) %3#1 : (f64, !quake.wire) -> !quake.wire + cc.return %2#0, %3#0, %4 : !quake.wire, !quake.wire, !quake.wire +} + +// CHECK-LABEL: func.func private @subcircuit5(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: !quake.wire, %arg3: f64, %arg4: f64) -> (!quake.wire, !quake.wire, !quake.wire) attributes {num_cnots = 2 : ui32, subcircuit} { +// CHECK: %0 = arith.addf %arg3, %arg4 : f64 +// CHECK: %1 = quake.rz (%0) %arg2 : (f64, !quake.wire) -> !quake.wire +// CHECK: %2:2 = quake.x [%arg0] %1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %3:2 = quake.swap %2#0, %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %4:2 = quake.x [%3#1] %2#1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: cc.return %3#0, %4#0, %4#1 : !quake.wire, !quake.wire, !quake.wire +// CHECK: } + +func.func private @subcircuit6(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: f64, %arg3: f64, %arg4: f64, %arg5: f64) -> (!quake.wire, !quake.wire) attributes {num_cnots = 3 : ui32, subcircuit} { + %0 = quake.x %arg0 : (!quake.wire) -> !quake.wire + %1:2 = quake.x [%0] %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %2 = quake.rz (%arg2) %1#1 : (f64, !quake.wire) -> !quake.wire + %3:2 = quake.x [%1#0] %2 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %4 = quake.x %3#0 : (!quake.wire) -> !quake.wire + %5 = quake.rz (%arg3) %3#1 : (f64, !quake.wire) -> !quake.wire + %6:2 = quake.x [%4] %5 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) + %7 = quake.rz (%arg4) %6#1 : (f64, !quake.wire) -> !quake.wire + %8 = quake.x %7 : (!quake.wire) -> !quake.wire + %9 = quake.rz (%arg5) %8 : (f64, !quake.wire) -> !quake.wire + cc.return %6#0, %9 : !quake.wire, !quake.wire +} + +// CHECK-LABEL: func.func private @subcircuit6(%arg0: !quake.wire, %arg1: !quake.wire, %arg2: f64, %arg3: f64, %arg4: f64, %arg5: f64) -> (!quake.wire, !quake.wire) attributes {num_cnots = 3 : ui32, subcircuit} { +// CHECK: %0 = quake.x %arg0 : (!quake.wire) -> !quake.wire +// CHECK: %1:2 = quake.x [%0] %arg1 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %2 = arith.addf %arg2, %arg5 : f64 +// CHECK: %3 = quake.rz (%2) %1#1 : (f64, !quake.wire) -> !quake.wire +// CHECK: %4:2 = quake.x [%1#0] %3 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %5 = quake.x %4#0 : (!quake.wire) -> !quake.wire +// CHECK: %6 = quake.rz (%arg3) %4#1 : (f64, !quake.wire) -> !quake.wire +// CHECK: %7:2 = quake.x [%5] %6 : (!quake.wire, !quake.wire) -> (!quake.wire, !quake.wire) +// CHECK: %8 = quake.rz (%arg4) %7#1 : (f64, !quake.wire) -> !quake.wire +// CHECK: %9 = quake.x %8 : (!quake.wire) -> !quake.wire +// CHECK: cc.return %7#0, %9 : !quake.wire, !quake.wire +// CHECK: } diff --git a/test/Transforms/PhasePolynomialRotationMerging/qpe2qubits.qke b/test/Transforms/PhasePolynomialRotationMerging/qpe2qubits.qke new file mode 100644 index 00000000000..4947adca828 --- /dev/null +++ b/test/Transforms/PhasePolynomialRotationMerging/qpe2qubits.qke @@ -0,0 +1,70 @@ +// ========================================================================== // +// Copyright (c) 2025 NVIDIA Corporation & Affiliates. // +// All rights reserved. // +// // +// This source code and the accompanying materials are made available under // +// the terms of the Apache License 2.0 which accompanies this distribution. // +// ========================================================================== // + +// ========================================================================== // +// Copyright (c) 2025 NVIDIA Corporation & Affiliates. // +// All rights reserved. // +// // +// This source code and the accompanying materials are made available under // +// the terms of the Apache License 2.0 which accompanies this distribution. // +// ========================================================================== // + +// This test is a cleaned up version of quake IR from +// docs/sphinx/applications/phase_estimation.cpp using 2 qubits. +// It uses CircuitCheck to verify that the optimization produces +// an equivalent circuit. + +// RUN: cudaq-opt --phase-polynomial-opt-pipeline %s | CircuitCheck %s + +module attributes {cc.sizeof_string = 32 : i64, llvm.data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128", llvm.triple = "x86_64-unknown-linux-gnu", quake.mangled_name_map = {__nvqpp__mlirgen__Z4mainE3$_0 = "_ZZ4mainENK3$_0clERN5cudaq5quditILm2EEE", __nvqpp__mlirgen__function_iqft._Z4iqftN5cudaq5qviewILm2EEE = "_Z4iqftN5cudaq5qviewILm2EEE", __nvqpp__mlirgen__instance_qpeZ4mainE3$_0r1PiGate._ZN3qpeclIZ4mainE3$_08r1PiGateEEviOT_OT0_ = "_ZN3qpeclIZ4mainE3$_08r1PiGateEEviOT_OT0_", __nvqpp__mlirgen__r1PiGate = "_ZN8r1PiGateclERN5cudaq5quditILm2EEE"}} { + func.func @__nvqpp__mlirgen__instance_qpeZ4mainE3$_0r1PiGate._ZN3qpeclIZ4mainE3$_08r1PiGateEEviOT_OT0_(%arg0: !cc.callable<(!quake.ref) -> ()>) attributes {"cudaq-entrypoint", "cudaq-kernel"} { + %cst = arith.constant 5.000000e-01 : f64 + %cst_0 = arith.constant -5.000000e-01 : f64 + %0 = quake.alloca !quake.veq<3> + %1 = quake.extract_ref %0[0] : (!quake.veq<3>) -> !quake.ref + %2 = quake.extract_ref %0[1] : (!quake.veq<3>) -> !quake.ref + %3 = quake.extract_ref %0[2] : (!quake.veq<3>) -> !quake.ref + quake.x %3 : (!quake.ref) -> () + quake.h %1 : (!quake.ref) -> () + quake.h %2 : (!quake.ref) -> () + quake.rz (%cst) %1 : (f64, !quake.ref) -> () + quake.x [%1] %3 : (!quake.ref, !quake.ref) -> () + quake.rz (%cst_0) %3 : (f64, !quake.ref) -> () + quake.x [%1] %3 : (!quake.ref, !quake.ref) -> () + quake.rz (%cst) %3 : (f64, !quake.ref) -> () + quake.rz (%cst) %2 : (f64, !quake.ref) -> () + quake.x [%2] %3 : (!quake.ref, !quake.ref) -> () + quake.rz (%cst_0) %3 : (f64, !quake.ref) -> () + quake.x [%2] %3 : (!quake.ref, !quake.ref) -> () + quake.rz (%cst) %3 : (f64, !quake.ref) -> () + quake.rz (%cst) %2 : (f64, !quake.ref) -> () + quake.x [%2] %3 : (!quake.ref, !quake.ref) -> () + quake.rz (%cst_0) %3 : (f64, !quake.ref) -> () + quake.x [%2] %3 : (!quake.ref, !quake.ref) -> () + quake.rz (%cst) %3 : (f64, !quake.ref) -> () + quake.x [%2] %1 : (!quake.ref, !quake.ref) -> () + quake.x [%1] %2 : (!quake.ref, !quake.ref) -> () + quake.x [%2] %1 : (!quake.ref, !quake.ref) -> () + quake.h %1 : (!quake.ref) -> () + quake.h %2 : (!quake.ref) -> () + %4 = cc.alloca !cc.array + %measOut = quake.mz %1 : (!quake.ref) -> !quake.measure + %5 = quake.discriminate %measOut : (!quake.measure) -> i1 + %6 = cc.cast %4 : (!cc.ptr>) -> !cc.ptr + %7 = cc.cast unsigned %5 : (i1) -> i8 + cc.store %7, %6 : !cc.ptr + %measOut_1 = quake.mz %2 : (!quake.ref) -> !quake.measure + %8 = quake.discriminate %measOut_1 : (!quake.measure) -> i1 + %9 = cc.compute_ptr %4[1] : (!cc.ptr>) -> !cc.ptr + %10 = cc.cast unsigned %8 : (i1) -> i8 + cc.store %10, %9 : !cc.ptr + quake.dealloc %0 : !quake.veq<3> + return + } +} +