From 530f3b82902481f535f280c3e8d2c184322bac51 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 24 Aug 2026 18:06:12 +0200 Subject: [PATCH 01/55] :poop: Generated first draft of Constant Propagation, assisted by GPT 5.4 via KiConnect --- .../Optimizations/ConstantPropagation.cpp | 359 +++++++++ .../ConstantPropagationLattice.cpp | 717 ++++++++++++++++++ .../ConstantPropagationLattice.hpp | 145 ++++ 3 files changed, 1221 insertions(+) create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp new file mode 100644 index 0000000000..173a8fcb44 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "ConstantPropagation/ConstantPropagationLattice.hpp" +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +// Adjust these includes to your actual generated QCO interface/type headers. +#include "mlir/Analysis/DataFlow/SparseAnalysis.h" +#include "mlir/Dialect/Arith/IR/Arith.h" +#include "mlir/Dialect/QCO/IR/QCOOpsTypes.h.inc" +#include "mlir/IR/PatternMatch.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/GreedyPatternRewriteDriver.h" + +using namespace mlir; + +namespace mlir::mqt::qco { + +static bool isQubitType(Type ty) { return isa(ty); } + +static bool isClassicalType(Type ty) { return ty.isIntOrIndexOrFloat(); } + +static std::optional foldWithState(Operation* op, + const HybridState& state) { + SmallVector operandAttrs; + operandAttrs.reserve(op->getNumOperands()); + for (Value operand : op->getOperands()) { + auto attr = state.getClassical(operand); + if (!attr) { + return std::nullopt; + } + operandAttrs.push_back(*attr); + } + + SmallVector foldResults; + if (succeeded(op->fold(operandAttrs, foldResults)) && + foldResults.size() == 1) { + if (auto attr = llvm::dyn_cast(foldResults.front())) { + return attr; + } + } + return std::nullopt; +} + +class HybridStateLattice : public dataflow::AbstractSparseLattice { +public: + explicit HybridStateLattice(Value anchor) + : dataflow::AbstractSparseLattice(anchor) {} + + const HybridStateSet& getValue() const { return value; } + + ChangeResult join(const HybridStateSet& rhs) { + HybridStateSet old = value; + value.join(rhs); + return old == value ? ChangeResult::NoChange : ChangeResult::Change; + } + +private: + HybridStateSet value = HybridStateSet::singletonInitial(); +}; + +class HybridConstantPropagationAnalysis + : public dataflow::SparseForwardDataFlowAnalysis { +public: + explicit HybridConstantPropagationAnalysis(DataFlowSolver& solver, + unsigned maxTrackedAmplitudes, + unsigned maxTrackedStates) + : dataflow::SparseForwardDataFlowAnalysis(solver), + maxTrackedAmplitudes(maxTrackedAmplitudes), + maxTrackedStates(maxTrackedStates) {} + + void setToEntryState(dataflow::AbstractSparseLattice* lattice) override { + auto* hybrid = llvm::cast(lattice); + propagateIfChanged(hybrid, + hybrid->join(HybridStateSet::singletonInitial())); + } + + LogicalResult + visitOperation(Operation* op, + ArrayRef operands, + ArrayRef results) override { + HybridStateSet input = gatherInputState(operands); + + if (input.isTop) { + setAllResults(results, HybridStateSet::top()); + return success(); + } + + if (auto measureOp = dyn_cast(op)) { + visitMeasureOp(measureOp, input, results); + return success(); + } + + if (auto unitary = dyn_cast(op)) { + visitUnitaryOp(op, unitary, input, results); + return success(); + } + + if (auto ctrlOp = dyn_cast(op)) { + visitCtrlOp(ctrlOp, input, results); + return success(); + } + + if (llvm::all_of(op->getResultTypes(), isClassicalType)) { + visitClassicalOp(op, input, results); + return success(); + } + + visitFallback(op, input, results); + return success(); + } + +private: + unsigned maxTrackedAmplitudes; + unsigned maxTrackedStates; + + static HybridStateLattice* asHybrid(dataflow::AbstractSparseLattice* l) { + return llvm::cast(l); + } + + static const HybridStateLattice* + asHybrid(const dataflow::AbstractSparseLattice* l) { + return llvm::cast(l); + } + + HybridStateSet + gatherInputState(ArrayRef operands) { + HybridStateSet input = HybridStateSet::singletonInitial(); + bool first = true; + for (const auto* operand : operands) { + const HybridStateSet& state = asHybrid(operand)->getValue(); + if (first) { + input = state; + first = false; + } else { + input.join(state); + } + } + return input; + } + + void setAllResults(ArrayRef results, + const HybridStateSet& state) { + for (auto* res : results) { + auto* lat = asHybrid(res); + propagateIfChanged(lat, lat->join(state)); + } + } + + void visitClassicalOp(Operation* op, const HybridStateSet& input, + ArrayRef results) { + HybridStateSet output; + output.states.clear(); + + for (const HybridState& state : input.states) { + HybridState next = state; + auto attr = foldWithState(op, state); + if (!attr) { + output.addState(std::move(next)); + continue; + } + if (!op->getResults().empty()) + next.setClassical(op->getResult(0), *attr); + output.addState(std::move(next)); + } + + output.enforceMaxStates(maxTrackedStates); + setAllResults(results, output); + } + + void visitUnitaryOp(Operation* op, qco::UnitaryOpInterface unitary, + const HybridStateSet& input, + ArrayRef results) { + HybridStateSet output; + output.states.clear(); + + SmallVector inputs(op->getOperands().begin(), + op->getOperands().end()); + SmallVector outputsV(op->getResults().begin(), + op->getResults().end()); + UnitaryMatrix matrix = unitary.getUnitaryMatrix(); + + for (const HybridState& state : input.states) { + HybridState next = state; + if (failed(next.quantumState.applyUnitary(inputs, matrix, outputsV, + maxTrackedAmplitudes))) { + for (Value out : outputsV) + next.quantumState.markTop(out); + } + output.addState(std::move(next)); + } + + output.enforceMaxStates(maxTrackedStates); + setAllResults(results, output); + } + + void visitMeasureOp(qco::MeasureOp op, const HybridStateSet& input, + ArrayRef results) { + HybridStateSet output; + output.states.clear(); + + Value inQubit = op.getOperand(); + Value outQubit = op.getResult(0); + Value outClassical = op.getResult(1); + + for (const HybridState& state : input.states) { + auto successors = + state.quantumState.measure(inQubit, outQubit, op.getContext()); + if (successors.empty()) { + HybridState next = state; + next.quantumState.markTop(inQubit); + output.addState(std::move(next)); + continue; + } + + const QuantumComponent* component = + state.quantumState.getComponent(inQubit); + double prob0 = 0.0; + double prob1 = 0.0; + if (component && !component->isTop) { + auto idx = component->indexOf(inQubit); + if (idx) { + for (const auto& it : component->amplitudes) { + double p = std::norm(it.second); + if (((it.first >> *idx) & 1ULL) == 0ULL) + prob0 += p; + else + prob1 += p; + } + } + } + + for (auto& succ : successors) { + HybridState next = state; + next.quantumState = std::move(succ.first); + next.setClassical(outClassical, succ.second); + if (isZeroAttribute(succ.second)) + next.probability *= prob0; + else if (isOneAttribute(succ.second)) + next.probability *= prob1; + output.addState(std::move(next)); + } + } + + output.enforceMaxStates(maxTrackedStates); + setAllResults(results, output); + } + + void visitCtrlOp(qco::CtrlOp op, const HybridStateSet& input, + ArrayRef results) { + // Forward target inputs conservatively. + HybridStateSet output; + output.states = input.states; + output.isTop = input.isTop; + + unsigned numResults = op->getNumResults(); + unsigned numOperands = op->getNumOperands(); + unsigned numControls = numOperands - numResults; + (void)numControls; + + for (HybridState& state : output.states) { + for (unsigned i = 0; i < numResults; ++i) { + Value in = op->getOperand(numOperands - numResults + i); + Value out = op->getResult(i); + state.quantumState.forwardQubit(in, out); + } + } + + output.enforceMaxStates(maxTrackedStates); + setAllResults(results, output); + } + + void visitFallback(Operation* op, const HybridStateSet& input, + ArrayRef results) { + HybridStateSet output = input; + for (HybridState& state : output.states) { + for (Value res : op->getResults()) { + if (isQubitType(res.getType())) + state.quantumState.initializeQubit(res), + state.quantumState.markTop(res); + } + } + output.enforceMaxStates(maxTrackedStates); + setAllResults(results, output); + } +}; + +struct RemoveAlwaysZeroCtrlPattern : public OpRewritePattern { + RemoveAlwaysZeroCtrlPattern(MLIRContext* ctx, DataFlowSolver& solver) + : OpRewritePattern(ctx), solver(solver) {} + + LogicalResult matchAndRewrite(qco::CtrlOp op, + PatternRewriter& rewriter) const override { + for (Value ctrl : op.getConditions()) { + auto* state = solver.lookupState(ctrl); + if (!state) + return failure(); + if (!state->getValue().isAlwaysZero(ctrl)) + continue; + + unsigned numResults = op->getNumResults(); + unsigned numOperands = op->getNumOperands(); + if (numOperands < numResults) + return failure(); + + SmallVector replacements; + for (unsigned i = 0; i < numResults; ++i) + replacements.push_back(op->getOperand(numOperands - numResults + i)); + + rewriter.replaceOp(op, replacements); + return success(); + } + return failure(); + } + +private: + DataFlowSolver& solver; +}; + +struct ConstantPropagationPass + : public mlir::mqt::impl::ConstantPropagationPassBase< + ConstantPropagationPass> { + void runOnOperation() override { + ModuleOp module = getOperation(); + + DataFlowSolver solver; + solver.load(); + solver.load(maxTrackedAmplitudes, + maxTrackedStates); + + if (failed(solver.initializeAndRun(module))) { + signalPassFailure(); + return; + } + + RewritePatternSet patterns(&getContext()); + patterns.add(&getContext(), solver); + + if (failed(applyPatternsAndFoldGreedily(module, std::move(patterns)))) + signalPassFailure(); + } +}; + +} // namespace mlir::mqt::qco + +std::unique_ptr mlir::mqt::createConstantPropagationPass() { + return std::make_unique(); +} + +void mlir::mqt::registerConstantPropagationPass() { + PassRegistration(); +} \ No newline at end of file diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp new file mode 100644 index 0000000000..be33013a48 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp @@ -0,0 +1,717 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "ConstantPropagationLattice.hpp" + +#include "llvm/ADT/STLExtras.h" +#include "mlir/Dialect/Arith/IR/Arith.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/Support/LogicalResult.h" + +#include +#include + +using namespace mlir; +using namespace mlir::mqt::qco; + +static uint64_t clearBit(uint64_t value, unsigned pos) { + const uint64_t lowMask = (pos == 0) ? 0 : ((uint64_t{1} << pos) - 1); + uint64_t low = value & lowMask; + uint64_t high = value >> (pos + 1); + return low | (high << pos); +} + +static uint64_t insertBit(uint64_t value, unsigned pos, bool bit) { + const uint64_t lowMask = (pos == 0) ? 0 : ((uint64_t{1} << pos) - 1); + uint64_t low = value & lowMask; + uint64_t high = value >> pos; + return low | (uint64_t(bit) << pos) | (high << (pos + 1)); +} + +static bool sameAttribute(Attribute a, Attribute b) { return a == b; } + +bool mlir::mqt::isZeroAttribute(Attribute attr) { + if (!attr) + return false; + if (auto intAttr = dyn_cast(attr)) + return intAttr.getValue().isZero(); + if (auto floatAttr = dyn_cast(attr)) + return floatAttr.getValue().isZero(); + if (auto boolAttr = dyn_cast(attr)) + return !boolAttr.getValue(); + return false; +} + +bool mlir::mqt::isOneAttribute(Attribute attr) { + if (!attr) + return false; + if (auto intAttr = dyn_cast(attr)) + return intAttr.getValue().isOne(); + if (auto floatAttr = dyn_cast(attr)) + return floatAttr.getValue().isExactlyValue(1.0); + if (auto boolAttr = dyn_cast(attr)) + return boolAttr.getValue(); + return false; +} + +//===----------------------------------------------------------------------===// +// QuantumComponent +//===----------------------------------------------------------------------===// + +QuantumComponent QuantumComponent::singletonZero(Value qubit) { + QuantumComponent c; + c.qubits.push_back(qubit); + c.amplitudes[0] = Complex(1.0, 0.0); + return c; +} + +QuantumComponent QuantumComponent::top(ArrayRef qs) { + QuantumComponent c; + c.isTop = true; + c.qubits.append(qs.begin(), qs.end()); + return c; +} + +bool QuantumComponent::operator==(const QuantumComponent& other) const { + if (isTop != other.isTop) + return false; + if (qubits.size() != other.qubits.size()) + return false; + for (auto [a, b] : llvm::zip(qubits, other.qubits)) { + if (a != b) + return false; + } + if (isTop) + return true; + if (amplitudes.size() != other.amplitudes.size()) + return false; + for (const auto& it : amplitudes) { + auto found = other.amplitudes.find(it.first); + if (found == other.amplitudes.end()) + return false; + if (found->second != it.second) + return false; + } + return true; +} + +bool QuantumComponent::contains(Value v) const { + return llvm::is_contained(qubits, v); +} + +std::optional QuantumComponent::indexOf(Value v) const { + for (auto [idx, q] : llvm::enumerate(qubits)) { + if (q == v) + return idx; + } + return std::nullopt; +} + +bool QuantumComponent::isAlwaysZero(Value q) const { + if (isTop) + return false; + auto idx = indexOf(q); + if (!idx) + return false; + for (const auto& it : amplitudes) { + if (((it.first >> *idx) & 1ULL) != 0ULL) + return false; + } + return true; +} + +bool QuantumComponent::isAlwaysOne(Value q) const { + if (isTop) + return false; + auto idx = indexOf(q); + if (!idx) + return false; + for (const auto& it : amplitudes) { + if (((it.first >> *idx) & 1ULL) == 0ULL) + return false; + } + return true; +} + +void QuantumComponent::markTop() { + isTop = true; + amplitudes.clear(); +} + +bool QuantumComponent::enforceMaxAmplitudes(unsigned maxTrackedAmplitudes) { + if (isTop) + return true; + if (amplitudes.size() > maxTrackedAmplitudes) { + markTop(); + return false; + } + return true; +} + +//===----------------------------------------------------------------------===// +// QuantumState +//===----------------------------------------------------------------------===// + +bool QuantumState::operator==(const QuantumState& other) const { + if (qubitToComponent.size() != other.qubitToComponent.size()) + return false; + if (components.size() != other.components.size()) + return false; + + for (const auto& it : qubitToComponent) { + auto found = other.qubitToComponent.find(it.first); + if (found == other.qubitToComponent.end()) + return false; + + auto compA = components.find(it.second); + auto compB = other.components.find(found->second); + if (compA == components.end() || compB == other.components.end()) + return false; + if (!(compA->second == compB->second)) + return false; + } + return true; +} + +void QuantumState::initializeQubit(Value q) { + if (qubitToComponent.count(q)) + return; + assignFreshComponent(q, QuantumComponent::singletonZero(q)); +} + +void QuantumState::assignFreshComponent(Value q, QuantumComponent component) { + unsigned id = nextComponentId++; + qubitToComponent[q] = id; + components[id] = std::move(component); +} + +void QuantumState::forwardQubit(Value from, Value to) { + auto id = getComponentId(from); + if (!id) + return; + auto& component = components[*id]; + for (Value& q : component.qubits) { + if (q == from) { + q = to; + break; + } + } + qubitToComponent.erase(from); + qubitToComponent[to] = *id; +} + +std::optional QuantumState::getComponentId(Value q) const { + auto it = qubitToComponent.find(q); + if (it == qubitToComponent.end()) + return std::nullopt; + return it->second; +} + +QuantumComponent* QuantumState::getComponent(Value q) { + auto id = getComponentId(q); + if (!id) + return nullptr; + auto it = components.find(*id); + if (it == components.end()) + return nullptr; + return &it->second; +} + +const QuantumComponent* QuantumState::getComponent(Value q) const { + auto id = getComponentId(q); + if (!id) + return nullptr; + auto it = components.find(*id); + if (it == components.end()) + return nullptr; + return &it->second; +} + +void QuantumState::markTop(Value q) { + if (auto* component = getComponent(q)) + component->markTop(); +} + +bool QuantumState::isAlwaysZero(Value q) const { + if (const auto* component = getComponent(q)) + return component->isAlwaysZero(q); + return false; +} + +bool QuantumState::isAlwaysOne(Value q) const { + if (const auto* component = getComponent(q)) + return component->isAlwaysOne(q); + return false; +} + +static QuantumComponent tensorProduct(const QuantumComponent& a, + const QuantumComponent& b) { + QuantumComponent result; + result.qubits.append(a.qubits.begin(), a.qubits.end()); + result.qubits.append(b.qubits.begin(), b.qubits.end()); + + if (a.isTop || b.isTop) { + result.isTop = true; + return result; + } + + unsigned widthB = b.qubits.size(); + for (const auto& itA : a.amplitudes) { + for (const auto& itB : b.amplitudes) { + uint64_t basis = itA.first | (itB.first << a.qubits.size()); + result.amplitudes[basis] += itA.second * itB.second; + } + } + return result; +} + +LogicalResult QuantumState::mergeComponents(Value a, Value b, + unsigned maxTrackedAmplitudes) { + auto idA = getComponentId(a); + auto idB = getComponentId(b); + if (!idA || !idB) + return failure(); + if (*idA == *idB) + return success(); + + QuantumComponent merged = tensorProduct(components[*idA], components[*idB]); + merged.enforceMaxAmplitudes(maxTrackedAmplitudes); + + unsigned newId = nextComponentId++; + components[newId] = std::move(merged); + + for (Value q : components[*idA].qubits) + qubitToComponent[q] = newId; + for (Value q : components[*idB].qubits) + qubitToComponent[q] = newId; + + components.erase(*idA); + components.erase(*idB); + return success(); +} + +static QuantumComponent applyMatrix1Q(const QuantumComponent& component, + Value input, Value output, + const Matrix2x2& matrix, + unsigned maxTrackedAmplitudes) { + QuantumComponent out = component; + if (out.isTop) { + for (Value& q : out.qubits) { + if (q == input) { + q = output; + break; + } + } + return out; + } + + auto idxOpt = out.indexOf(input); + if (!idxOpt) { + out.markTop(); + return out; + } + unsigned idx = *idxOpt; + + llvm::DenseMap result; + llvm::DenseMap inputAmps = out.amplitudes; + + // Group amplitudes by all bits except target bit. + llvm::DenseMap> grouped; + for (const auto& it : inputAmps) { + uint64_t reduced = clearBit(it.first, idx); + bool bit = ((it.first >> idx) & 1ULL) != 0ULL; + grouped[reduced][bit ? 1 : 0] += it.second; + } + + for (const auto& it : grouped) { + Complex in0 = it.second[0]; + Complex in1 = it.second[1]; + Complex out0 = matrix[0][0] * in0 + matrix[0][1] * in1; + Complex out1 = matrix[1][0] * in0 + matrix[1][1] * in1; + if (out0 != Complex(0.0, 0.0)) + result[insertBit(it.first, idx, false)] += out0; + if (out1 != Complex(0.0, 0.0)) + result[insertBit(it.first, idx, true)] += out1; + } + + out.amplitudes = std::move(result); + for (Value& q : out.qubits) { + if (q == input) { + q = output; + break; + } + } + out.enforceMaxAmplitudes(maxTrackedAmplitudes); + return out; +} + +static QuantumComponent applyMatrix2Q(const QuantumComponent& component, + Value input0, Value input1, Value output0, + Value output1, const Matrix4x4& matrix, + unsigned maxTrackedAmplitudes) { + QuantumComponent out = component; + if (out.isTop) { + for (Value& q : out.qubits) { + if (q == input0) + q = output0; + else if (q == input1) + q = output1; + } + return out; + } + + auto idx0Opt = out.indexOf(input0); + auto idx1Opt = out.indexOf(input1); + if (!idx0Opt || !idx1Opt || *idx0Opt == *idx1Opt) { + out.markTop(); + return out; + } + unsigned idx0 = *idx0Opt; + unsigned idx1 = *idx1Opt; + if (idx0 > idx1) + std::swap(idx0, idx1); + + llvm::DenseMap> grouped; + for (const auto& it : out.amplitudes) { + bool b0 = ((it.first >> idx0) & 1ULL) != 0ULL; + bool b1 = ((it.first >> idx1) & 1ULL) != 0ULL; + unsigned local = unsigned(b0) | (unsigned(b1) << 1u); + uint64_t reduced = clearBit(clearBit(it.first, idx1), idx0); + grouped[reduced][local] += it.second; + } + + llvm::DenseMap result; + for (const auto& it : grouped) { + std::array outVec{}; + for (unsigned row = 0; row < 4; ++row) { + Complex sum(0.0, 0.0); + for (unsigned col = 0; col < 4; ++col) + sum += matrix[row][col] * it.second[col]; + outVec[row] = sum; + } + + for (unsigned row = 0; row < 4; ++row) { + if (outVec[row] == Complex(0.0, 0.0)) + continue; + bool b0 = (row & 1u) != 0u; + bool b1 = (row & 2u) != 0u; + uint64_t basis = insertBit(insertBit(it.first, idx0, b0), idx1, b1); + result[basis] += outVec[row]; + } + } + + out.amplitudes = std::move(result); + for (Value& q : out.qubits) { + if (q == input0) + q = output0; + else if (q == input1) + q = output1; + } + out.enforceMaxAmplitudes(maxTrackedAmplitudes); + return out; +} + +LogicalResult QuantumState::applyUnitary(ArrayRef inputs, + const UnitaryMatrix& matrix, + ArrayRef outputs, + unsigned maxTrackedAmplitudes) { + if (inputs.size() != outputs.size()) + return failure(); + if (inputs.empty() || inputs.size() > 2) + return failure(); + + for (Value in : inputs) { + if (!getComponentId(in)) + initializeQubit(in); + } + + if (inputs.size() == 1) { + auto id = getComponentId(inputs[0]); + if (!id) + return failure(); + + QuantumComponent component = components[*id]; + if (!std::holds_alternative(matrix)) { + component.markTop(); + } else { + component = + applyMatrix1Q(component, inputs[0], outputs[0], + std::get(matrix), maxTrackedAmplitudes); + } + + unsigned newId = nextComponentId++; + components.erase(*id); + qubitToComponent.erase(inputs[0]); + components[newId] = std::move(component); + qubitToComponent[outputs[0]] = newId; + return success(); + } + + if (failed(mergeComponents(inputs[0], inputs[1], maxTrackedAmplitudes))) + return failure(); + + auto mergedId = getComponentId(inputs[0]); + if (!mergedId) + return failure(); + + QuantumComponent component = components[*mergedId]; + if (!std::holds_alternative(matrix)) { + component.markTop(); + } else { + component = + applyMatrix2Q(component, inputs[0], inputs[1], outputs[0], outputs[1], + std::get(matrix), maxTrackedAmplitudes); + } + + components.erase(*mergedId); + qubitToComponent.erase(inputs[0]); + qubitToComponent.erase(inputs[1]); + + unsigned newId = nextComponentId++; + components[newId] = std::move(component); + qubitToComponent[outputs[0]] = newId; + qubitToComponent[outputs[1]] = newId; + return success(); +} + +SmallVector> +QuantumState::measure(Value inQubit, Value outQubit, MLIRContext* ctx) const { + SmallVector> successors; + auto compId = getComponentId(inQubit); + if (!compId) { + QuantumState unknown = *this; + auto i1 = IntegerType::get(ctx, 1); + // Unknown classical result cannot be represented as an Attribute directly + // here, so return no successors and let caller handle top/unknown. + (void)i1; + return successors; + } + + const QuantumComponent& component = components.at(*compId); + if (component.isTop) + return successors; + + auto idxOpt = component.indexOf(inQubit); + if (!idxOpt) + return successors; + unsigned idx = *idxOpt; + + double prob0 = 0.0; + double prob1 = 0.0; + for (const auto& it : component.amplitudes) { + double p = std::norm(it.second); + if (((it.first >> idx) & 1ULL) == 0ULL) + prob0 += p; + else + prob1 += p; + } + + auto makeSuccessor = [&](bool bit) { + QuantumState next = *this; + auto nextId = next.getComponentId(inQubit); + if (!nextId) + return std::pair{next, {}}; + + QuantumComponent& c = next.components[*nextId]; + llvm::DenseMap filtered; + double norm = 0.0; + for (const auto& it : c.amplitudes) { + bool curBit = (((it.first >> idx) & 1ULL) != 0ULL); + if (curBit == bit) { + filtered[it.first] = it.second; + norm += std::norm(it.second); + } + } + + if (norm == 0.0) { + c.markTop(); + } else { + double scale = 1.0 / std::sqrt(norm); + for (auto& it : filtered) + it.second *= scale; + c.amplitudes = std::move(filtered); + } + + for (Value& q : c.qubits) { + if (q == inQubit) { + q = outQubit; + break; + } + } + next.qubitToComponent.erase(inQubit); + next.qubitToComponent[outQubit] = *nextId; + + auto i1 = IntegerType::get(ctx, 1); + Attribute bitAttr = IntegerAttr::get(i1, bit ? 1 : 0); + return std::pair{std::move(next), bitAttr}; + }; + + if (prob0 > 0.0) + successors.push_back(makeSuccessor(false)); + if (prob1 > 0.0) + successors.push_back(makeSuccessor(true)); + return successors; +} + +//===----------------------------------------------------------------------===// +// HybridState +//===----------------------------------------------------------------------===// + +bool HybridState::operator==(const HybridState& other) const { + if (probability != other.probability) + return false; + if (!(quantumState == other.quantumState)) + return false; + if (classicalValues.size() != other.classicalValues.size()) + return false; + for (const auto& it : classicalValues) { + auto found = other.classicalValues.find(it.first); + if (found == other.classicalValues.end()) + return false; + if (!sameAttribute(it.second, found->second)) + return false; + } + return true; +} + +std::optional HybridState::getClassical(Value v) const { + auto it = classicalValues.find(v); + if (it == classicalValues.end()) + return std::nullopt; + return it->second; +} + +void HybridState::setClassical(Value v, Attribute attr) { + classicalValues[v] = attr; +} + +//===----------------------------------------------------------------------===// +// HybridStateSet +//===----------------------------------------------------------------------===// + +bool HybridStateSet::operator==(const HybridStateSet& other) const { + if (isTop != other.isTop) + return false; + if (isTop) + return true; + if (states.size() != other.states.size()) + return false; + for (const auto& s : states) { + if (!llvm::is_contained(other.states, s)) + return false; + } + return true; +} + +HybridStateSet HybridStateSet::top() { + HybridStateSet s; + s.isTop = true; + return s; +} + +HybridStateSet HybridStateSet::singletonInitial() { + HybridStateSet s; + s.states.push_back(HybridState{}); + return s; +} + +void HybridStateSet::addState(HybridState state) { + if (isTop) + return; + states.push_back(std::move(state)); +} + +void HybridStateSet::canonicalize() { + if (isTop) + return; + + SmallVector merged; + for (HybridState& state : states) { + bool found = false; + for (HybridState& existing : merged) { + HybridState lhs = state; + HybridState rhs = existing; + lhs.probability = 0.0; + rhs.probability = 0.0; + if (lhs == rhs) { + existing.probability += state.probability; + found = true; + break; + } + } + if (!found) + merged.push_back(std::move(state)); + } + states = std::move(merged); +} + +void HybridStateSet::join(const HybridStateSet& other) { + if (isTop || other.isTop) { + isTop = true; + states.clear(); + return; + } + states.append(other.states.begin(), other.states.end()); + canonicalize(); +} + +void HybridStateSet::enforceMaxStates(unsigned maxTrackedStates) { + if (isTop) + return; + canonicalize(); + if (states.size() > maxTrackedStates) { + isTop = true; + states.clear(); + } +} + +bool HybridStateSet::isAlwaysZero(Value v) const { + if (isTop || states.empty()) + return false; + for (const HybridState& state : states) { + auto attr = state.getClassical(v); + if (attr && isZeroAttribute(*attr)) + continue; + if (state.quantumState.isAlwaysZero(v)) + continue; + return false; + } + return true; +} + +bool HybridStateSet::isAlwaysOne(Value v) const { + if (isTop || states.empty()) + return false; + for (const HybridState& state : states) { + auto attr = state.getClassical(v); + if (attr && isOneAttribute(*attr)) + continue; + if (state.quantumState.isAlwaysOne(v)) + continue; + return false; + } + return true; +} + +std::optional HybridStateSet::getUniqueConstant(Value v) const { + if (isTop || states.empty()) + return std::nullopt; + std::optional candidate; + for (const HybridState& state : states) { + auto attr = state.getClassical(v); + if (!attr) + return std::nullopt; + if (!candidate) + candidate = attr; + else if (*candidate != *attr) + return std::nullopt; + } + return candidate; +} \ No newline at end of file diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp new file mode 100644 index 0000000000..c8f8e3d98e --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" +#include "mlir/IR/Attributes.h" +#include "mlir/IR/MLIRContext.h" +#include "mlir/IR/Value.h" + +#include +#include +#include +#include +#include + +namespace mlir::mqt::qco { + +using Complex = std::complex; + +using Matrix2x2 = std::array, 2>; +using Matrix4x4 = std::array, 4>; +using UnitaryMatrix = std::variant; + +struct QuantumState { + bool isTop = false; + unsigned int maxTrackedAmplitudes; + SmallVector qubits; + llvm::DenseMap amplitudes; + + explicit QuantumState(const unsigned int maxTrackedAmplitudes) + : maxTrackedAmplitudes(maxTrackedAmplitudes) {} + + /** + * Create a new QuantumState that is initialized to |0>. + * + * @param maxTrackedAmplitudes The maximum number of amplitudes before + * QuantumStates becomes top. + * @param qubit The qubit value that the new quantum component should own. + * @return The newly created QuantumState. + */ + static QuantumState singletonZero(unsigned int maxTrackedAmplitudes, + Value qubit); + + bool operator==(const QuantumState& other) const; + + /** + * Check if the QuantumState contains a certain value. + * + * @param v The value to be checked. + * @return True if QuantumState contains v. + */ + [[nodiscard("QuantumState::contains called but ignored.")]] + bool contains(Value v) const; + + /** + * Checks what the index of value v in the QuantumState is. + * + * @param v The value to look for. + * @return The index where v is in QuantumState. + */ + [[nodiscard("QuantumState::indexOf called but ignored.")]] + std::optional indexOf(Value v) const; + + /** + * Check if a value is always zero. + * + * @param q The value to check for. + * @return True if the value is always zero. + */ + [[nodiscard("QuantumState::isAlwaysZero called but ignored.")]] + bool isAlwaysZero(Value q) const; + + /** + * Check if a value is always one. + * + * @param q The value to check for. + * @return True if the value is always one. + */ + [[nodiscard("QuantumState::isAlwaysOne called but ignored.")]] + bool isAlwaysOne(Value q) const; + + /** + * Put QuantumState to top. + */ + void markTop(); + + void forwardQubit(Value from, Value to); + + [[nodiscard("QuantumState::mergeQuantumStates called but ignored.")]] + QuantumState mergeQuantumStates(QuantumState that); + + LogicalResult applyUnitary(ArrayRef inputs, + const UnitaryMatrix& matrix, + ArrayRef outputs); + + /// Returns successor states paired with the measured classical result. + std::unordered_map> + measure(Value inQubit, Value outQubit, MLIRContext* ctx) const; +}; + +struct HybridState { + llvm::DenseMap classicalValues; + QuantumState quantumState; + double probability = 1.0; + + bool operator==(const HybridState& other) const; + + std::optional getClassical(Value v) const; + void setClassical(Value v, Attribute attr); +}; + +struct HybridStateSet { + bool isTop = false; + llvm::SmallVector states; + + bool operator==(const HybridStateSet& other) const; + + static HybridStateSet top(); + static HybridStateSet singletonInitial(); + + void addState(HybridState state); + void canonicalize(); + void join(const HybridStateSet& other); + + void enforceMaxStates(unsigned maxTrackedStates); + + bool isAlwaysZero(Value v) const; + bool isAlwaysOne(Value v) const; + std::optional getUniqueConstant(Value v) const; +}; + +/// Utility used by the pass analysis. +bool isZeroAttribute(Attribute attr); +bool isOneAttribute(Attribute attr); + +} // namespace mlir::mqt::qco \ No newline at end of file From c3398b64570fbf5e8c141d99f53bd0fdf6748bce Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 24 Aug 2026 19:51:16 +0200 Subject: [PATCH 02/55] :construction: Improved generated code --- .../ConstantPropagationLattice.cpp | 395 +++++++----------- .../ConstantPropagationLattice.hpp | 159 ++++++- 2 files changed, 293 insertions(+), 261 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp index be33013a48..0cdbda5ce5 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp @@ -10,323 +10,232 @@ #include "ConstantPropagationLattice.hpp" -#include "llvm/ADT/STLExtras.h" -#include "mlir/Dialect/Arith/IR/Arith.h" -#include "mlir/IR/BuiltinTypes.h" -#include "mlir/Support/LogicalResult.h" +#include +#include +#include +#include #include #include using namespace mlir; -using namespace mlir::mqt::qco; +namespace mlir::mqt::qco { -static uint64_t clearBit(uint64_t value, unsigned pos) { - const uint64_t lowMask = (pos == 0) ? 0 : ((uint64_t{1} << pos) - 1); - uint64_t low = value & lowMask; - uint64_t high = value >> (pos + 1); - return low | (high << pos); +/** + * Removes the bit at a specified position in a 64-bit unsigned integer. + * The resulting value is effectively the input value with the bit at the given + * position cleared or removed, shifting higher bits down by one position. + * + * @param value The 64-bit unsigned integer from which to clear a bit. + * @param pos The zero-based position of the bit to remove. + * Must be less than 64; undefined behavior if out of bounds. + * @return A new 64-bit unsigned integer with the specified bit removed. + */ +static uint64_t clearBit(const uint64_t value, const unsigned pos) { + const uint64_t lowMask = pos == 0 ? 0 : (uint64_t{1} << pos) - 1; + const uint64_t low = value & lowMask; + const uint64_t high = value >> (pos + 1); + return low | high << pos; } -static uint64_t insertBit(uint64_t value, unsigned pos, bool bit) { - const uint64_t lowMask = (pos == 0) ? 0 : ((uint64_t{1} << pos) - 1); - uint64_t low = value & lowMask; - uint64_t high = value >> pos; - return low | (uint64_t(bit) << pos) | (high << (pos + 1)); +/** + * Inserts a bit at a specified position in a 64-bit unsigned integer. + * The resulting value includes the new bit at the given position, with + * all higher bits shifted up by one position to make room for the insertion. + * + * @param value The 64-bit unsigned integer where the bit will be inserted. + * @param pos The zero-based position at which the bit is to be inserted. + * Must be less than 64; undefined behavior if out of bounds. + * @param bit The value of the bit to be inserted (true for 1, false for 0). + * @return A new 64-bit unsigned integer with the specified bit inserted. + */ +static uint64_t insertBit(const uint64_t value, const unsigned pos, + const bool bit) { + const uint64_t lowMask = pos == 0 ? 0 : (uint64_t{1} << pos) - 1; + const uint64_t low = value & lowMask; + const uint64_t high = value >> pos; + return low | static_cast(bit) << pos | high << (pos + 1); } -static bool sameAttribute(Attribute a, Attribute b) { return a == b; } - -bool mlir::mqt::isZeroAttribute(Attribute attr) { - if (!attr) +bool isZeroAttribute(const Attribute attr) { + if (!attr) { return false; - if (auto intAttr = dyn_cast(attr)) + } + if (const auto intAttr = dyn_cast(attr)) { return intAttr.getValue().isZero(); - if (auto floatAttr = dyn_cast(attr)) + } + if (const auto floatAttr = dyn_cast(attr)) { return floatAttr.getValue().isZero(); - if (auto boolAttr = dyn_cast(attr)) + } + if (const auto boolAttr = dyn_cast(attr)) { return !boolAttr.getValue(); + } return false; } -bool mlir::mqt::isOneAttribute(Attribute attr) { - if (!attr) +bool isOneAttribute(const Attribute attr) { + if (!attr) { return false; - if (auto intAttr = dyn_cast(attr)) + } + if (const auto intAttr = dyn_cast(attr)) { return intAttr.getValue().isOne(); - if (auto floatAttr = dyn_cast(attr)) + } + if (const auto floatAttr = dyn_cast(attr)) { return floatAttr.getValue().isExactlyValue(1.0); - if (auto boolAttr = dyn_cast(attr)) + } + if (const auto boolAttr = dyn_cast(attr)) { return boolAttr.getValue(); + } return false; } //===----------------------------------------------------------------------===// -// QuantumComponent +// QuantumState //===----------------------------------------------------------------------===// -QuantumComponent QuantumComponent::singletonZero(Value qubit) { - QuantumComponent c; +QuantumState +QuantumState::singletonZero(const unsigned int maxTrackedAmplitudes, + const Value qubit) { + QuantumState c(std::min(maxTrackedAmplitudes, 64u)); c.qubits.push_back(qubit); c.amplitudes[0] = Complex(1.0, 0.0); return c; } -QuantumComponent QuantumComponent::top(ArrayRef qs) { - QuantumComponent c; - c.isTop = true; - c.qubits.append(qs.begin(), qs.end()); - return c; -} - -bool QuantumComponent::operator==(const QuantumComponent& other) const { - if (isTop != other.isTop) +bool QuantumState::operator==(const QuantumState& other) const { + if (isTop != other.isTop) { + return false; + } + if (maxTrackedAmplitudes != other.maxTrackedAmplitudes) { return false; - if (qubits.size() != other.qubits.size()) + } + if (qubits.size() != other.qubits.size()) { return false; + } for (auto [a, b] : llvm::zip(qubits, other.qubits)) { - if (a != b) + if (a != b) { return false; + } } - if (isTop) - return true; - if (amplitudes.size() != other.amplitudes.size()) + if (isTop) { + return other.isTop; + } + if (amplitudes.size() != other.amplitudes.size()) { return false; + } for (const auto& it : amplitudes) { auto found = other.amplitudes.find(it.first); - if (found == other.amplitudes.end()) + if (found == other.amplitudes.end()) { return false; - if (found->second != it.second) + } + if (found->second != it.second) { return false; + } } return true; } -bool QuantumComponent::contains(Value v) const { +bool QuantumState::contains(const Value v) const { return llvm::is_contained(qubits, v); } -std::optional QuantumComponent::indexOf(Value v) const { +std::optional QuantumState::indexOf(const Value v) const { for (auto [idx, q] : llvm::enumerate(qubits)) { - if (q == v) + if (q == v) { return idx; + } } - return std::nullopt; + return {}; } -bool QuantumComponent::isAlwaysZero(Value q) const { - if (isTop) +bool QuantumState::isAlwaysZero(const Value q) const { + if (isTop) { return false; - auto idx = indexOf(q); - if (!idx) + } + const auto idx = indexOf(q); + if (!idx) { return false; + } for (const auto& it : amplitudes) { - if (((it.first >> *idx) & 1ULL) != 0ULL) + if ((it.first >> *idx & 1ULL) != 0ULL) { return false; + } } return true; } -bool QuantumComponent::isAlwaysOne(Value q) const { - if (isTop) +bool QuantumState::isAlwaysOne(const Value q) const { + if (isTop) { return false; - auto idx = indexOf(q); - if (!idx) + } + const auto idx = indexOf(q); + if (!idx) { return false; + } for (const auto& it : amplitudes) { - if (((it.first >> *idx) & 1ULL) == 0ULL) + if ((it.first >> *idx & 1ULL) == 0ULL) { return false; + } } return true; } -void QuantumComponent::markTop() { +void QuantumState::markTop() { isTop = true; amplitudes.clear(); } -bool QuantumComponent::enforceMaxAmplitudes(unsigned maxTrackedAmplitudes) { - if (isTop) - return true; - if (amplitudes.size() > maxTrackedAmplitudes) { - markTop(); - return false; - } - return true; -} - -//===----------------------------------------------------------------------===// -// QuantumState -//===----------------------------------------------------------------------===// - -bool QuantumState::operator==(const QuantumState& other) const { - if (qubitToComponent.size() != other.qubitToComponent.size()) - return false; - if (components.size() != other.components.size()) - return false; - - for (const auto& it : qubitToComponent) { - auto found = other.qubitToComponent.find(it.first); - if (found == other.qubitToComponent.end()) - return false; - - auto compA = components.find(it.second); - auto compB = other.components.find(found->second); - if (compA == components.end() || compB == other.components.end()) - return false; - if (!(compA->second == compB->second)) - return false; - } - return true; -} - -void QuantumState::initializeQubit(Value q) { - if (qubitToComponent.count(q)) - return; - assignFreshComponent(q, QuantumComponent::singletonZero(q)); -} - -void QuantumState::assignFreshComponent(Value q, QuantumComponent component) { - unsigned id = nextComponentId++; - qubitToComponent[q] = id; - components[id] = std::move(component); -} - -void QuantumState::forwardQubit(Value from, Value to) { - auto id = getComponentId(from); +void QuantumState::forwardQubit(const Value from, const Value to) { + const auto id = indexOf(from); if (!id) return; - auto& component = components[*id]; - for (Value& q : component.qubits) { - if (q == from) { - q = to; - break; - } - } - qubitToComponent.erase(from); - qubitToComponent[to] = *id; + qubits[id.value()] = to; } -std::optional QuantumState::getComponentId(Value q) const { - auto it = qubitToComponent.find(q); - if (it == qubitToComponent.end()) - return std::nullopt; - return it->second; -} - -QuantumComponent* QuantumState::getComponent(Value q) { - auto id = getComponentId(q); - if (!id) - return nullptr; - auto it = components.find(*id); - if (it == components.end()) - return nullptr; - return &it->second; -} - -const QuantumComponent* QuantumState::getComponent(Value q) const { - auto id = getComponentId(q); - if (!id) - return nullptr; - auto it = components.find(*id); - if (it == components.end()) - return nullptr; - return &it->second; -} - -void QuantumState::markTop(Value q) { - if (auto* component = getComponent(q)) - component->markTop(); -} - -bool QuantumState::isAlwaysZero(Value q) const { - if (const auto* component = getComponent(q)) - return component->isAlwaysZero(q); - return false; -} +QuantumState QuantumState::tensorProduct(const QuantumState& that) { + QuantumState result(maxTrackedAmplitudes); + result.qubits.append(qubits.begin(), qubits.end()); + result.qubits.append(that.qubits.begin(), that.qubits.end()); -bool QuantumState::isAlwaysOne(Value q) const { - if (const auto* component = getComponent(q)) - return component->isAlwaysOne(q); - return false; -} - -static QuantumComponent tensorProduct(const QuantumComponent& a, - const QuantumComponent& b) { - QuantumComponent result; - result.qubits.append(a.qubits.begin(), a.qubits.end()); - result.qubits.append(b.qubits.begin(), b.qubits.end()); - - if (a.isTop || b.isTop) { + if (isTop || that.isTop) { result.isTop = true; return result; } - unsigned widthB = b.qubits.size(); - for (const auto& itA : a.amplitudes) { - for (const auto& itB : b.amplitudes) { - uint64_t basis = itA.first | (itB.first << a.qubits.size()); + for (const auto& itA : amplitudes) { + for (const auto& itB : that.amplitudes) { + uint64_t basis = itA.first | itB.first << qubits.size(); result.amplitudes[basis] += itA.second * itB.second; } } return result; } -LogicalResult QuantumState::mergeComponents(Value a, Value b, - unsigned maxTrackedAmplitudes) { - auto idA = getComponentId(a); - auto idB = getComponentId(b); - if (!idA || !idB) - return failure(); - if (*idA == *idB) - return success(); - - QuantumComponent merged = tensorProduct(components[*idA], components[*idB]); - merged.enforceMaxAmplitudes(maxTrackedAmplitudes); - - unsigned newId = nextComponentId++; - components[newId] = std::move(merged); - - for (Value q : components[*idA].qubits) - qubitToComponent[q] = newId; - for (Value q : components[*idB].qubits) - qubitToComponent[q] = newId; - - components.erase(*idA); - components.erase(*idB); - return success(); -} - -static QuantumComponent applyMatrix1Q(const QuantumComponent& component, - Value input, Value output, - const Matrix2x2& matrix, - unsigned maxTrackedAmplitudes) { - QuantumComponent out = component; - if (out.isTop) { - for (Value& q : out.qubits) { +void QuantumState::applyMatrix1Q(const Value input, const Value output, + const Matrix2x2& matrix) { + if (isTop) { + for (Value& q : qubits) { if (q == input) { q = output; break; } } - return out; + return; } - auto idxOpt = out.indexOf(input); + const auto idxOpt = indexOf(input); if (!idxOpt) { - out.markTop(); - return out; + return; } - unsigned idx = *idxOpt; + const unsigned idx = *idxOpt; llvm::DenseMap result; - llvm::DenseMap inputAmps = out.amplitudes; - // Group amplitudes by all bits except target bit. + // Group amplitudes by all bits except the target bit. llvm::DenseMap> grouped; - for (const auto& it : inputAmps) { + for (const auto& it : amplitudes) { uint64_t reduced = clearBit(it.first, idx); - bool bit = ((it.first >> idx) & 1ULL) != 0ULL; + const bool bit = (it.first >> idx & 1ULL) != 0ULL; grouped[reduced][bit ? 1 : 0] += it.second; } @@ -341,48 +250,43 @@ static QuantumComponent applyMatrix1Q(const QuantumComponent& component, result[insertBit(it.first, idx, true)] += out1; } - out.amplitudes = std::move(result); - for (Value& q : out.qubits) { + amplitudes = std::move(result); + for (Value& q : qubits) { if (q == input) { q = output; break; } } - out.enforceMaxAmplitudes(maxTrackedAmplitudes); - return out; } -static QuantumComponent applyMatrix2Q(const QuantumComponent& component, - Value input0, Value input1, Value output0, - Value output1, const Matrix4x4& matrix, - unsigned maxTrackedAmplitudes) { - QuantumComponent out = component; - if (out.isTop) { - for (Value& q : out.qubits) { - if (q == input0) +void QuantumState::applyMatrix2Q(const Value input0, const Value input1, + const Value output0, const Value output1, + const Matrix4x4& matrix) { + if (isTop) { + for (Value& q : qubits) { + if (q == input0) { q = output0; - else if (q == input1) + } else if (q == input1) { q = output1; + } } - return out; + return; } - auto idx0Opt = out.indexOf(input0); - auto idx1Opt = out.indexOf(input1); + const auto idx0Opt = indexOf(input0); + const auto idx1Opt = indexOf(input1); if (!idx0Opt || !idx1Opt || *idx0Opt == *idx1Opt) { - out.markTop(); - return out; + return; } - unsigned idx0 = *idx0Opt; - unsigned idx1 = *idx1Opt; - if (idx0 > idx1) - std::swap(idx0, idx1); + const unsigned idx0 = *idx0Opt; + const unsigned idx1 = *idx1Opt; llvm::DenseMap> grouped; - for (const auto& it : out.amplitudes) { - bool b0 = ((it.first >> idx0) & 1ULL) != 0ULL; - bool b1 = ((it.first >> idx1) & 1ULL) != 0ULL; - unsigned local = unsigned(b0) | (unsigned(b1) << 1u); + for (const auto& it : amplitudes) { + const bool b0 = (it.first >> idx0 & 1ULL) != 0ULL; + const bool b1 = (it.first >> idx1 & 1ULL) != 0ULL; + const unsigned local = static_cast(b0) | static_cast(b1) + << 1u; uint64_t reduced = clearBit(clearBit(it.first, idx1), idx0); grouped[reduced][local] += it.second; } @@ -437,7 +341,7 @@ LogicalResult QuantumState::applyUnitary(ArrayRef inputs, if (!id) return failure(); - QuantumComponent component = components[*id]; + QuantumState component = components[*id]; if (!std::holds_alternative(matrix)) { component.markTop(); } else { @@ -461,7 +365,7 @@ LogicalResult QuantumState::applyUnitary(ArrayRef inputs, if (!mergedId) return failure(); - QuantumComponent component = components[*mergedId]; + QuantumState component = components[*mergedId]; if (!std::holds_alternative(matrix)) { component.markTop(); } else { @@ -494,7 +398,7 @@ QuantumState::measure(Value inQubit, Value outQubit, MLIRContext* ctx) const { return successors; } - const QuantumComponent& component = components.at(*compId); + const QuantumState& component = components.at(*compId); if (component.isTop) return successors; @@ -519,7 +423,7 @@ QuantumState::measure(Value inQubit, Value outQubit, MLIRContext* ctx) const { if (!nextId) return std::pair{next, {}}; - QuantumComponent& c = next.components[*nextId]; + QuantumState& c = next.components[*nextId]; llvm::DenseMap filtered; double norm = 0.0; for (const auto& it : c.amplitudes) { @@ -714,4 +618,5 @@ std::optional HybridStateSet::getUniqueConstant(Value v) const { return std::nullopt; } return candidate; -} \ No newline at end of file +} +} // namespace mlir::mqt::qco \ No newline at end of file diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp index c8f8e3d98e..d3c6e41417 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp @@ -10,14 +10,13 @@ #pragma once -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/SmallVector.h" -#include "mlir/IR/Attributes.h" -#include "mlir/IR/MLIRContext.h" -#include "mlir/IR/Value.h" +#include +#include +#include +#include +#include #include -#include #include #include #include @@ -30,12 +29,19 @@ using Matrix2x2 = std::array, 2>; using Matrix4x4 = std::array, 4>; using UnitaryMatrix = std::variant; +/** + * This struct represents a QuantumState. It contains of the amplitudes of + * different qubit states. It is top if the number of non-zero amplitudes + * exceeds a given maximum number of amplitudes. + */ struct QuantumState { +private: bool isTop = false; unsigned int maxTrackedAmplitudes; SmallVector qubits; llvm::DenseMap amplitudes; +public: explicit QuantumState(const unsigned int maxTrackedAmplitudes) : maxTrackedAmplitudes(maxTrackedAmplitudes) {} @@ -93,49 +99,170 @@ struct QuantumState { */ void markTop(); + /** + * Changes qubit value one to another. + * + * @param from The original qubit value. + * @param to The new qubit value. + */ void forwardQubit(Value from, Value to); - [[nodiscard("QuantumState::mergeQuantumStates called but ignored.")]] - QuantumState mergeQuantumStates(QuantumState that); + /** + * Computes the tensor product of this QuantumState with another QuantumState. + * The tensor product combines the qubits and amplitudes of both states, + * producing a new QuantumState that represents the combined quantum system. + * + * @param that The QuantumState to be combined with this QuantumState. + * @return A new QuantumState representing the tensor product of the two + * states. + */ + [[nodiscard("QuantumState::tensorProduct called but ignored.")]] + QuantumState tensorProduct(const QuantumState& that); + + /** + * Applies a 2x2 unitary matrix to a single qubit in the QuantumState. + * This operation updates the quantum state's amplitude distribution + * and the tracked qubit values. + * + * @param input The qubit identifier to which the matrix is applied. + * @param output The updated qubit identifier after transformation. + * @param matrix The 2x2 unitary matrix describing the transformation + * to be applied to the specified qubit. + */ + void applyMatrix1Q(Value input, Value output, const Matrix2x2& matrix); + + /** + * Applies a 4x4 unitary matrix to a single qubit in the QuantumState. + * This operation updates the quantum state's amplitude distribution + * and the tracked qubit values. + * + * @param input0 The first qubit identifier (corresponding to the lower index + * of the matrix) to which the matrix is applied. + * @param input1 The second qubit identifier (corresponding to the higher + * index of the matrix) to which the matrix is applied. + * @param output0 The updated first qubit identifier after transformation. + * @param output1 The updated second qubit identifier after transformation. + * @param matrix The 4x4 unitary matrix describing the transformation + * to be applied to the specified qubit. + */ + void applyMatrix2Q(Value input0, Value input1, Value output0, Value output1, + const Matrix4x4& matrix); + /** + * Applies a unitary matrix to the QuantumState. + * + * @param inputs The values that the matrix is applied to. + * @param matrix The matrix that is applied to the QuantumState. + * @param outputs The values that replace the input values after matrix + * application. + * @return Whether the application was successful or not. + */ LogicalResult applyUnitary(ArrayRef inputs, const UnitaryMatrix& matrix, ArrayRef outputs); - /// Returns successor states paired with the measured classical result. + /** + * Simulates a quantum measurement on a given qubit and updates the quantum + * state, producing possible successor states along with their classical + * outcomes. + * + * @param inQubit The qubit to be measured. + * @param outQubit The qubit value after the measurement. + * @param ctx The MLIRContext used for type creation and attribute + * propagation. + * @return A map of possible successor states paired with their probability. + * The keys are the measurement results. + */ std::unordered_map> measure(Value inQubit, Value outQubit, MLIRContext* ctx) const; }; +/** + * This struct represents a HybridState. It contains a QuantumState and + * classical values that are tracked alongside the QuantumState. It is top if + * the QuantumState is top. + */ struct HybridState { +private: llvm::DenseMap classicalValues; - QuantumState quantumState; + std::unique_ptr quantumState; + unsigned int maxTrackedAmplitudes; double probability = 1.0; + bool isTop = false; + +public: + explicit HybridState(const unsigned int maxTrackedAmplitudes, + const Value qubit) + : quantumState(std::make_unique( + QuantumState::singletonZero(maxTrackedAmplitudes, qubit))), + maxTrackedAmplitudes(maxTrackedAmplitudes) {} + + explicit HybridState(const unsigned int maxTrackedAmplitudes) + : quantumState(nullptr), maxTrackedAmplitudes(maxTrackedAmplitudes) {} bool operator==(const HybridState& other) const; - std::optional getClassical(Value v) const; + /** + * Gets the attribute of a classical value if present. + * + * @param v The classical value to be checked. + * @return The Attribute of the classical value. + */ + [[nodiscard("HybridState::getClassical called but ignored.")]] std::optional< + Attribute> + getClassical(Value v) const; + + /** + * Sets the attribute of a classical value. If the value already has an + * attribute, it is overwritten. + * + * @param v The classical value to be set. + * @param attr The attribute to be set. + */ void setClassical(Value v, Attribute attr); }; +/** + * A set of all HybridStates in the current pass. It becomes top if either all + * HybridStates are top or if the number of HybridStates exceeds the maximum + * number. + */ struct HybridStateSet { +private: bool isTop = false; - llvm::SmallVector states; + unsigned int maxTrackedAmplitudes; + unsigned int maxTrackedHybridStates; + SmallVector states; + +public: + explicit HybridStateSet(const unsigned int maxTrackedAmplitudes, + const unsigned int maxTrackedHybridStates) + : maxTrackedAmplitudes(maxTrackedAmplitudes), + maxTrackedHybridStates(maxTrackedHybridStates) {} bool operator==(const HybridStateSet& other) const; - static HybridStateSet top(); + /** + * Creates a HybridStateSet with an empty set of HybridStates. + * + * @return An empty HybridStateSet. + */ static HybridStateSet singletonInitial(); + /** + * Adds a hybridState to the set. + * + * @param state The HybridState to be added. + */ void addState(HybridState state); void canonicalize(); void join(const HybridStateSet& other); - void enforceMaxStates(unsigned maxTrackedStates); - + [[nodiscard("HybridStateSet::isAlwaysZero called but ignored.")]] bool isAlwaysZero(Value v) const; + + [[nodiscard("HybridStateSet::isAlwaysOne called but ignored.")]] bool isAlwaysOne(Value v) const; - std::optional getUniqueConstant(Value v) const; }; /// Utility used by the pass analysis. From 339feb64bb7872b820a5e855564e82b6d1471956 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 25 Aug 2026 09:37:01 +0200 Subject: [PATCH 03/55] :construction: Improved QuantumState --- .../ConstantPropagationLattice.cpp | 215 ++++++++---------- .../ConstantPropagationLattice.hpp | 116 +++++----- 2 files changed, 154 insertions(+), 177 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp index 0cdbda5ce5..b573259481 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp @@ -156,12 +156,9 @@ bool QuantumState::isAlwaysZero(const Value q) const { if (!idx) { return false; } - for (const auto& it : amplitudes) { - if ((it.first >> *idx & 1ULL) != 0ULL) { - return false; - } - } - return true; + return std::ranges::all_of(amplitudes, [&](const auto& it) { + return (it.first >> *idx & 1ULL) == 0ULL; + }); } bool QuantumState::isAlwaysOne(const Value q) const { @@ -172,12 +169,9 @@ bool QuantumState::isAlwaysOne(const Value q) const { if (!idx) { return false; } - for (const auto& it : amplitudes) { - if ((it.first >> *idx & 1ULL) == 0ULL) { - return false; - } - } - return true; + return std::ranges::all_of(amplitudes, [&](const auto& it) { + return (it.first >> *idx & 1ULL) != 0ULL; + }); } void QuantumState::markTop() { @@ -187,8 +181,9 @@ void QuantumState::markTop() { void QuantumState::forwardQubit(const Value from, const Value to) { const auto id = indexOf(from); - if (!id) + if (!id) { return; + } qubits[id.value()] = to; } @@ -244,10 +239,12 @@ void QuantumState::applyMatrix1Q(const Value input, const Value output, Complex in1 = it.second[1]; Complex out0 = matrix[0][0] * in0 + matrix[0][1] * in1; Complex out1 = matrix[1][0] * in0 + matrix[1][1] * in1; - if (out0 != Complex(0.0, 0.0)) + if (out0 != Complex(0.0, 0.0)) { result[insertBit(it.first, idx, false)] += out0; - if (out1 != Complex(0.0, 0.0)) + } + if (out1 != Complex(0.0, 0.0)) { result[insertBit(it.first, idx, true)] += out1; + } } amplitudes = std::move(result); @@ -257,6 +254,10 @@ void QuantumState::applyMatrix1Q(const Value input, const Value output, break; } } + if (amplitudes.size() > maxTrackedAmplitudes) { + amplitudes.clear(); + isTop = true; + } } void QuantumState::applyMatrix2Q(const Value input0, const Value input1, @@ -296,14 +297,16 @@ void QuantumState::applyMatrix2Q(const Value input0, const Value input1, std::array outVec{}; for (unsigned row = 0; row < 4; ++row) { Complex sum(0.0, 0.0); - for (unsigned col = 0; col < 4; ++col) + for (unsigned col = 0; col < 4; ++col) { sum += matrix[row][col] * it.second[col]; + } outVec[row] = sum; } for (unsigned row = 0; row < 4; ++row) { - if (outVec[row] == Complex(0.0, 0.0)) + if (outVec[row] == Complex(0.0, 0.0)) { continue; + } bool b0 = (row & 1u) != 0u; bool b1 = (row & 2u) != 0u; uint64_t basis = insertBit(insertBit(it.first, idx0, b0), idx1, b1); @@ -311,157 +314,117 @@ void QuantumState::applyMatrix2Q(const Value input0, const Value input1, } } - out.amplitudes = std::move(result); - for (Value& q : out.qubits) { - if (q == input0) + amplitudes = std::move(result); + for (Value& q : qubits) { + if (q == input0) { q = output0; - else if (q == input1) + } else if (q == input1) { q = output1; + } + } + if (amplitudes.size() > maxTrackedAmplitudes) { + amplitudes.clear(); + isTop = true; } - out.enforceMaxAmplitudes(maxTrackedAmplitudes); - return out; } -LogicalResult QuantumState::applyUnitary(ArrayRef inputs, +LogicalResult QuantumState::applyUnitary(const ArrayRef inputs, const UnitaryMatrix& matrix, - ArrayRef outputs, - unsigned maxTrackedAmplitudes) { - if (inputs.size() != outputs.size()) + const ArrayRef outputs) { + + if (inputs.size() != outputs.size()) { return failure(); - if (inputs.empty() || inputs.size() > 2) + } + if (inputs.empty() || inputs.size() > 2) { return failure(); - - for (Value in : inputs) { - if (!getComponentId(in)) - initializeQubit(in); } - if (inputs.size() == 1) { - auto id = getComponentId(inputs[0]); - if (!id) + for (const auto& in : inputs) { + if (!indexOf(in)) { return failure(); - - QuantumState component = components[*id]; - if (!std::holds_alternative(matrix)) { - component.markTop(); - } else { - component = - applyMatrix1Q(component, inputs[0], outputs[0], - std::get(matrix), maxTrackedAmplitudes); } + } - unsigned newId = nextComponentId++; - components.erase(*id); - qubitToComponent.erase(inputs[0]); - components[newId] = std::move(component); - qubitToComponent[outputs[0]] = newId; + if (isTop) { + for (const auto& [in, out] : llvm::zip(inputs, outputs)) { + forwardQubit(in, out); + } return success(); } - if (failed(mergeComponents(inputs[0], inputs[1], maxTrackedAmplitudes))) - return failure(); + if (inputs.size() == 1) { + if (!std::holds_alternative(matrix)) { + return failure(); + } + applyMatrix1Q(inputs[0], outputs[0], std::get(matrix)); - auto mergedId = getComponentId(inputs[0]); - if (!mergedId) - return failure(); + return success(); + } - QuantumState component = components[*mergedId]; if (!std::holds_alternative(matrix)) { - component.markTop(); - } else { - component = - applyMatrix2Q(component, inputs[0], inputs[1], outputs[0], outputs[1], - std::get(matrix), maxTrackedAmplitudes); + return failure(); } + applyMatrix2Q(inputs[0], inputs[1], outputs[0], outputs[1], + std::get(matrix)); - components.erase(*mergedId); - qubitToComponent.erase(inputs[0]); - qubitToComponent.erase(inputs[1]); - - unsigned newId = nextComponentId++; - components[newId] = std::move(component); - qubitToComponent[outputs[0]] = newId; - qubitToComponent[outputs[1]] = newId; return success(); } -SmallVector> -QuantumState::measure(Value inQubit, Value outQubit, MLIRContext* ctx) const { - SmallVector> successors; - auto compId = getComponentId(inQubit); - if (!compId) { - QuantumState unknown = *this; - auto i1 = IntegerType::get(ctx, 1); - // Unknown classical result cannot be represented as an Attribute directly - // here, so return no successors and let caller handle top/unknown. - (void)i1; - return successors; - } - - const QuantumState& component = components.at(*compId); - if (component.isTop) - return successors; - - auto idxOpt = component.indexOf(inQubit); - if (!idxOpt) - return successors; +std::unordered_map> +QuantumState::measure(const Value inQubit, const Value outQubit, + MLIRContext* ctx) { + + if (isTop) { + forwardQubit(inQubit, outQubit); + return {}; + } + + const auto idxOpt = indexOf(inQubit); + if (!idxOpt) { + llvm::report_fatal_error("Called measure on a qubit not in the state"); + } unsigned idx = *idxOpt; double prob0 = 0.0; double prob1 = 0.0; - for (const auto& it : component.amplitudes) { - double p = std::norm(it.second); - if (((it.first >> idx) & 1ULL) == 0ULL) + for (const auto& it : amplitudes) { + const double p = std::norm(it.second); + if ((it.first >> idx & 1ULL) == 0ULL) { prob0 += p; - else + } else { prob1 += p; + } } - auto makeSuccessor = [&](bool bit) { - QuantumState next = *this; - auto nextId = next.getComponentId(inQubit); - if (!nextId) - return std::pair{next, {}}; - - QuantumState& c = next.components[*nextId]; - llvm::DenseMap filtered; - double norm = 0.0; - for (const auto& it : c.amplitudes) { - bool curBit = (((it.first >> idx) & 1ULL) != 0ULL); + auto makeSuccessor = [&](const bool bit, const double probability) { + const double scaleFactor = 1.0 / std::sqrt(probability); + auto c = QuantumState(maxTrackedAmplitudes); + for (const auto& it : amplitudes) { + const bool curBit = (it.first >> idx & 1ULL) != 0ULL; if (curBit == bit) { - filtered[it.first] = it.second; - norm += std::norm(it.second); + c.amplitudes[it.first] = it.second * scaleFactor; } } - if (norm == 0.0) { - c.markTop(); - } else { - double scale = 1.0 / std::sqrt(norm); - for (auto& it : filtered) - it.second *= scale; - c.amplitudes = std::move(filtered); - } - - for (Value& q : c.qubits) { + for (Value& q : qubits) { if (q == inQubit) { - q = outQubit; - break; + c.qubits.push_back(outQubit); + } else { + c.qubits.push_back(q); } } - next.qubitToComponent.erase(inQubit); - next.qubitToComponent[outQubit] = *nextId; - auto i1 = IntegerType::get(ctx, 1); - Attribute bitAttr = IntegerAttr::get(i1, bit ? 1 : 0); - return std::pair{std::move(next), bitAttr}; + return std::pair{std::move(c), probability}; }; - if (prob0 > 0.0) - successors.push_back(makeSuccessor(false)); - if (prob1 > 0.0) - successors.push_back(makeSuccessor(true)); - return successors; + std::unordered_map> result; + if (std::norm(prob0 - 0.0) >= 1e-10) { + result.emplace(0, makeSuccessor(false, prob0)); + } + if (std::norm(prob1 - 0.0) >= 1e-10) { + result.emplace(1, makeSuccessor(true, prob1)); + } + return result; } //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp index d3c6e41417..6fd56e062e 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp @@ -41,6 +41,56 @@ struct QuantumState { SmallVector qubits; llvm::DenseMap amplitudes; + /** + * Checks what the index of value v in the QuantumState is. + * + * @param v The value to look for. + * @return The index where v is in QuantumState. + */ + [[nodiscard("QuantumState::indexOf called but ignored.")]] + std::optional indexOf(Value v) const; + + /** + * Computes the tensor product of this QuantumState with another QuantumState. + * The tensor product combines the qubits and amplitudes of both states, + * producing a new QuantumState that represents the combined quantum system. + * + * @param that The QuantumState to be combined with this QuantumState. + * @return A new QuantumState representing the tensor product of the two + * states. + */ + [[nodiscard("QuantumState::tensorProduct called but ignored.")]] + QuantumState tensorProduct(const QuantumState& that); + + /** + * Applies a 2x2 unitary matrix to a single qubit in the QuantumState. + * This operation updates the quantum state's amplitude distribution + * and the tracked qubit values. + * + * @param input The qubit identifier to which the matrix is applied. + * @param output The updated qubit identifier after transformation. + * @param matrix The 2x2 unitary matrix describing the transformation + * to be applied to the specified qubit. + */ + void applyMatrix1Q(Value input, Value output, const Matrix2x2& matrix); + + /** + * Applies a 4x4 unitary matrix to a single qubit in the QuantumState. + * This operation updates the quantum state's amplitude distribution + * and the tracked qubit values. + * + * @param input0 The first qubit identifier (corresponding to the lower index + * of the matrix) to which the matrix is applied. + * @param input1 The second qubit identifier (corresponding to the higher + * index of the matrix) to which the matrix is applied. + * @param output0 The updated first qubit identifier after transformation. + * @param output1 The updated second qubit identifier after transformation. + * @param matrix The 4x4 unitary matrix describing the transformation + * to be applied to the specified qubit. + */ + void applyMatrix2Q(Value input0, Value input1, Value output0, Value output1, + const Matrix4x4& matrix); + public: explicit QuantumState(const unsigned int maxTrackedAmplitudes) : maxTrackedAmplitudes(maxTrackedAmplitudes) {} @@ -67,15 +117,6 @@ struct QuantumState { [[nodiscard("QuantumState::contains called but ignored.")]] bool contains(Value v) const; - /** - * Checks what the index of value v in the QuantumState is. - * - * @param v The value to look for. - * @return The index where v is in QuantumState. - */ - [[nodiscard("QuantumState::indexOf called but ignored.")]] - std::optional indexOf(Value v) const; - /** * Check if a value is always zero. * @@ -107,47 +148,6 @@ struct QuantumState { */ void forwardQubit(Value from, Value to); - /** - * Computes the tensor product of this QuantumState with another QuantumState. - * The tensor product combines the qubits and amplitudes of both states, - * producing a new QuantumState that represents the combined quantum system. - * - * @param that The QuantumState to be combined with this QuantumState. - * @return A new QuantumState representing the tensor product of the two - * states. - */ - [[nodiscard("QuantumState::tensorProduct called but ignored.")]] - QuantumState tensorProduct(const QuantumState& that); - - /** - * Applies a 2x2 unitary matrix to a single qubit in the QuantumState. - * This operation updates the quantum state's amplitude distribution - * and the tracked qubit values. - * - * @param input The qubit identifier to which the matrix is applied. - * @param output The updated qubit identifier after transformation. - * @param matrix The 2x2 unitary matrix describing the transformation - * to be applied to the specified qubit. - */ - void applyMatrix1Q(Value input, Value output, const Matrix2x2& matrix); - - /** - * Applies a 4x4 unitary matrix to a single qubit in the QuantumState. - * This operation updates the quantum state's amplitude distribution - * and the tracked qubit values. - * - * @param input0 The first qubit identifier (corresponding to the lower index - * of the matrix) to which the matrix is applied. - * @param input1 The second qubit identifier (corresponding to the higher - * index of the matrix) to which the matrix is applied. - * @param output0 The updated first qubit identifier after transformation. - * @param output1 The updated second qubit identifier after transformation. - * @param matrix The 4x4 unitary matrix describing the transformation - * to be applied to the specified qubit. - */ - void applyMatrix2Q(Value input0, Value input1, Value output0, Value output1, - const Matrix4x4& matrix); - /** * Applies a unitary matrix to the QuantumState. * @@ -174,7 +174,7 @@ struct QuantumState { * The keys are the measurement results. */ std::unordered_map> - measure(Value inQubit, Value outQubit, MLIRContext* ctx) const; + measure(Value inQubit, Value outQubit, MLIRContext* ctx); }; /** @@ -220,6 +220,15 @@ struct HybridState { * @param attr The attribute to be set. */ void setClassical(Value v, Attribute attr); + + /** + * Checks whether a HybridState contains a value. The value can be a quantum + * or a classical one. + * + * @param v The value to check for. + * @return Whether the value is in the HybridState. + */ + bool contains(Value v); }; /** @@ -234,6 +243,8 @@ struct HybridStateSet { unsigned int maxTrackedHybridStates; SmallVector states; + // TODO: Merging of Hybrid States given values + public: explicit HybridStateSet(const unsigned int maxTrackedAmplitudes, const unsigned int maxTrackedHybridStates) @@ -255,6 +266,9 @@ struct HybridStateSet { * @param state The HybridState to be added. */ void addState(HybridState state); + + // TODO: Application of various operations + void canonicalize(); void join(const HybridStateSet& other); From 38ea72f989cf99c9b36a11f764f80302db353023 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 25 Aug 2026 12:25:02 +0200 Subject: [PATCH 04/55] :construction: Improved Generated Code --- .../Optimizations/ConstantPropagation.cpp | 82 ++++++++++-------- .../ConstantPropagationLattice.cpp | 84 +++++++++++-------- .../ConstantPropagationLattice.hpp | 22 +++-- 3 files changed, 106 insertions(+), 82 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 173a8fcb44..6dc46ad71d 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -14,24 +14,27 @@ // Adjust these includes to your actual generated QCO interface/type headers. #include "mlir/Analysis/DataFlow/SparseAnalysis.h" #include "mlir/Dialect/Arith/IR/Arith.h" -#include "mlir/Dialect/QCO/IR/QCOOpsTypes.h.inc" +#include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Pass/Pass.h" #include "mlir/Transforms/GreedyPatternRewriteDriver.h" using namespace mlir; -namespace mlir::mqt::qco { +namespace mlir::qco { -static bool isQubitType(Type ty) { return isa(ty); } +static unsigned int maxTrackedAmplitudes = 8; +static unsigned int maxTrackedHybridStates = 4; -static bool isClassicalType(Type ty) { return ty.isIntOrIndexOrFloat(); } +static bool isQubitType(const Type ty) { return isa(ty); } + +static bool isClassicalType(const Type ty) { return ty.isIntOrIndexOrFloat(); } static std::optional foldWithState(Operation* op, const HybridState& state) { SmallVector operandAttrs; operandAttrs.reserve(op->getNumOperands()); - for (Value operand : op->getOperands()) { + for (const Value operand : op->getOperands()) { auto attr = state.getClassical(operand); if (!attr) { return std::nullopt; @@ -51,59 +54,68 @@ static std::optional foldWithState(Operation* op, class HybridStateLattice : public dataflow::AbstractSparseLattice { public: - explicit HybridStateLattice(Value anchor) - : dataflow::AbstractSparseLattice(anchor) {} + using AbstractSparseLattice::AbstractSparseLattice; + + explicit HybridStateLattice(const Value anchor) + : AbstractSparseLattice(anchor), + value(HybridStateSet(maxTrackedAmplitudes, maxTrackedHybridStates)) {} const HybridStateSet& getValue() const { return value; } - ChangeResult join(const HybridStateSet& rhs) { - HybridStateSet old = value; - value.join(rhs); + ChangeResult join(const AbstractSparseLattice& rhs) override { + const auto rhsHS = llvm::cast(rhs); + const HybridStateSet old = value; + value.join(rhsHS.getValue()); return old == value ? ChangeResult::NoChange : ChangeResult::Change; } + ChangeResult meet(const AbstractSparseLattice& rhs) override { + return join(rhs); + } + + void print(raw_ostream& os) const override; + private: - HybridStateSet value = HybridStateSet::singletonInitial(); + HybridStateSet value; }; class HybridConstantPropagationAnalysis : public dataflow::SparseForwardDataFlowAnalysis { public: - explicit HybridConstantPropagationAnalysis(DataFlowSolver& solver, - unsigned maxTrackedAmplitudes, - unsigned maxTrackedStates) - : dataflow::SparseForwardDataFlowAnalysis(solver), - maxTrackedAmplitudes(maxTrackedAmplitudes), - maxTrackedStates(maxTrackedStates) {} + explicit HybridConstantPropagationAnalysis(DataFlowSolver& solver) + : SparseForwardDataFlowAnalysis(solver) {} void setToEntryState(dataflow::AbstractSparseLattice* lattice) override { + const auto value = lattice->getAnchor(); auto* hybrid = llvm::cast(lattice); - propagateIfChanged(hybrid, - hybrid->join(HybridStateSet::singletonInitial())); + // TODO: Propagate the values to the HybridState + // auto newLattice = HybridStateLattice(); + // propagateIfChanged(hybrid, hybrid->join(newLattice)); } LogicalResult visitOperation(Operation* op, - ArrayRef operands, - ArrayRef results) override { + const ArrayRef operands, + ArrayRef results) override { HybridStateSet input = gatherInputState(operands); - if (input.isTop) { - setAllResults(results, HybridStateSet::top()); + if (input.areStatesTop()) { + // TODO: Forward Qubits to results + // setAllResults(results, HybridStateSet::top()); return success(); } - if (auto measureOp = dyn_cast(op)) { + if (const auto measureOp = dyn_cast(op)) { visitMeasureOp(measureOp, input, results); return success(); } - if (auto unitary = dyn_cast(op)) { + if (const auto unitary = dyn_cast(op)) { visitUnitaryOp(op, unitary, input, results); return success(); } - if (auto ctrlOp = dyn_cast(op)) { + if (const auto ctrlOp = dyn_cast(op)) { visitCtrlOp(ctrlOp, input, results); return success(); } @@ -118,9 +130,6 @@ class HybridConstantPropagationAnalysis } private: - unsigned maxTrackedAmplitudes; - unsigned maxTrackedStates; - static HybridStateLattice* asHybrid(dataflow::AbstractSparseLattice* l) { return llvm::cast(l); } @@ -130,8 +139,9 @@ class HybridConstantPropagationAnalysis return llvm::cast(l); } + // TODO: Merge :) HybridStateSet - gatherInputState(ArrayRef operands) { + gatherInputState(ArrayRef operands) { HybridStateSet input = HybridStateSet::singletonInitial(); bool first = true; for (const auto* operand : operands) { @@ -146,7 +156,7 @@ class HybridConstantPropagationAnalysis return input; } - void setAllResults(ArrayRef results, + void setAllResults(const ArrayRef results, const HybridStateSet& state) { for (auto* res : results) { auto* lat = asHybrid(res); @@ -155,7 +165,7 @@ class HybridConstantPropagationAnalysis } void visitClassicalOp(Operation* op, const HybridStateSet& input, - ArrayRef results) { + ArrayRef results) { HybridStateSet output; output.states.clear(); @@ -177,7 +187,7 @@ class HybridConstantPropagationAnalysis void visitUnitaryOp(Operation* op, qco::UnitaryOpInterface unitary, const HybridStateSet& input, - ArrayRef results) { + ArrayRef results) { HybridStateSet output; output.states.clear(); @@ -202,7 +212,7 @@ class HybridConstantPropagationAnalysis } void visitMeasureOp(qco::MeasureOp op, const HybridStateSet& input, - ArrayRef results) { + ArrayRef results) { HybridStateSet output; output.states.clear(); @@ -254,7 +264,7 @@ class HybridConstantPropagationAnalysis } void visitCtrlOp(qco::CtrlOp op, const HybridStateSet& input, - ArrayRef results) { + ArrayRef results) { // Forward target inputs conservatively. HybridStateSet output; output.states = input.states; @@ -278,7 +288,7 @@ class HybridConstantPropagationAnalysis } void visitFallback(Operation* op, const HybridStateSet& input, - ArrayRef results) { + ArrayRef results) { HybridStateSet output = input; for (HybridState& state : output.states) { for (Value res : op->getResults()) { diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp index b573259481..4c406f6f1f 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp @@ -19,7 +19,7 @@ #include using namespace mlir; -namespace mlir::mqt::qco { +namespace mlir::qco { /** * Removes the bit at a specified position in a 64-bit unsigned integer. @@ -431,73 +431,89 @@ QuantumState::measure(const Value inQubit, const Value outQubit, // HybridState //===----------------------------------------------------------------------===// -bool HybridState::operator==(const HybridState& other) const { - if (probability != other.probability) +bool HybridState::operator==(const HybridState& that) const { + if (isTop || that.isTop) { + return isTop == that.isTop; + } + if (std::norm(probability - that.probability) >= 1e-10) { return false; - if (!(quantumState == other.quantumState)) + } + if (maxTrackedAmplitudes != that.maxTrackedAmplitudes) { return false; - if (classicalValues.size() != other.classicalValues.size()) + } + if (quantumState != that.quantumState) { return false; + } + if (classicalValues.size() != that.classicalValues.size()) { + return false; + } for (const auto& it : classicalValues) { - auto found = other.classicalValues.find(it.first); - if (found == other.classicalValues.end()) + auto found = that.classicalValues.find(it.first); + if (found == that.classicalValues.end()) { return false; - if (!sameAttribute(it.second, found->second)) + } + if (it.second != found->second) { return false; + } } return true; } -std::optional HybridState::getClassical(Value v) const { - auto it = classicalValues.find(v); - if (it == classicalValues.end()) - return std::nullopt; +std::optional HybridState::getClassical(const Value v) const { + const auto it = classicalValues.find(v); + if (it == classicalValues.end()) { + return {}; + } return it->second; } -void HybridState::setClassical(Value v, Attribute attr) { +void HybridState::setClassical(const Value v, const Attribute attr) { classicalValues[v] = attr; } +bool HybridState::contains(const Value v) const { + if (getClassical(v).has_value()) { + return true; + } + return quantumState->contains(v); +} + //===----------------------------------------------------------------------===// // HybridStateSet //===----------------------------------------------------------------------===// -bool HybridStateSet::operator==(const HybridStateSet& other) const { - if (isTop != other.isTop) +bool HybridStateSet::operator==(const HybridStateSet& that) const { + if (isTop || that.isTop) { + return isTop && that.isTop; + } + if (states.size() != that.states.size()) { return false; - if (isTop) - return true; - if (states.size() != other.states.size()) + } + if (maxTrackedAmplitudes != that.maxTrackedAmplitudes) { + return false; + } + if (maxTrackedHybridStates != that.maxTrackedHybridStates) { return false; + } for (const auto& s : states) { - if (!llvm::is_contained(other.states, s)) + if (!llvm::is_contained(that.states, s)) { return false; + } } return true; } -HybridStateSet HybridStateSet::top() { - HybridStateSet s; - s.isTop = true; - return s; -} - -HybridStateSet HybridStateSet::singletonInitial() { - HybridStateSet s; - s.states.push_back(HybridState{}); - return s; -} - void HybridStateSet::addState(HybridState state) { - if (isTop) + if (isTop) { return; + } states.push_back(std::move(state)); } void HybridStateSet::canonicalize() { - if (isTop) + if (isTop) { return; + } SmallVector merged; for (HybridState& state : states) { @@ -582,4 +598,4 @@ std::optional HybridStateSet::getUniqueConstant(Value v) const { } return candidate; } -} // namespace mlir::mqt::qco \ No newline at end of file +} // namespace mlir::qco \ No newline at end of file diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp index 6fd56e062e..d7fb0dfbee 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp @@ -21,7 +21,7 @@ #include #include -namespace mlir::mqt::qco { +namespace mlir::qco { using Complex = std::complex; @@ -200,7 +200,7 @@ struct HybridState { explicit HybridState(const unsigned int maxTrackedAmplitudes) : quantumState(nullptr), maxTrackedAmplitudes(maxTrackedAmplitudes) {} - bool operator==(const HybridState& other) const; + bool operator==(const HybridState& that) const; /** * Gets the attribute of a classical value if present. @@ -228,7 +228,9 @@ struct HybridState { * @param v The value to check for. * @return Whether the value is in the HybridState. */ - bool contains(Value v); + bool contains(Value v) const; + + // TODO: Application of various operations }; /** @@ -251,14 +253,7 @@ struct HybridStateSet { : maxTrackedAmplitudes(maxTrackedAmplitudes), maxTrackedHybridStates(maxTrackedHybridStates) {} - bool operator==(const HybridStateSet& other) const; - - /** - * Creates a HybridStateSet with an empty set of HybridStates. - * - * @return An empty HybridStateSet. - */ - static HybridStateSet singletonInitial(); + bool operator==(const HybridStateSet& that) const; /** * Adds a hybridState to the set. @@ -272,6 +267,9 @@ struct HybridStateSet { void canonicalize(); void join(const HybridStateSet& other); + [[nodiscard("HybridStateSet::isTop called but ignored.")]] + bool areStatesTop() const; + [[nodiscard("HybridStateSet::isAlwaysZero called but ignored.")]] bool isAlwaysZero(Value v) const; @@ -283,4 +281,4 @@ struct HybridStateSet { bool isZeroAttribute(Attribute attr); bool isOneAttribute(Attribute attr); -} // namespace mlir::mqt::qco \ No newline at end of file +} // namespace mlir::qco \ No newline at end of file From 4d1dc93b941b411797f64974223ceaec8c4f1e26 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 25 Aug 2026 14:01:38 +0200 Subject: [PATCH 05/55] :construction: Improved Generated Code --- .../ConstantPropagationLattice.cpp | 108 +++++++----------- .../ConstantPropagationLattice.hpp | 90 ++++++++++++++- 2 files changed, 126 insertions(+), 72 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp index 4c406f6f1f..bacc095efc 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -73,15 +72,15 @@ bool isZeroAttribute(const Attribute attr) { return false; } -bool isOneAttribute(const Attribute attr) { +bool isTrueAttribute(const Attribute attr) { if (!attr) { return false; } if (const auto intAttr = dyn_cast(attr)) { - return intAttr.getValue().isOne(); + return !intAttr.getValue().isZero(); } if (const auto floatAttr = dyn_cast(attr)) { - return floatAttr.getValue().isExactlyValue(1.0); + return !floatAttr.getValue().isZero(); } if (const auto boolAttr = dyn_cast(attr)) { return boolAttr.getValue(); @@ -478,6 +477,22 @@ bool HybridState::contains(const Value v) const { return quantumState->contains(v); } +bool HybridState::isAlwaysFalse(const Value v) const { + const auto attr = getClassical(v); + if (attr.has_value()) { + return isZeroAttribute(attr.value()); + } + return quantumState->isAlwaysZero(v); +} + +bool HybridState::isAlwaysTrue(const Value v) const { + const auto attr = getClassical(v); + if (attr.has_value()) { + return isTrueAttribute(attr.value()); + } + return quantumState->isAlwaysOne(v); +} + //===----------------------------------------------------------------------===// // HybridStateSet //===----------------------------------------------------------------------===// @@ -510,92 +525,53 @@ void HybridStateSet::addState(HybridState state) { states.push_back(std::move(state)); } -void HybridStateSet::canonicalize() { - if (isTop) { - return; - } - - SmallVector merged; - for (HybridState& state : states) { - bool found = false; - for (HybridState& existing : merged) { - HybridState lhs = state; - HybridState rhs = existing; - lhs.probability = 0.0; - rhs.probability = 0.0; - if (lhs == rhs) { - existing.probability += state.probability; - found = true; - break; - } - } - if (!found) - merged.push_back(std::move(state)); - } - states = std::move(merged); -} - void HybridStateSet::join(const HybridStateSet& other) { if (isTop || other.isTop) { isTop = true; states.clear(); return; } - states.append(other.states.begin(), other.states.end()); - canonicalize(); + llvm::append_range(states, other.states); } -void HybridStateSet::enforceMaxStates(unsigned maxTrackedStates) { - if (isTop) +void HybridStateSet::enforceMaxStates() { + if (isTop) { return; - canonicalize(); - if (states.size() > maxTrackedStates) { + } + if (states.size() > maxTrackedHybridStates) { isTop = true; states.clear(); } } -bool HybridStateSet::isAlwaysZero(Value v) const { - if (isTop || states.empty()) +bool HybridStateSet::areStatesTop() const { return isTop; } + +bool HybridStateSet::isAlwaysFalse(const Value v) const { + if (isTop) { return false; + } for (const HybridState& state : states) { - auto attr = state.getClassical(v); - if (attr && isZeroAttribute(*attr)) - continue; - if (state.quantumState.isAlwaysZero(v)) - continue; - return false; + if (state.contains(v)) { + if (!state.isAlwaysFalse(v)) { + return false; + } + } } return true; } -bool HybridStateSet::isAlwaysOne(Value v) const { - if (isTop || states.empty()) +bool HybridStateSet::isAlwaysTrue(const Value v) const { + if (isTop) { return false; + } for (const HybridState& state : states) { - auto attr = state.getClassical(v); - if (attr && isOneAttribute(*attr)) - continue; - if (state.quantumState.isAlwaysOne(v)) - continue; - return false; + if (state.contains(v)) { + if (!state.isAlwaysTrue(v)) { + return false; + } + } } return true; } -std::optional HybridStateSet::getUniqueConstant(Value v) const { - if (isTop || states.empty()) - return std::nullopt; - std::optional candidate; - for (const HybridState& state : states) { - auto attr = state.getClassical(v); - if (!attr) - return std::nullopt; - if (!candidate) - candidate = attr; - else if (*candidate != *attr) - return std::nullopt; - } - return candidate; -} } // namespace mlir::qco \ No newline at end of file diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp index d7fb0dfbee..e058f3272a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp @@ -200,6 +200,33 @@ struct HybridState { explicit HybridState(const unsigned int maxTrackedAmplitudes) : quantumState(nullptr), maxTrackedAmplitudes(maxTrackedAmplitudes) {} + explicit HybridState(const HybridState& that) + : classicalValues(that.classicalValues), + quantumState(that.quantumState + ? std::make_unique(*that.quantumState) + : nullptr), + maxTrackedAmplitudes(that.maxTrackedAmplitudes), + probability(that.probability), isTop(that.isTop) {} + + HybridState& operator=(const HybridState& that) { + if (this == &that) { + return *this; + } + + classicalValues = that.classicalValues; + maxTrackedAmplitudes = that.maxTrackedAmplitudes; + probability = that.probability; + isTop = that.isTop; + + if (that.quantumState) { + quantumState = std::make_unique(*that.quantumState); + } else { + quantumState.reset(); + } + + return *this; + } + bool operator==(const HybridState& that) const; /** @@ -230,6 +257,28 @@ struct HybridState { */ bool contains(Value v) const; + /** + * Checks if a value is always false, i.e., false/zero if it is a classical + * value and |0> if it is a quantum value. If the value is not part of the + * HybridState, the result is false. + * + * @param v The value to be checked. + * @return Whether the value is always false. + */ + [[nodiscard("HybridState::isAlwaysFalse called but ignored.")]] + bool isAlwaysFalse(Value v) const; + + /** + * Checks if a value is always false, i.e., true/nonzero if it is a classical + * value and |1> if it is a quantum value. If the value is not part of the + * HybridState, the result is false. + * + * @param v The value to be checked. + * @return Whether the value is always true. + */ + [[nodiscard("HybridState::isAlwaysTrue called but ignored.")]] + bool isAlwaysTrue(Value v) const; + // TODO: Application of various operations }; @@ -264,21 +313,50 @@ struct HybridStateSet { // TODO: Application of various operations - void canonicalize(); + /** + * Joins HybridStateSets after branching. In that case, the new HybridStateSet + * is the union of the states in both old sets. If either of the old sets is + * top, the new state is top. + * + * @param other the HybridStateSet to join the current set with. + */ void join(const HybridStateSet& other); + /** + * Checks if there are too many HybridStates in the set. If the number of + * states exceeds the specified maximum, the state set is marked as "top", and + * all individual states tracked in the set are cleared. + */ + void enforceMaxStates(); + [[nodiscard("HybridStateSet::isTop called but ignored.")]] bool areStatesTop() const; - [[nodiscard("HybridStateSet::isAlwaysZero called but ignored.")]] - bool isAlwaysZero(Value v) const; + /** + * Checks if a value is always false, i.e., false/zero if it is a classical + * value and |0> if it is a quantum value. If the value is not part of the + * HybridState, the result is false. + * + * @param v The value to be checked. + * @return Whether the value is always false. + */ + [[nodiscard("HybridStateSet::isAlwaysFalse called but ignored.")]] + bool isAlwaysFalse(Value v) const; - [[nodiscard("HybridStateSet::isAlwaysOne called but ignored.")]] - bool isAlwaysOne(Value v) const; + /** + * Checks if a value is always false, i.e., true/nonzero if it is a classical + * value and |1> if it is a quantum value. If the value is not part of the + * HybridState, the result is false. + * + * @param v The value to be checked. + * @return Whether the value is always true. + */ + [[nodiscard("HybridStateSet::isAlwaysTrue called but ignored.")]] + bool isAlwaysTrue(Value v) const; }; /// Utility used by the pass analysis. bool isZeroAttribute(Attribute attr); -bool isOneAttribute(Attribute attr); +bool isTrueAttribute(Attribute attr); } // namespace mlir::qco \ No newline at end of file From c949b499d737cbd26308305d2641dd9f23a05660 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 25 Aug 2026 14:22:01 +0200 Subject: [PATCH 06/55] :construction: Added merge of two HybridStates --- .../Optimizations/ConstantPropagation.cpp | 34 +++++----- .../ConstantPropagationLattice.cpp | 55 ++++++++++++++- .../ConstantPropagationLattice.hpp | 67 ++++++++++++------- 3 files changed, 113 insertions(+), 43 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 6dc46ad71d..e9edee49c3 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -96,8 +96,8 @@ class HybridConstantPropagationAnalysis LogicalResult visitOperation(Operation* op, const ArrayRef operands, - ArrayRef results) override { - HybridStateSet input = gatherInputState(operands); + const ArrayRef results) override { + const HybridStateSet input = gatherInputState(operands); if (input.areStatesTop()) { // TODO: Forward Qubits to results @@ -139,21 +139,17 @@ class HybridConstantPropagationAnalysis return llvm::cast(l); } - // TODO: Merge :) - HybridStateSet - gatherInputState(ArrayRef operands) { - HybridStateSet input = HybridStateSet::singletonInitial(); - bool first = true; - for (const auto* operand : operands) { - const HybridStateSet& state = asHybrid(operand)->getValue(); - if (first) { - input = state; - first = false; - } else { - input.join(state); - } + static HybridStateSet + gatherInputState(const ArrayRef operands) { + if (operands.size() == 1) { + return operands[0]->getValue(); + } + + auto result = operands[0]->getValue().mergeStates(operands[1]->getValue()); + for (unsigned int i = 2; i < operands.size(); ++i) { + result = result.mergeStates(operands[i]->getValue()); } - return input; + return result; } void setAllResults(const ArrayRef results, @@ -253,7 +249,7 @@ class HybridConstantPropagationAnalysis next.setClassical(outClassical, succ.second); if (isZeroAttribute(succ.second)) next.probability *= prob0; - else if (isOneAttribute(succ.second)) + else if (isTrueAttribute(succ.second)) next.probability *= prob1; output.addState(std::move(next)); } @@ -312,7 +308,7 @@ struct RemoveAlwaysZeroCtrlPattern : public OpRewritePattern { auto* state = solver.lookupState(ctrl); if (!state) return failure(); - if (!state->getValue().isAlwaysZero(ctrl)) + if (!state->getValue().isAlwaysFalse(ctrl)) continue; unsigned numResults = op->getNumResults(); @@ -358,7 +354,7 @@ struct ConstantPropagationPass } }; -} // namespace mlir::mqt::qco +} // namespace mlir::qco std::unique_ptr mlir::mqt::createConstantPropagationPass() { return std::make_unique(); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp index bacc095efc..19ff4118ba 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp @@ -191,7 +191,8 @@ QuantumState QuantumState::tensorProduct(const QuantumState& that) { result.qubits.append(qubits.begin(), qubits.end()); result.qubits.append(that.qubits.begin(), that.qubits.end()); - if (isTop || that.isTop) { + if (isTop || that.isTop || + amplitudes.size() * that.amplitudes.size() > maxTrackedAmplitudes) { result.isTop = true; return result; } @@ -204,6 +205,7 @@ QuantumState QuantumState::tensorProduct(const QuantumState& that) { } return result; } +bool QuantumState::isStateTop() const { return isTop; } void QuantumState::applyMatrix1Q(const Value input, const Value output, const Matrix2x2& matrix) { @@ -476,6 +478,7 @@ bool HybridState::contains(const Value v) const { } return quantumState->contains(v); } +bool HybridState::isStateTop() const { return isTop; } bool HybridState::isAlwaysFalse(const Value v) const { const auto attr = getClassical(v); @@ -493,6 +496,27 @@ bool HybridState::isAlwaysTrue(const Value v) const { return quantumState->isAlwaysOne(v); } +HybridState HybridState::mergeStates(const HybridState& that) const { + auto result = HybridState(maxTrackedAmplitudes); + result.probability = probability * that.probability; + + if (isTop || that.isTop) { + result.isTop = true; + return result; + } + result.classicalValues = classicalValues; + for (const auto& [v, a] : that.classicalValues) { + result.classicalValues[v] = a; + } + auto qS = quantumState->tensorProduct(*that.quantumState); + if (qS.isStateTop()) { + result.isTop = true; + return result; + } + result.quantumState = std::make_unique(qS); + return result; +} + //===----------------------------------------------------------------------===// // HybridStateSet //===----------------------------------------------------------------------===// @@ -522,6 +546,11 @@ void HybridStateSet::addState(HybridState state) { if (isTop) { return; } + if (maxTrackedHybridStates == states.size()) { + isTop = true; + states.clear(); + return; + } states.push_back(std::move(state)); } @@ -543,6 +572,30 @@ void HybridStateSet::enforceMaxStates() { states.clear(); } } +HybridStateSet HybridStateSet::mergeStates(const HybridStateSet& that) const { + auto result = HybridStateSet(maxTrackedAmplitudes, maxTrackedHybridStates); + if (isTop || that.isTop) { + result.isTop = true; + result.states.clear(); + return result; + } + bool allTop = true; + SmallVector newStates; + for (const auto& s : states) { + for (const auto& thatS : that.states) { + const auto newState = s.mergeStates(thatS); + newStates.push_back(newState); + allTop &= newState.isStateTop(); + } + } + if (allTop) { + result.isTop = true; + result.states.clear(); + } else { + result.states = std::move(newStates); + } + return result; +} bool HybridStateSet::areStatesTop() const { return isTop; } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp index e058f3272a..5d66e9d77a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp @@ -50,18 +50,6 @@ struct QuantumState { [[nodiscard("QuantumState::indexOf called but ignored.")]] std::optional indexOf(Value v) const; - /** - * Computes the tensor product of this QuantumState with another QuantumState. - * The tensor product combines the qubits and amplitudes of both states, - * producing a new QuantumState that represents the combined quantum system. - * - * @param that The QuantumState to be combined with this QuantumState. - * @return A new QuantumState representing the tensor product of the two - * states. - */ - [[nodiscard("QuantumState::tensorProduct called but ignored.")]] - QuantumState tensorProduct(const QuantumState& that); - /** * Applies a 2x2 unitary matrix to a single qubit in the QuantumState. * This operation updates the quantum state's amplitude distribution @@ -117,6 +105,21 @@ struct QuantumState { [[nodiscard("QuantumState::contains called but ignored.")]] bool contains(Value v) const; + /** + * Computes the tensor product of this QuantumState with another QuantumState. + * The tensor product combines the qubits and amplitudes of both states, + * producing a new QuantumState that represents the combined quantum system. + * + * @param that The QuantumState to be combined with this QuantumState. + * @return A new QuantumState representing the tensor product of the two + * states. + */ + [[nodiscard("QuantumState::tensorProduct called but ignored.")]] + QuantumState tensorProduct(const QuantumState& that); + + [[nodiscard("QuantumState::isStateTop called but ignored.")]] + bool isStateTop() const; + /** * Check if a value is always zero. * @@ -200,7 +203,7 @@ struct HybridState { explicit HybridState(const unsigned int maxTrackedAmplitudes) : quantumState(nullptr), maxTrackedAmplitudes(maxTrackedAmplitudes) {} - explicit HybridState(const HybridState& that) + HybridState(const HybridState& that) : classicalValues(that.classicalValues), quantumState(that.quantumState ? std::make_unique(*that.quantumState) @@ -257,6 +260,9 @@ struct HybridState { */ bool contains(Value v) const; + [[nodiscard("HybridState::isStateTop called but ignored.")]] + bool isStateTop() const; + /** * Checks if a value is always false, i.e., false/zero if it is a classical * value and |0> if it is a quantum value. If the value is not part of the @@ -280,6 +286,13 @@ struct HybridState { bool isAlwaysTrue(Value v) const; // TODO: Application of various operations + /** + * Merges two HybridStates which have QuantumState with different qubits. + * + * @param that The HybridStateSet to be merged with this. + * @return A new merged HybridState. + */ + HybridState mergeStates(const HybridState& that) const; }; /** @@ -294,8 +307,6 @@ struct HybridStateSet { unsigned int maxTrackedHybridStates; SmallVector states; - // TODO: Merging of Hybrid States given values - public: explicit HybridStateSet(const unsigned int maxTrackedAmplitudes, const unsigned int maxTrackedHybridStates) @@ -329,17 +340,27 @@ struct HybridStateSet { */ void enforceMaxStates(); - [[nodiscard("HybridStateSet::isTop called but ignored.")]] + /** + * Merges two HybridStateSets which have QuantumState with different qubits. + * Needs to be done before an operation entangles qubits from two + * HybridStates. + * + * @param that The HybridStateSet to be merged with this. + * @returns The new HybridStateSet. + */ + HybridStateSet mergeStates(const HybridStateSet& that) const; + + [[nodiscard("HybridStateSet::areStatesTop called but ignored.")]] bool areStatesTop() const; /** - * Checks if a value is always false, i.e., false/zero if it is a classical - * value and |0> if it is a quantum value. If the value is not part of the - * HybridState, the result is false. - * - * @param v The value to be checked. - * @return Whether the value is always false. - */ + * Checks if a value is always false, i.e., false/zero if it is a classical + * value and |0> if it is a quantum value. If the value is not part of the + * HybridState, the result is false. + * + * @param v The value to be checked. + * @return Whether the value is always false. + */ [[nodiscard("HybridStateSet::isAlwaysFalse called but ignored.")]] bool isAlwaysFalse(Value v) const; From 53f98c61ffde07ca2518e697fc3232f622a3391e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 26 Aug 2026 10:04:55 +0200 Subject: [PATCH 07/55] :construction: Propagate classical operations --- .../Optimizations/ConstantPropagation.cpp | 53 ++++--------------- .../ConstantPropagationLattice.cpp | 30 +++++++++++ .../ConstantPropagationLattice.hpp | 16 ++++++ 3 files changed, 57 insertions(+), 42 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index e9edee49c3..6dbad9549a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -26,32 +26,10 @@ namespace mlir::qco { static unsigned int maxTrackedAmplitudes = 8; static unsigned int maxTrackedHybridStates = 4; -static bool isQubitType(const Type ty) { return isa(ty); } +static bool isQubitType(const Type ty) { return isa(ty); } static bool isClassicalType(const Type ty) { return ty.isIntOrIndexOrFloat(); } -static std::optional foldWithState(Operation* op, - const HybridState& state) { - SmallVector operandAttrs; - operandAttrs.reserve(op->getNumOperands()); - for (const Value operand : op->getOperands()) { - auto attr = state.getClassical(operand); - if (!attr) { - return std::nullopt; - } - operandAttrs.push_back(*attr); - } - - SmallVector foldResults; - if (succeeded(op->fold(operandAttrs, foldResults)) && - foldResults.size() == 1) { - if (auto attr = llvm::dyn_cast(foldResults.front())) { - return attr; - } - } - return std::nullopt; -} - class HybridStateLattice : public dataflow::AbstractSparseLattice { public: using AbstractSparseLattice::AbstractSparseLattice; @@ -60,6 +38,9 @@ class HybridStateLattice : public dataflow::AbstractSparseLattice { : AbstractSparseLattice(anchor), value(HybridStateSet(maxTrackedAmplitudes, maxTrackedHybridStates)) {} + explicit HybridStateLattice(const Value anchor, const HybridStateSet& state) + : AbstractSparseLattice(anchor), value(state) {} + const HybridStateSet& getValue() const { return value; } ChangeResult join(const AbstractSparseLattice& rhs) override { @@ -96,8 +77,8 @@ class HybridConstantPropagationAnalysis LogicalResult visitOperation(Operation* op, const ArrayRef operands, - const ArrayRef results) override { - const HybridStateSet input = gatherInputState(operands); + ArrayRef results) override { + HybridStateSet input = gatherInputState(operands); if (input.areStatesTop()) { // TODO: Forward Qubits to results @@ -160,25 +141,13 @@ class HybridConstantPropagationAnalysis } } - void visitClassicalOp(Operation* op, const HybridStateSet& input, + void visitClassicalOp(Operation* op, HybridStateSet& input, ArrayRef results) { - HybridStateSet output; - output.states.clear(); - - for (const HybridState& state : input.states) { - HybridState next = state; - auto attr = foldWithState(op, state); - if (!attr) { - output.addState(std::move(next)); - continue; - } - if (!op->getResults().empty()) - next.setClassical(op->getResult(0), *attr); - output.addState(std::move(next)); + input.applyClassicalOperation(op); + for (auto [resLattice, resValue] : llvm::zip(results, op->getResults())) { + const auto newLattice = HybridStateLattice(resValue, input); + propagateIfChanged(resLattice, resLattice->join(newLattice)); } - - output.enforceMaxStates(maxTrackedStates); - setAllResults(results, output); } void visitUnitaryOp(Operation* op, qco::UnitaryOpInterface unitary, diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp index 19ff4118ba..94a4eac1a6 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp @@ -517,6 +517,30 @@ HybridState HybridState::mergeStates(const HybridState& that) const { return result; } +void HybridState::applyClassicalOperation(Operation* op) { + SmallVector operandAttrs; + operandAttrs.reserve(op->getNumOperands()); + for (const Value operand : op->getOperands()) { + auto attr = getClassical(operand); + if (!attr) { + llvm::report_fatal_error( + "Called operation on a classical value not in the state"); + } + operandAttrs.push_back(*attr); + } + + SmallVector foldResults; + if (succeeded(op->fold(operandAttrs, foldResults))) { + for (const auto& [val, res] : llvm::zip(op->getResults(), foldResults)) { + if (auto attr = llvm::dyn_cast(res)) { + setClassical(val, attr); + } + } + return; + } + llvm::report_fatal_error("Error while propagating classical operation."); +} + //===----------------------------------------------------------------------===// // HybridStateSet //===----------------------------------------------------------------------===// @@ -627,4 +651,10 @@ bool HybridStateSet::isAlwaysTrue(const Value v) const { return true; } +void HybridStateSet::applyClassicalOperation(Operation* op) { + for (HybridState& state : states) { + state.applyClassicalOperation(op); + } +} + } // namespace mlir::qco \ No newline at end of file diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp index 5d66e9d77a..23cd319e95 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp @@ -293,6 +293,13 @@ struct HybridState { * @return A new merged HybridState. */ HybridState mergeStates(const HybridState& that) const; + + /** + * Apply a classical state to the Hybrid state. + * + * @param op The classical operation to apply. + */ + void applyClassicalOperation(Operation* op); }; /** @@ -374,6 +381,15 @@ struct HybridStateSet { */ [[nodiscard("HybridStateSet::isAlwaysTrue called but ignored.")]] bool isAlwaysTrue(Value v) const; + + /** + * Applies a classical operation on all HybridStates of the set and returns a + * new set. + * + * @param op The operation to apply. + * @return The set with applied operation. + */ + void applyClassicalOperation(Operation* op); }; /// Utility used by the pass analysis. From e353b7e8324176200ecd2226b71bcac15aa38e9e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 26 Aug 2026 10:40:22 +0200 Subject: [PATCH 08/55] :construction: Propagate unitary operations --- .../Optimizations/ConstantPropagation.cpp | 55 ++++++------------ .../ConstantPropagationLattice.cpp | 58 +++++++++++++++---- .../ConstantPropagationLattice.hpp | 30 ++++++---- 3 files changed, 84 insertions(+), 59 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 6dbad9549a..b8a1a5866b 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -92,8 +92,7 @@ class HybridConstantPropagationAnalysis } if (const auto unitary = dyn_cast(op)) { - visitUnitaryOp(op, unitary, input, results); - return success(); + return visitUnitaryOp(unitary, input, results); } if (const auto ctrlOp = dyn_cast(op)) { @@ -102,8 +101,7 @@ class HybridConstantPropagationAnalysis } if (llvm::all_of(op->getResultTypes(), isClassicalType)) { - visitClassicalOp(op, input, results); - return success(); + return visitClassicalOp(op, input, results); } visitFallback(op, input, results); @@ -133,47 +131,30 @@ class HybridConstantPropagationAnalysis return result; } - void setAllResults(const ArrayRef results, - const HybridStateSet& state) { - for (auto* res : results) { - auto* lat = asHybrid(res); - propagateIfChanged(lat, lat->join(state)); + LogicalResult visitClassicalOp(Operation* op, HybridStateSet& input, + ArrayRef results) { + if (input.applyClassicalOperation(op).failed()) { + return failure(); } - } - - void visitClassicalOp(Operation* op, HybridStateSet& input, - ArrayRef results) { - input.applyClassicalOperation(op); for (auto [resLattice, resValue] : llvm::zip(results, op->getResults())) { const auto newLattice = HybridStateLattice(resValue, input); propagateIfChanged(resLattice, resLattice->join(newLattice)); } + return success(); } - void visitUnitaryOp(Operation* op, qco::UnitaryOpInterface unitary, - const HybridStateSet& input, - ArrayRef results) { - HybridStateSet output; - output.states.clear(); - - SmallVector inputs(op->getOperands().begin(), - op->getOperands().end()); - SmallVector outputsV(op->getResults().begin(), - op->getResults().end()); - UnitaryMatrix matrix = unitary.getUnitaryMatrix(); - - for (const HybridState& state : input.states) { - HybridState next = state; - if (failed(next.quantumState.applyUnitary(inputs, matrix, outputsV, - maxTrackedAmplitudes))) { - for (Value out : outputsV) - next.quantumState.markTop(out); - } - output.addState(std::move(next)); + LogicalResult visitUnitaryOp(UnitaryOpInterface unitary, + HybridStateSet& input, + ArrayRef results) { + if (input.applyUnitaryOperation(&unitary).failed()) { + return failure(); } - - output.enforceMaxStates(maxTrackedStates); - setAllResults(results, output); + for (auto [resLattice, resValue] : + llvm::zip(results, unitary->getResults())) { + const auto newLattice = HybridStateLattice(resValue, input); + propagateIfChanged(resLattice, resLattice->join(newLattice)); + } + return success(); } void visitMeasureOp(qco::MeasureOp op, const HybridStateSet& input, diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp index 94a4eac1a6..2498f1e5b2 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp @@ -238,8 +238,8 @@ void QuantumState::applyMatrix1Q(const Value input, const Value output, for (const auto& it : grouped) { Complex in0 = it.second[0]; Complex in1 = it.second[1]; - Complex out0 = matrix[0][0] * in0 + matrix[0][1] * in1; - Complex out1 = matrix[1][0] * in0 + matrix[1][1] * in1; + Complex out0 = matrix.data[0] * in0 + matrix.data[1] * in1; + Complex out1 = matrix.data[2] * in0 + matrix.data[3] * in1; if (out0 != Complex(0.0, 0.0)) { result[insertBit(it.first, idx, false)] += out0; } @@ -299,7 +299,7 @@ void QuantumState::applyMatrix2Q(const Value input0, const Value input1, for (unsigned row = 0; row < 4; ++row) { Complex sum(0.0, 0.0); for (unsigned col = 0; col < 4; ++col) { - sum += matrix[row][col] * it.second[col]; + sum += matrix.data[row * 4 + col] * it.second[col]; } outVec[row] = sum; } @@ -308,8 +308,8 @@ void QuantumState::applyMatrix2Q(const Value input0, const Value input1, if (outVec[row] == Complex(0.0, 0.0)) { continue; } - bool b0 = (row & 1u) != 0u; - bool b1 = (row & 2u) != 0u; + const bool b0 = (row & 1u) != 0u; + const bool b1 = (row & 2u) != 0u; uint64_t basis = insertBit(insertBit(it.first, idx0, b0), idx1, b1); result[basis] += outVec[row]; } @@ -372,8 +372,7 @@ LogicalResult QuantumState::applyUnitary(const ArrayRef inputs, } std::unordered_map> -QuantumState::measure(const Value inQubit, const Value outQubit, - MLIRContext* ctx) { +QuantumState::measure(const Value inQubit, const Value outQubit) { if (isTop) { forwardQubit(inQubit, outQubit); @@ -384,7 +383,7 @@ QuantumState::measure(const Value inQubit, const Value outQubit, if (!idxOpt) { llvm::report_fatal_error("Called measure on a qubit not in the state"); } - unsigned idx = *idxOpt; + const unsigned idx = *idxOpt; double prob0 = 0.0; double prob1 = 0.0; @@ -517,7 +516,7 @@ HybridState HybridState::mergeStates(const HybridState& that) const { return result; } -void HybridState::applyClassicalOperation(Operation* op) { +LogicalResult HybridState::applyClassicalOperation(Operation* op) { SmallVector operandAttrs; operandAttrs.reserve(op->getNumOperands()); for (const Value operand : op->getOperands()) { @@ -525,6 +524,7 @@ void HybridState::applyClassicalOperation(Operation* op) { if (!attr) { llvm::report_fatal_error( "Called operation on a classical value not in the state"); + return failure(); } operandAttrs.push_back(*attr); } @@ -536,9 +536,31 @@ void HybridState::applyClassicalOperation(Operation* op) { setClassical(val, attr); } } - return; + return success(); } llvm::report_fatal_error("Error while propagating classical operation."); + return failure(); +} + +LogicalResult HybridState::applyUnitaryOperation(UnitaryOpInterface* op) const { + const SmallVector inputs = op->getInputTargets(); + const SmallVector outputs = op->getOutputTargets(); + UnitaryMatrix matrix; + + if (inputs.size() == 1) { + auto matrix2 = Matrix2x2(); + op->getUnitaryMatrix2x2(matrix2); + matrix = matrix2; + } else if (inputs.size() == 2) { + auto matrix4 = Matrix4x4(); + op->getUnitaryMatrix4x4(matrix4); + matrix = matrix4; + } else { + llvm::report_fatal_error( + "Constant propagation needs gates with one or two targets."); + return failure(); + } + return quantumState->applyUnitary(inputs, matrix, outputs); } //===----------------------------------------------------------------------===// @@ -651,10 +673,22 @@ bool HybridStateSet::isAlwaysTrue(const Value v) const { return true; } -void HybridStateSet::applyClassicalOperation(Operation* op) { +LogicalResult HybridStateSet::applyClassicalOperation(Operation* op) { for (HybridState& state : states) { - state.applyClassicalOperation(op); + if (state.applyClassicalOperation(op).failed()) { + return failure(); + } + } + return success(); +} + +LogicalResult HybridStateSet::applyUnitaryOperation(UnitaryOpInterface* op) { + for (HybridState& state : states) { + if (state.applyUnitaryOperation(op).failed()) { + return failure(); + } } + return success(); } } // namespace mlir::qco \ No newline at end of file diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp index 23cd319e95..64c9dd244a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp @@ -10,6 +10,8 @@ #pragma once +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" + #include #include #include @@ -25,8 +27,6 @@ namespace mlir::qco { using Complex = std::complex; -using Matrix2x2 = std::array, 2>; -using Matrix4x4 = std::array, 4>; using UnitaryMatrix = std::variant; /** @@ -171,13 +171,11 @@ struct QuantumState { * * @param inQubit The qubit to be measured. * @param outQubit The qubit value after the measurement. - * @param ctx The MLIRContext used for type creation and attribute - * propagation. * @return A map of possible successor states paired with their probability. * The keys are the measurement results. */ std::unordered_map> - measure(Value inQubit, Value outQubit, MLIRContext* ctx); + measure(Value inQubit, Value outQubit); }; /** @@ -299,7 +297,14 @@ struct HybridState { * * @param op The classical operation to apply. */ - void applyClassicalOperation(Operation* op); + LogicalResult applyClassicalOperation(Operation* op); + + /** + * Applies an (uncontrolled) unitary gate on all HybridStates of the set. + * + * @param op The operation to apply. + */ + LogicalResult applyUnitaryOperation(UnitaryOpInterface* op) const; }; /** @@ -383,13 +388,18 @@ struct HybridStateSet { bool isAlwaysTrue(Value v) const; /** - * Applies a classical operation on all HybridStates of the set and returns a - * new set. + * Applies a classical operation on all HybridStates of the set. + * + * @param op The operation to apply. + */ + LogicalResult applyClassicalOperation(Operation* op); + + /** + * Applies an (uncontrolled) unitary gate on all HybridStates of the set. * * @param op The operation to apply. - * @return The set with applied operation. */ - void applyClassicalOperation(Operation* op); + LogicalResult applyUnitaryOperation(UnitaryOpInterface* op); }; /// Utility used by the pass analysis. From e2543216478cafc3eb87f4a6b9245df9b3df83b6 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 26 Aug 2026 10:47:42 +0200 Subject: [PATCH 09/55] :boom: Removed some code for now --- .../Optimizations/ConstantPropagation.cpp | 151 +++++++++--------- 1 file changed, 73 insertions(+), 78 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index b8a1a5866b..173d72bc75 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -157,94 +157,89 @@ class HybridConstantPropagationAnalysis return success(); } - void visitMeasureOp(qco::MeasureOp op, const HybridStateSet& input, + void visitMeasureOp(MeasureOp op, const HybridStateSet& input, ArrayRef results) { - HybridStateSet output; - output.states.clear(); - - Value inQubit = op.getOperand(); - Value outQubit = op.getResult(0); - Value outClassical = op.getResult(1); - - for (const HybridState& state : input.states) { - auto successors = - state.quantumState.measure(inQubit, outQubit, op.getContext()); - if (successors.empty()) { - HybridState next = state; - next.quantumState.markTop(inQubit); - output.addState(std::move(next)); - continue; - } - - const QuantumComponent* component = - state.quantumState.getComponent(inQubit); - double prob0 = 0.0; - double prob1 = 0.0; - if (component && !component->isTop) { - auto idx = component->indexOf(inQubit); - if (idx) { - for (const auto& it : component->amplitudes) { - double p = std::norm(it.second); - if (((it.first >> *idx) & 1ULL) == 0ULL) - prob0 += p; - else - prob1 += p; - } - } - } - - for (auto& succ : successors) { - HybridState next = state; - next.quantumState = std::move(succ.first); - next.setClassical(outClassical, succ.second); - if (isZeroAttribute(succ.second)) - next.probability *= prob0; - else if (isTrueAttribute(succ.second)) - next.probability *= prob1; - output.addState(std::move(next)); - } - } - - output.enforceMaxStates(maxTrackedStates); - setAllResults(results, output); + // HybridStateSet output; + // output.states.clear(); + // + // Value inQubit = op.getOperand(); + // Value outQubit = op.getResult(0); + // Value outClassical = op.getResult(1); + // + // for (const HybridState& state : input.states) { + // auto successors = + // state.quantumState.measure(inQubit, outQubit, op.getContext()); + // if (successors.empty()) { + // HybridState next = state; + // next.quantumState.markTop(inQubit); + // output.addState(std::move(next)); + // continue; + // } + // + // const QuantumComponent* component = + // state.quantumState.getComponent(inQubit); + // double prob0 = 0.0; + // double prob1 = 0.0; + // if (component && !component->isTop) { + // auto idx = component->indexOf(inQubit); + // if (idx) { + // for (const auto& it : component->amplitudes) { + // double p = std::norm(it.second); + // if (((it.first >> *idx) & 1ULL) == 0ULL) + // prob0 += p; + // else + // prob1 += p; + // } + // } + // } + // + // for (auto& succ : successors) { + // HybridState next = state; + // next.quantumState = std::move(succ.first); + // next.setClassical(outClassical, succ.second); + // if (isZeroAttribute(succ.second)) + // next.probability *= prob0; + // else if (isTrueAttribute(succ.second)) + // next.probability *= prob1; + // output.addState(std::move(next)); + // } + // } + // + // output.enforceMaxStates(maxTrackedStates); + // setAllResults(results, output); } - void visitCtrlOp(qco::CtrlOp op, const HybridStateSet& input, + void visitCtrlOp(CtrlOp op, const HybridStateSet& input, ArrayRef results) { // Forward target inputs conservatively. - HybridStateSet output; - output.states = input.states; - output.isTop = input.isTop; - - unsigned numResults = op->getNumResults(); - unsigned numOperands = op->getNumOperands(); - unsigned numControls = numOperands - numResults; - (void)numControls; - - for (HybridState& state : output.states) { - for (unsigned i = 0; i < numResults; ++i) { - Value in = op->getOperand(numOperands - numResults + i); - Value out = op->getResult(i); - state.quantumState.forwardQubit(in, out); - } - } - - output.enforceMaxStates(maxTrackedStates); - setAllResults(results, output); + // HybridStateSet output; + // output.states = input.states; + // output.isTop = input.isTop; + // + // unsigned numResults = op->getNumResults(); + // unsigned numOperands = op->getNumOperands(); + // unsigned numControls = numOperands - numResults; + // (void)numControls; + // + // for (HybridState& state : output.states) { + // for (unsigned i = 0; i < numResults; ++i) { + // Value in = op->getOperand(numOperands - numResults + i); + // Value out = op->getResult(i); + // state.quantumState.forwardQubit(in, out); + // } + // } + // + // output.enforceMaxStates(maxTrackedStates); + // setAllResults(results, output); } void visitFallback(Operation* op, const HybridStateSet& input, ArrayRef results) { - HybridStateSet output = input; - for (HybridState& state : output.states) { - for (Value res : op->getResults()) { - if (isQubitType(res.getType())) - state.quantumState.initializeQubit(res), - state.quantumState.markTop(res); - } + for (auto [resLattice, resValue] : + llvm::zip(results, op->getResults())) { + const auto newLattice = HybridStateLattice(resValue, input); + propagateIfChanged(resLattice, resLattice->join(newLattice)); } - output.enforceMaxStates(maxTrackedStates); - setAllResults(results, output); } }; From 2f42fea70153ac494ff9df7c64dfc1c1909d805c Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 26 Aug 2026 11:32:48 +0200 Subject: [PATCH 10/55] :construction: Added Constant Propagation Base pass --- .../mlir/Dialect/QCO/Transforms/Passes.td | 37 ++++++++++++ .../Optimizations/ConstantPropagation.cpp | 58 ++++++++----------- .../ConstantPropagationLattice.hpp | 1 - 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 897c05d01c..771642f05c 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -180,6 +180,43 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { // Optimization Passes //===----------------------------------------------------------------------===// +def ConstantPropagation : Pass<"constant-propagation", "mlir::ModuleOp"> { + let dependentDialects = ["mlir::qco::QCODialect"]; + let summary = + "This pass applies constant propagation to a circuit. It " + "assumes that all input qubits are |0>. It propagates the " + "state of the qubits up to a given complexity threshold and " + "removes gates which are superfluous considering the current " + "state."; + let description = [{ + This pass applies quantum constant propagation. This optimization routine assumes that the input qubits of the + circuits are |0>. It propagates the qubit states and the state of additional classical values through the circuit. + All quantum instructions are removed which are superfluous considering the current state. + + The qubit states and classical values are stored in hybrid states. Hybrid states are stored in a union table to + reduce the amount of complex amplitudes and classical values to track. There is a maximum number of non zero + amplitudes that is saved per union table entry. Additionally, there is also a maximum of hybrid states that can be + propagated. If the maximum number of amplitudes or the maximum number of hybrid states is exceeded, the propagated + state reaches top and no optimization routines are further applied. + + The applied optimization routines are: + + **General Control Reduction** + If a controlling qubit is always one, the control is removed. + If a controlling qubit is always zero, the complete gate is removed. + + }]; + let options = [Option<"maximumNonzeroAmplitudes", + "maximum-nonzero-amplitudes", "std::size_t", "4", + "The maximum number of non-zero amplitudes in the " + "tracked quantum states before reaching top.">, + Option<"maximumHybridStates", "maximum-hybrid-states", + "std::size_t", "4", + "The maximum number of hybrid states which have a " + "non-zero probability.">]; +} + + def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { let dependentDialects = ["mlir::qco::QCODialect"]; let summary = "This pass attempts to move Hadamard gates as far away from " diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 173d72bc75..fa3fea6e58 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -9,20 +9,20 @@ */ #include "ConstantPropagation/ConstantPropagationLattice.hpp" -#include "mlir/Dialect/QCO/IR/QCODialect.h" - -// Adjust these includes to your actual generated QCO interface/type headers. #include "mlir/Analysis/DataFlow/SparseAnalysis.h" -#include "mlir/Dialect/Arith/IR/Arith.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" -#include "mlir/IR/PatternMatch.h" -#include "mlir/Pass/Pass.h" -#include "mlir/Transforms/GreedyPatternRewriteDriver.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" + +#include +#include using namespace mlir; namespace mlir::qco { +#define GEN_PASS_DEF_CONSTANTPROPAGATION +#include "mlir/Dialect/QCO/Transforms/Passes.h.inc" + static unsigned int maxTrackedAmplitudes = 8; static unsigned int maxTrackedHybridStates = 4; @@ -62,22 +62,23 @@ class HybridStateLattice : public dataflow::AbstractSparseLattice { class HybridConstantPropagationAnalysis : public dataflow::SparseForwardDataFlowAnalysis { + +protected: + void setToEntryState(HybridStateLattice* lattice) override {} + public: explicit HybridConstantPropagationAnalysis(DataFlowSolver& solver) : SparseForwardDataFlowAnalysis(solver) {} void setToEntryState(dataflow::AbstractSparseLattice* lattice) override { - const auto value = lattice->getAnchor(); - auto* hybrid = llvm::cast(lattice); - // TODO: Propagate the values to the HybridState - // auto newLattice = HybridStateLattice(); - // propagateIfChanged(hybrid, hybrid->join(newLattice)); + const auto newLattice = HybridStateLattice(lattice->getAnchor()); + propagateIfChanged(lattice, lattice->join(newLattice)); } LogicalResult visitOperation(Operation* op, const ArrayRef operands, - ArrayRef results) override { + const ArrayRef results) override { HybridStateSet input = gatherInputState(operands); if (input.areStatesTop()) { @@ -235,8 +236,7 @@ class HybridConstantPropagationAnalysis void visitFallback(Operation* op, const HybridStateSet& input, ArrayRef results) { - for (auto [resLattice, resValue] : - llvm::zip(results, op->getResults())) { + for (auto [resLattice, resValue] : llvm::zip(results, op->getResults())) { const auto newLattice = HybridStateLattice(resValue, input); propagateIfChanged(resLattice, resLattice->join(newLattice)); } @@ -249,7 +249,7 @@ struct RemoveAlwaysZeroCtrlPattern : public OpRewritePattern { LogicalResult matchAndRewrite(qco::CtrlOp op, PatternRewriter& rewriter) const override { - for (Value ctrl : op.getConditions()) { + for (Value ctrl : op.getControlsIn()) { auto* state = solver.lookupState(ctrl); if (!state) return failure(); @@ -275,18 +275,17 @@ struct RemoveAlwaysZeroCtrlPattern : public OpRewritePattern { DataFlowSolver& solver; }; -struct ConstantPropagationPass - : public mlir::mqt::impl::ConstantPropagationPassBase< - ConstantPropagationPass> { +struct ConstantPropagation final + : impl::ConstantPropagationBase { + using ConstantPropagationBase::ConstantPropagationBase; + void runOnOperation() override { - ModuleOp module = getOperation(); + const auto op = getOperation(); DataFlowSolver solver; - solver.load(); - solver.load(maxTrackedAmplitudes, - maxTrackedStates); + solver.load(); - if (failed(solver.initializeAndRun(module))) { + if (failed(solver.initializeAndRun(op))) { signalPassFailure(); return; } @@ -294,17 +293,10 @@ struct ConstantPropagationPass RewritePatternSet patterns(&getContext()); patterns.add(&getContext(), solver); - if (failed(applyPatternsAndFoldGreedily(module, std::move(patterns)))) + if (failed(applyPatternsGreedily(op, std::move(patterns)))) { signalPassFailure(); + } } }; } // namespace mlir::qco - -std::unique_ptr mlir::mqt::createConstantPropagationPass() { - return std::make_unique(); -} - -void mlir::mqt::registerConstantPropagationPass() { - PassRegistration(); -} \ No newline at end of file diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp index 64c9dd244a..8f55029b95 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include From b87ed7440fced79bd2674f81197d653039602560 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 28 Aug 2026 07:40:50 +0200 Subject: [PATCH 11/55] :construction: Adapted structure for new approach, assisted-by Sonnet 5 via Claude Code --- .../mlir/Dialect/QCO/Transforms/Passes.td | 3 +- .../Optimizations/ConstantPropagation.cpp | 294 +------- .../ConstantPropagationLattice.cpp | 694 ------------------ .../ConstantPropagationLattice.hpp | 408 ---------- 4 files changed, 22 insertions(+), 1377 deletions(-) delete mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp delete mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 771642f05c..6c53de788b 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -181,7 +181,8 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { //===----------------------------------------------------------------------===// def ConstantPropagation : Pass<"constant-propagation", "mlir::ModuleOp"> { - let dependentDialects = ["mlir::qco::QCODialect"]; + let dependentDialects = ["mlir::qco::QCODialect", + "::mlir::arith::ArithDialect"]; let summary = "This pass applies constant propagation to a circuit. It " "assumes that all input qubits are |0>. It propagates the " diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index fa3fea6e58..80dde7a537 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -8,295 +8,41 @@ * Licensed under the MIT License */ -#include "ConstantPropagation/ConstantPropagationLattice.hpp" -#include "mlir/Analysis/DataFlow/SparseAnalysis.h" -#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" -#include -#include - -using namespace mlir; +#include namespace mlir::qco { #define GEN_PASS_DEF_CONSTANTPROPAGATION #include "mlir/Dialect/QCO/Transforms/Passes.h.inc" -static unsigned int maxTrackedAmplitudes = 8; -static unsigned int maxTrackedHybridStates = 4; - -static bool isQubitType(const Type ty) { return isa(ty); } - -static bool isClassicalType(const Type ty) { return ty.isIntOrIndexOrFloat(); } - -class HybridStateLattice : public dataflow::AbstractSparseLattice { -public: - using AbstractSparseLattice::AbstractSparseLattice; - - explicit HybridStateLattice(const Value anchor) - : AbstractSparseLattice(anchor), - value(HybridStateSet(maxTrackedAmplitudes, maxTrackedHybridStates)) {} - - explicit HybridStateLattice(const Value anchor, const HybridStateSet& state) - : AbstractSparseLattice(anchor), value(state) {} - - const HybridStateSet& getValue() const { return value; } - - ChangeResult join(const AbstractSparseLattice& rhs) override { - const auto rhsHS = llvm::cast(rhs); - const HybridStateSet old = value; - value.join(rhsHS.getValue()); - return old == value ? ChangeResult::NoChange : ChangeResult::Change; - } - - ChangeResult meet(const AbstractSparseLattice& rhs) override { - return join(rhs); - } - - void print(raw_ostream& os) const override; - -private: - HybridStateSet value; -}; - -class HybridConstantPropagationAnalysis - : public dataflow::SparseForwardDataFlowAnalysis { - -protected: - void setToEntryState(HybridStateLattice* lattice) override {} - -public: - explicit HybridConstantPropagationAnalysis(DataFlowSolver& solver) - : SparseForwardDataFlowAnalysis(solver) {} - - void setToEntryState(dataflow::AbstractSparseLattice* lattice) override { - const auto newLattice = HybridStateLattice(lattice->getAnchor()); - propagateIfChanged(lattice, lattice->join(newLattice)); - } - - LogicalResult - visitOperation(Operation* op, - const ArrayRef operands, - const ArrayRef results) override { - HybridStateSet input = gatherInputState(operands); - - if (input.areStatesTop()) { - // TODO: Forward Qubits to results - // setAllResults(results, HybridStateSet::top()); - return success(); - } - - if (const auto measureOp = dyn_cast(op)) { - visitMeasureOp(measureOp, input, results); - return success(); - } - - if (const auto unitary = dyn_cast(op)) { - return visitUnitaryOp(unitary, input, results); - } - - if (const auto ctrlOp = dyn_cast(op)) { - visitCtrlOp(ctrlOp, input, results); - return success(); - } - - if (llvm::all_of(op->getResultTypes(), isClassicalType)) { - return visitClassicalOp(op, input, results); - } - - visitFallback(op, input, results); - return success(); - } - -private: - static HybridStateLattice* asHybrid(dataflow::AbstractSparseLattice* l) { - return llvm::cast(l); - } - - static const HybridStateLattice* - asHybrid(const dataflow::AbstractSparseLattice* l) { - return llvm::cast(l); - } - - static HybridStateSet - gatherInputState(const ArrayRef operands) { - if (operands.size() == 1) { - return operands[0]->getValue(); - } - - auto result = operands[0]->getValue().mergeStates(operands[1]->getValue()); - for (unsigned int i = 2; i < operands.size(); ++i) { - result = result.mergeStates(operands[i]->getValue()); - } - return result; - } - - LogicalResult visitClassicalOp(Operation* op, HybridStateSet& input, - ArrayRef results) { - if (input.applyClassicalOperation(op).failed()) { - return failure(); - } - for (auto [resLattice, resValue] : llvm::zip(results, op->getResults())) { - const auto newLattice = HybridStateLattice(resValue, input); - propagateIfChanged(resLattice, resLattice->join(newLattice)); - } - return success(); - } - - LogicalResult visitUnitaryOp(UnitaryOpInterface unitary, - HybridStateSet& input, - ArrayRef results) { - if (input.applyUnitaryOperation(&unitary).failed()) { - return failure(); - } - for (auto [resLattice, resValue] : - llvm::zip(results, unitary->getResults())) { - const auto newLattice = HybridStateLattice(resValue, input); - propagateIfChanged(resLattice, resLattice->join(newLattice)); - } - return success(); - } - - void visitMeasureOp(MeasureOp op, const HybridStateSet& input, - ArrayRef results) { - // HybridStateSet output; - // output.states.clear(); - // - // Value inQubit = op.getOperand(); - // Value outQubit = op.getResult(0); - // Value outClassical = op.getResult(1); - // - // for (const HybridState& state : input.states) { - // auto successors = - // state.quantumState.measure(inQubit, outQubit, op.getContext()); - // if (successors.empty()) { - // HybridState next = state; - // next.quantumState.markTop(inQubit); - // output.addState(std::move(next)); - // continue; - // } - // - // const QuantumComponent* component = - // state.quantumState.getComponent(inQubit); - // double prob0 = 0.0; - // double prob1 = 0.0; - // if (component && !component->isTop) { - // auto idx = component->indexOf(inQubit); - // if (idx) { - // for (const auto& it : component->amplitudes) { - // double p = std::norm(it.second); - // if (((it.first >> *idx) & 1ULL) == 0ULL) - // prob0 += p; - // else - // prob1 += p; - // } - // } - // } - // - // for (auto& succ : successors) { - // HybridState next = state; - // next.quantumState = std::move(succ.first); - // next.setClassical(outClassical, succ.second); - // if (isZeroAttribute(succ.second)) - // next.probability *= prob0; - // else if (isTrueAttribute(succ.second)) - // next.probability *= prob1; - // output.addState(std::move(next)); - // } - // } - // - // output.enforceMaxStates(maxTrackedStates); - // setAllResults(results, output); - } - - void visitCtrlOp(CtrlOp op, const HybridStateSet& input, - ArrayRef results) { - // Forward target inputs conservatively. - // HybridStateSet output; - // output.states = input.states; - // output.isTop = input.isTop; - // - // unsigned numResults = op->getNumResults(); - // unsigned numOperands = op->getNumOperands(); - // unsigned numControls = numOperands - numResults; - // (void)numControls; - // - // for (HybridState& state : output.states) { - // for (unsigned i = 0; i < numResults; ++i) { - // Value in = op->getOperand(numOperands - numResults + i); - // Value out = op->getResult(i); - // state.quantumState.forwardQubit(in, out); - // } - // } - // - // output.enforceMaxStates(maxTrackedStates); - // setAllResults(results, output); - } - - void visitFallback(Operation* op, const HybridStateSet& input, - ArrayRef results) { - for (auto [resLattice, resValue] : llvm::zip(results, op->getResults())) { - const auto newLattice = HybridStateLattice(resValue, input); - propagateIfChanged(resLattice, resLattice->join(newLattice)); - } - } -}; - -struct RemoveAlwaysZeroCtrlPattern : public OpRewritePattern { - RemoveAlwaysZeroCtrlPattern(MLIRContext* ctx, DataFlowSolver& solver) - : OpRewritePattern(ctx), solver(solver) {} - - LogicalResult matchAndRewrite(qco::CtrlOp op, - PatternRewriter& rewriter) const override { - for (Value ctrl : op.getControlsIn()) { - auto* state = solver.lookupState(ctrl); - if (!state) - return failure(); - if (!state->getValue().isAlwaysFalse(ctrl)) - continue; - - unsigned numResults = op->getNumResults(); - unsigned numOperands = op->getNumOperands(); - if (numOperands < numResults) - return failure(); - - SmallVector replacements; - for (unsigned i = 0; i < numResults; ++i) - replacements.push_back(op->getOperand(numOperands - numResults + i)); - - rewriter.replaceOp(op, replacements); - return success(); - } - return failure(); - } - -private: - DataFlowSolver& solver; -}; +namespace { +/** + * @brief Quantum constant propagation. + * + * Assumes all input qubits start in |0>, propagates the quantum/classical state + * through the circuit up to a complexity threshold, and removes operations that + * are superfluous given that state. + * + * The analysis is done as an MLIR `DenseForwardDataFlowAnalysis` over a + * `UnionTable` lattice, with a separate rewrite phase driven by the computed + * facts. + */ struct ConstantPropagation final : impl::ConstantPropagationBase { using ConstantPropagationBase::ConstantPropagationBase; void runOnOperation() override { - const auto op = getOperation(); - - DataFlowSolver solver; - solver.load(); - - if (failed(solver.initializeAndRun(op))) { - signalPassFailure(); - return; - } - - RewritePatternSet patterns(&getContext()); - patterns.add(&getContext(), solver); - - if (failed(applyPatternsGreedily(op, std::move(patterns)))) { - signalPassFailure(); - } + // TODO(mlir/constant-propagation-v2): implement in stages -- + // 1. QuantumState, 2. UnionTable/HybridState, 3. + // ConstantPropagationAnalysis, + // 4. Decisions + Rewriter + driver, 5. pass-level tests. } }; +} // namespace + } // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp deleted file mode 100644 index 2498f1e5b2..0000000000 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.cpp +++ /dev/null @@ -1,694 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ConstantPropagationLattice.hpp" - -#include -#include -#include - -#include -#include - -using namespace mlir; -namespace mlir::qco { - -/** - * Removes the bit at a specified position in a 64-bit unsigned integer. - * The resulting value is effectively the input value with the bit at the given - * position cleared or removed, shifting higher bits down by one position. - * - * @param value The 64-bit unsigned integer from which to clear a bit. - * @param pos The zero-based position of the bit to remove. - * Must be less than 64; undefined behavior if out of bounds. - * @return A new 64-bit unsigned integer with the specified bit removed. - */ -static uint64_t clearBit(const uint64_t value, const unsigned pos) { - const uint64_t lowMask = pos == 0 ? 0 : (uint64_t{1} << pos) - 1; - const uint64_t low = value & lowMask; - const uint64_t high = value >> (pos + 1); - return low | high << pos; -} - -/** - * Inserts a bit at a specified position in a 64-bit unsigned integer. - * The resulting value includes the new bit at the given position, with - * all higher bits shifted up by one position to make room for the insertion. - * - * @param value The 64-bit unsigned integer where the bit will be inserted. - * @param pos The zero-based position at which the bit is to be inserted. - * Must be less than 64; undefined behavior if out of bounds. - * @param bit The value of the bit to be inserted (true for 1, false for 0). - * @return A new 64-bit unsigned integer with the specified bit inserted. - */ -static uint64_t insertBit(const uint64_t value, const unsigned pos, - const bool bit) { - const uint64_t lowMask = pos == 0 ? 0 : (uint64_t{1} << pos) - 1; - const uint64_t low = value & lowMask; - const uint64_t high = value >> pos; - return low | static_cast(bit) << pos | high << (pos + 1); -} - -bool isZeroAttribute(const Attribute attr) { - if (!attr) { - return false; - } - if (const auto intAttr = dyn_cast(attr)) { - return intAttr.getValue().isZero(); - } - if (const auto floatAttr = dyn_cast(attr)) { - return floatAttr.getValue().isZero(); - } - if (const auto boolAttr = dyn_cast(attr)) { - return !boolAttr.getValue(); - } - return false; -} - -bool isTrueAttribute(const Attribute attr) { - if (!attr) { - return false; - } - if (const auto intAttr = dyn_cast(attr)) { - return !intAttr.getValue().isZero(); - } - if (const auto floatAttr = dyn_cast(attr)) { - return !floatAttr.getValue().isZero(); - } - if (const auto boolAttr = dyn_cast(attr)) { - return boolAttr.getValue(); - } - return false; -} - -//===----------------------------------------------------------------------===// -// QuantumState -//===----------------------------------------------------------------------===// - -QuantumState -QuantumState::singletonZero(const unsigned int maxTrackedAmplitudes, - const Value qubit) { - QuantumState c(std::min(maxTrackedAmplitudes, 64u)); - c.qubits.push_back(qubit); - c.amplitudes[0] = Complex(1.0, 0.0); - return c; -} - -bool QuantumState::operator==(const QuantumState& other) const { - if (isTop != other.isTop) { - return false; - } - if (maxTrackedAmplitudes != other.maxTrackedAmplitudes) { - return false; - } - if (qubits.size() != other.qubits.size()) { - return false; - } - for (auto [a, b] : llvm::zip(qubits, other.qubits)) { - if (a != b) { - return false; - } - } - if (isTop) { - return other.isTop; - } - if (amplitudes.size() != other.amplitudes.size()) { - return false; - } - for (const auto& it : amplitudes) { - auto found = other.amplitudes.find(it.first); - if (found == other.amplitudes.end()) { - return false; - } - if (found->second != it.second) { - return false; - } - } - return true; -} - -bool QuantumState::contains(const Value v) const { - return llvm::is_contained(qubits, v); -} - -std::optional QuantumState::indexOf(const Value v) const { - for (auto [idx, q] : llvm::enumerate(qubits)) { - if (q == v) { - return idx; - } - } - return {}; -} - -bool QuantumState::isAlwaysZero(const Value q) const { - if (isTop) { - return false; - } - const auto idx = indexOf(q); - if (!idx) { - return false; - } - return std::ranges::all_of(amplitudes, [&](const auto& it) { - return (it.first >> *idx & 1ULL) == 0ULL; - }); -} - -bool QuantumState::isAlwaysOne(const Value q) const { - if (isTop) { - return false; - } - const auto idx = indexOf(q); - if (!idx) { - return false; - } - return std::ranges::all_of(amplitudes, [&](const auto& it) { - return (it.first >> *idx & 1ULL) != 0ULL; - }); -} - -void QuantumState::markTop() { - isTop = true; - amplitudes.clear(); -} - -void QuantumState::forwardQubit(const Value from, const Value to) { - const auto id = indexOf(from); - if (!id) { - return; - } - qubits[id.value()] = to; -} - -QuantumState QuantumState::tensorProduct(const QuantumState& that) { - QuantumState result(maxTrackedAmplitudes); - result.qubits.append(qubits.begin(), qubits.end()); - result.qubits.append(that.qubits.begin(), that.qubits.end()); - - if (isTop || that.isTop || - amplitudes.size() * that.amplitudes.size() > maxTrackedAmplitudes) { - result.isTop = true; - return result; - } - - for (const auto& itA : amplitudes) { - for (const auto& itB : that.amplitudes) { - uint64_t basis = itA.first | itB.first << qubits.size(); - result.amplitudes[basis] += itA.second * itB.second; - } - } - return result; -} -bool QuantumState::isStateTop() const { return isTop; } - -void QuantumState::applyMatrix1Q(const Value input, const Value output, - const Matrix2x2& matrix) { - if (isTop) { - for (Value& q : qubits) { - if (q == input) { - q = output; - break; - } - } - return; - } - - const auto idxOpt = indexOf(input); - if (!idxOpt) { - return; - } - const unsigned idx = *idxOpt; - - llvm::DenseMap result; - - // Group amplitudes by all bits except the target bit. - llvm::DenseMap> grouped; - for (const auto& it : amplitudes) { - uint64_t reduced = clearBit(it.first, idx); - const bool bit = (it.first >> idx & 1ULL) != 0ULL; - grouped[reduced][bit ? 1 : 0] += it.second; - } - - for (const auto& it : grouped) { - Complex in0 = it.second[0]; - Complex in1 = it.second[1]; - Complex out0 = matrix.data[0] * in0 + matrix.data[1] * in1; - Complex out1 = matrix.data[2] * in0 + matrix.data[3] * in1; - if (out0 != Complex(0.0, 0.0)) { - result[insertBit(it.first, idx, false)] += out0; - } - if (out1 != Complex(0.0, 0.0)) { - result[insertBit(it.first, idx, true)] += out1; - } - } - - amplitudes = std::move(result); - for (Value& q : qubits) { - if (q == input) { - q = output; - break; - } - } - if (amplitudes.size() > maxTrackedAmplitudes) { - amplitudes.clear(); - isTop = true; - } -} - -void QuantumState::applyMatrix2Q(const Value input0, const Value input1, - const Value output0, const Value output1, - const Matrix4x4& matrix) { - if (isTop) { - for (Value& q : qubits) { - if (q == input0) { - q = output0; - } else if (q == input1) { - q = output1; - } - } - return; - } - - const auto idx0Opt = indexOf(input0); - const auto idx1Opt = indexOf(input1); - if (!idx0Opt || !idx1Opt || *idx0Opt == *idx1Opt) { - return; - } - const unsigned idx0 = *idx0Opt; - const unsigned idx1 = *idx1Opt; - - llvm::DenseMap> grouped; - for (const auto& it : amplitudes) { - const bool b0 = (it.first >> idx0 & 1ULL) != 0ULL; - const bool b1 = (it.first >> idx1 & 1ULL) != 0ULL; - const unsigned local = static_cast(b0) | static_cast(b1) - << 1u; - uint64_t reduced = clearBit(clearBit(it.first, idx1), idx0); - grouped[reduced][local] += it.second; - } - - llvm::DenseMap result; - for (const auto& it : grouped) { - std::array outVec{}; - for (unsigned row = 0; row < 4; ++row) { - Complex sum(0.0, 0.0); - for (unsigned col = 0; col < 4; ++col) { - sum += matrix.data[row * 4 + col] * it.second[col]; - } - outVec[row] = sum; - } - - for (unsigned row = 0; row < 4; ++row) { - if (outVec[row] == Complex(0.0, 0.0)) { - continue; - } - const bool b0 = (row & 1u) != 0u; - const bool b1 = (row & 2u) != 0u; - uint64_t basis = insertBit(insertBit(it.first, idx0, b0), idx1, b1); - result[basis] += outVec[row]; - } - } - - amplitudes = std::move(result); - for (Value& q : qubits) { - if (q == input0) { - q = output0; - } else if (q == input1) { - q = output1; - } - } - if (amplitudes.size() > maxTrackedAmplitudes) { - amplitudes.clear(); - isTop = true; - } -} - -LogicalResult QuantumState::applyUnitary(const ArrayRef inputs, - const UnitaryMatrix& matrix, - const ArrayRef outputs) { - - if (inputs.size() != outputs.size()) { - return failure(); - } - if (inputs.empty() || inputs.size() > 2) { - return failure(); - } - - for (const auto& in : inputs) { - if (!indexOf(in)) { - return failure(); - } - } - - if (isTop) { - for (const auto& [in, out] : llvm::zip(inputs, outputs)) { - forwardQubit(in, out); - } - return success(); - } - - if (inputs.size() == 1) { - if (!std::holds_alternative(matrix)) { - return failure(); - } - applyMatrix1Q(inputs[0], outputs[0], std::get(matrix)); - - return success(); - } - - if (!std::holds_alternative(matrix)) { - return failure(); - } - applyMatrix2Q(inputs[0], inputs[1], outputs[0], outputs[1], - std::get(matrix)); - - return success(); -} - -std::unordered_map> -QuantumState::measure(const Value inQubit, const Value outQubit) { - - if (isTop) { - forwardQubit(inQubit, outQubit); - return {}; - } - - const auto idxOpt = indexOf(inQubit); - if (!idxOpt) { - llvm::report_fatal_error("Called measure on a qubit not in the state"); - } - const unsigned idx = *idxOpt; - - double prob0 = 0.0; - double prob1 = 0.0; - for (const auto& it : amplitudes) { - const double p = std::norm(it.second); - if ((it.first >> idx & 1ULL) == 0ULL) { - prob0 += p; - } else { - prob1 += p; - } - } - - auto makeSuccessor = [&](const bool bit, const double probability) { - const double scaleFactor = 1.0 / std::sqrt(probability); - auto c = QuantumState(maxTrackedAmplitudes); - for (const auto& it : amplitudes) { - const bool curBit = (it.first >> idx & 1ULL) != 0ULL; - if (curBit == bit) { - c.amplitudes[it.first] = it.second * scaleFactor; - } - } - - for (Value& q : qubits) { - if (q == inQubit) { - c.qubits.push_back(outQubit); - } else { - c.qubits.push_back(q); - } - } - - return std::pair{std::move(c), probability}; - }; - - std::unordered_map> result; - if (std::norm(prob0 - 0.0) >= 1e-10) { - result.emplace(0, makeSuccessor(false, prob0)); - } - if (std::norm(prob1 - 0.0) >= 1e-10) { - result.emplace(1, makeSuccessor(true, prob1)); - } - return result; -} - -//===----------------------------------------------------------------------===// -// HybridState -//===----------------------------------------------------------------------===// - -bool HybridState::operator==(const HybridState& that) const { - if (isTop || that.isTop) { - return isTop == that.isTop; - } - if (std::norm(probability - that.probability) >= 1e-10) { - return false; - } - if (maxTrackedAmplitudes != that.maxTrackedAmplitudes) { - return false; - } - if (quantumState != that.quantumState) { - return false; - } - if (classicalValues.size() != that.classicalValues.size()) { - return false; - } - for (const auto& it : classicalValues) { - auto found = that.classicalValues.find(it.first); - if (found == that.classicalValues.end()) { - return false; - } - if (it.second != found->second) { - return false; - } - } - return true; -} - -std::optional HybridState::getClassical(const Value v) const { - const auto it = classicalValues.find(v); - if (it == classicalValues.end()) { - return {}; - } - return it->second; -} - -void HybridState::setClassical(const Value v, const Attribute attr) { - classicalValues[v] = attr; -} - -bool HybridState::contains(const Value v) const { - if (getClassical(v).has_value()) { - return true; - } - return quantumState->contains(v); -} -bool HybridState::isStateTop() const { return isTop; } - -bool HybridState::isAlwaysFalse(const Value v) const { - const auto attr = getClassical(v); - if (attr.has_value()) { - return isZeroAttribute(attr.value()); - } - return quantumState->isAlwaysZero(v); -} - -bool HybridState::isAlwaysTrue(const Value v) const { - const auto attr = getClassical(v); - if (attr.has_value()) { - return isTrueAttribute(attr.value()); - } - return quantumState->isAlwaysOne(v); -} - -HybridState HybridState::mergeStates(const HybridState& that) const { - auto result = HybridState(maxTrackedAmplitudes); - result.probability = probability * that.probability; - - if (isTop || that.isTop) { - result.isTop = true; - return result; - } - result.classicalValues = classicalValues; - for (const auto& [v, a] : that.classicalValues) { - result.classicalValues[v] = a; - } - auto qS = quantumState->tensorProduct(*that.quantumState); - if (qS.isStateTop()) { - result.isTop = true; - return result; - } - result.quantumState = std::make_unique(qS); - return result; -} - -LogicalResult HybridState::applyClassicalOperation(Operation* op) { - SmallVector operandAttrs; - operandAttrs.reserve(op->getNumOperands()); - for (const Value operand : op->getOperands()) { - auto attr = getClassical(operand); - if (!attr) { - llvm::report_fatal_error( - "Called operation on a classical value not in the state"); - return failure(); - } - operandAttrs.push_back(*attr); - } - - SmallVector foldResults; - if (succeeded(op->fold(operandAttrs, foldResults))) { - for (const auto& [val, res] : llvm::zip(op->getResults(), foldResults)) { - if (auto attr = llvm::dyn_cast(res)) { - setClassical(val, attr); - } - } - return success(); - } - llvm::report_fatal_error("Error while propagating classical operation."); - return failure(); -} - -LogicalResult HybridState::applyUnitaryOperation(UnitaryOpInterface* op) const { - const SmallVector inputs = op->getInputTargets(); - const SmallVector outputs = op->getOutputTargets(); - UnitaryMatrix matrix; - - if (inputs.size() == 1) { - auto matrix2 = Matrix2x2(); - op->getUnitaryMatrix2x2(matrix2); - matrix = matrix2; - } else if (inputs.size() == 2) { - auto matrix4 = Matrix4x4(); - op->getUnitaryMatrix4x4(matrix4); - matrix = matrix4; - } else { - llvm::report_fatal_error( - "Constant propagation needs gates with one or two targets."); - return failure(); - } - return quantumState->applyUnitary(inputs, matrix, outputs); -} - -//===----------------------------------------------------------------------===// -// HybridStateSet -//===----------------------------------------------------------------------===// - -bool HybridStateSet::operator==(const HybridStateSet& that) const { - if (isTop || that.isTop) { - return isTop && that.isTop; - } - if (states.size() != that.states.size()) { - return false; - } - if (maxTrackedAmplitudes != that.maxTrackedAmplitudes) { - return false; - } - if (maxTrackedHybridStates != that.maxTrackedHybridStates) { - return false; - } - for (const auto& s : states) { - if (!llvm::is_contained(that.states, s)) { - return false; - } - } - return true; -} - -void HybridStateSet::addState(HybridState state) { - if (isTop) { - return; - } - if (maxTrackedHybridStates == states.size()) { - isTop = true; - states.clear(); - return; - } - states.push_back(std::move(state)); -} - -void HybridStateSet::join(const HybridStateSet& other) { - if (isTop || other.isTop) { - isTop = true; - states.clear(); - return; - } - llvm::append_range(states, other.states); -} - -void HybridStateSet::enforceMaxStates() { - if (isTop) { - return; - } - if (states.size() > maxTrackedHybridStates) { - isTop = true; - states.clear(); - } -} -HybridStateSet HybridStateSet::mergeStates(const HybridStateSet& that) const { - auto result = HybridStateSet(maxTrackedAmplitudes, maxTrackedHybridStates); - if (isTop || that.isTop) { - result.isTop = true; - result.states.clear(); - return result; - } - bool allTop = true; - SmallVector newStates; - for (const auto& s : states) { - for (const auto& thatS : that.states) { - const auto newState = s.mergeStates(thatS); - newStates.push_back(newState); - allTop &= newState.isStateTop(); - } - } - if (allTop) { - result.isTop = true; - result.states.clear(); - } else { - result.states = std::move(newStates); - } - return result; -} - -bool HybridStateSet::areStatesTop() const { return isTop; } - -bool HybridStateSet::isAlwaysFalse(const Value v) const { - if (isTop) { - return false; - } - for (const HybridState& state : states) { - if (state.contains(v)) { - if (!state.isAlwaysFalse(v)) { - return false; - } - } - } - return true; -} - -bool HybridStateSet::isAlwaysTrue(const Value v) const { - if (isTop) { - return false; - } - for (const HybridState& state : states) { - if (state.contains(v)) { - if (!state.isAlwaysTrue(v)) { - return false; - } - } - } - return true; -} - -LogicalResult HybridStateSet::applyClassicalOperation(Operation* op) { - for (HybridState& state : states) { - if (state.applyClassicalOperation(op).failed()) { - return failure(); - } - } - return success(); -} - -LogicalResult HybridStateSet::applyUnitaryOperation(UnitaryOpInterface* op) { - for (HybridState& state : states) { - if (state.applyUnitaryOperation(op).failed()) { - return failure(); - } - } - return success(); -} - -} // namespace mlir::qco \ No newline at end of file diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp deleted file mode 100644 index 8f55029b95..0000000000 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationLattice.hpp +++ /dev/null @@ -1,408 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#pragma once - -#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" - -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace mlir::qco { - -using Complex = std::complex; - -using UnitaryMatrix = std::variant; - -/** - * This struct represents a QuantumState. It contains of the amplitudes of - * different qubit states. It is top if the number of non-zero amplitudes - * exceeds a given maximum number of amplitudes. - */ -struct QuantumState { -private: - bool isTop = false; - unsigned int maxTrackedAmplitudes; - SmallVector qubits; - llvm::DenseMap amplitudes; - - /** - * Checks what the index of value v in the QuantumState is. - * - * @param v The value to look for. - * @return The index where v is in QuantumState. - */ - [[nodiscard("QuantumState::indexOf called but ignored.")]] - std::optional indexOf(Value v) const; - - /** - * Applies a 2x2 unitary matrix to a single qubit in the QuantumState. - * This operation updates the quantum state's amplitude distribution - * and the tracked qubit values. - * - * @param input The qubit identifier to which the matrix is applied. - * @param output The updated qubit identifier after transformation. - * @param matrix The 2x2 unitary matrix describing the transformation - * to be applied to the specified qubit. - */ - void applyMatrix1Q(Value input, Value output, const Matrix2x2& matrix); - - /** - * Applies a 4x4 unitary matrix to a single qubit in the QuantumState. - * This operation updates the quantum state's amplitude distribution - * and the tracked qubit values. - * - * @param input0 The first qubit identifier (corresponding to the lower index - * of the matrix) to which the matrix is applied. - * @param input1 The second qubit identifier (corresponding to the higher - * index of the matrix) to which the matrix is applied. - * @param output0 The updated first qubit identifier after transformation. - * @param output1 The updated second qubit identifier after transformation. - * @param matrix The 4x4 unitary matrix describing the transformation - * to be applied to the specified qubit. - */ - void applyMatrix2Q(Value input0, Value input1, Value output0, Value output1, - const Matrix4x4& matrix); - -public: - explicit QuantumState(const unsigned int maxTrackedAmplitudes) - : maxTrackedAmplitudes(maxTrackedAmplitudes) {} - - /** - * Create a new QuantumState that is initialized to |0>. - * - * @param maxTrackedAmplitudes The maximum number of amplitudes before - * QuantumStates becomes top. - * @param qubit The qubit value that the new quantum component should own. - * @return The newly created QuantumState. - */ - static QuantumState singletonZero(unsigned int maxTrackedAmplitudes, - Value qubit); - - bool operator==(const QuantumState& other) const; - - /** - * Check if the QuantumState contains a certain value. - * - * @param v The value to be checked. - * @return True if QuantumState contains v. - */ - [[nodiscard("QuantumState::contains called but ignored.")]] - bool contains(Value v) const; - - /** - * Computes the tensor product of this QuantumState with another QuantumState. - * The tensor product combines the qubits and amplitudes of both states, - * producing a new QuantumState that represents the combined quantum system. - * - * @param that The QuantumState to be combined with this QuantumState. - * @return A new QuantumState representing the tensor product of the two - * states. - */ - [[nodiscard("QuantumState::tensorProduct called but ignored.")]] - QuantumState tensorProduct(const QuantumState& that); - - [[nodiscard("QuantumState::isStateTop called but ignored.")]] - bool isStateTop() const; - - /** - * Check if a value is always zero. - * - * @param q The value to check for. - * @return True if the value is always zero. - */ - [[nodiscard("QuantumState::isAlwaysZero called but ignored.")]] - bool isAlwaysZero(Value q) const; - - /** - * Check if a value is always one. - * - * @param q The value to check for. - * @return True if the value is always one. - */ - [[nodiscard("QuantumState::isAlwaysOne called but ignored.")]] - bool isAlwaysOne(Value q) const; - - /** - * Put QuantumState to top. - */ - void markTop(); - - /** - * Changes qubit value one to another. - * - * @param from The original qubit value. - * @param to The new qubit value. - */ - void forwardQubit(Value from, Value to); - - /** - * Applies a unitary matrix to the QuantumState. - * - * @param inputs The values that the matrix is applied to. - * @param matrix The matrix that is applied to the QuantumState. - * @param outputs The values that replace the input values after matrix - * application. - * @return Whether the application was successful or not. - */ - LogicalResult applyUnitary(ArrayRef inputs, - const UnitaryMatrix& matrix, - ArrayRef outputs); - - /** - * Simulates a quantum measurement on a given qubit and updates the quantum - * state, producing possible successor states along with their classical - * outcomes. - * - * @param inQubit The qubit to be measured. - * @param outQubit The qubit value after the measurement. - * @return A map of possible successor states paired with their probability. - * The keys are the measurement results. - */ - std::unordered_map> - measure(Value inQubit, Value outQubit); -}; - -/** - * This struct represents a HybridState. It contains a QuantumState and - * classical values that are tracked alongside the QuantumState. It is top if - * the QuantumState is top. - */ -struct HybridState { -private: - llvm::DenseMap classicalValues; - std::unique_ptr quantumState; - unsigned int maxTrackedAmplitudes; - double probability = 1.0; - bool isTop = false; - -public: - explicit HybridState(const unsigned int maxTrackedAmplitudes, - const Value qubit) - : quantumState(std::make_unique( - QuantumState::singletonZero(maxTrackedAmplitudes, qubit))), - maxTrackedAmplitudes(maxTrackedAmplitudes) {} - - explicit HybridState(const unsigned int maxTrackedAmplitudes) - : quantumState(nullptr), maxTrackedAmplitudes(maxTrackedAmplitudes) {} - - HybridState(const HybridState& that) - : classicalValues(that.classicalValues), - quantumState(that.quantumState - ? std::make_unique(*that.quantumState) - : nullptr), - maxTrackedAmplitudes(that.maxTrackedAmplitudes), - probability(that.probability), isTop(that.isTop) {} - - HybridState& operator=(const HybridState& that) { - if (this == &that) { - return *this; - } - - classicalValues = that.classicalValues; - maxTrackedAmplitudes = that.maxTrackedAmplitudes; - probability = that.probability; - isTop = that.isTop; - - if (that.quantumState) { - quantumState = std::make_unique(*that.quantumState); - } else { - quantumState.reset(); - } - - return *this; - } - - bool operator==(const HybridState& that) const; - - /** - * Gets the attribute of a classical value if present. - * - * @param v The classical value to be checked. - * @return The Attribute of the classical value. - */ - [[nodiscard("HybridState::getClassical called but ignored.")]] std::optional< - Attribute> - getClassical(Value v) const; - - /** - * Sets the attribute of a classical value. If the value already has an - * attribute, it is overwritten. - * - * @param v The classical value to be set. - * @param attr The attribute to be set. - */ - void setClassical(Value v, Attribute attr); - - /** - * Checks whether a HybridState contains a value. The value can be a quantum - * or a classical one. - * - * @param v The value to check for. - * @return Whether the value is in the HybridState. - */ - bool contains(Value v) const; - - [[nodiscard("HybridState::isStateTop called but ignored.")]] - bool isStateTop() const; - - /** - * Checks if a value is always false, i.e., false/zero if it is a classical - * value and |0> if it is a quantum value. If the value is not part of the - * HybridState, the result is false. - * - * @param v The value to be checked. - * @return Whether the value is always false. - */ - [[nodiscard("HybridState::isAlwaysFalse called but ignored.")]] - bool isAlwaysFalse(Value v) const; - - /** - * Checks if a value is always false, i.e., true/nonzero if it is a classical - * value and |1> if it is a quantum value. If the value is not part of the - * HybridState, the result is false. - * - * @param v The value to be checked. - * @return Whether the value is always true. - */ - [[nodiscard("HybridState::isAlwaysTrue called but ignored.")]] - bool isAlwaysTrue(Value v) const; - - // TODO: Application of various operations - /** - * Merges two HybridStates which have QuantumState with different qubits. - * - * @param that The HybridStateSet to be merged with this. - * @return A new merged HybridState. - */ - HybridState mergeStates(const HybridState& that) const; - - /** - * Apply a classical state to the Hybrid state. - * - * @param op The classical operation to apply. - */ - LogicalResult applyClassicalOperation(Operation* op); - - /** - * Applies an (uncontrolled) unitary gate on all HybridStates of the set. - * - * @param op The operation to apply. - */ - LogicalResult applyUnitaryOperation(UnitaryOpInterface* op) const; -}; - -/** - * A set of all HybridStates in the current pass. It becomes top if either all - * HybridStates are top or if the number of HybridStates exceeds the maximum - * number. - */ -struct HybridStateSet { -private: - bool isTop = false; - unsigned int maxTrackedAmplitudes; - unsigned int maxTrackedHybridStates; - SmallVector states; - -public: - explicit HybridStateSet(const unsigned int maxTrackedAmplitudes, - const unsigned int maxTrackedHybridStates) - : maxTrackedAmplitudes(maxTrackedAmplitudes), - maxTrackedHybridStates(maxTrackedHybridStates) {} - - bool operator==(const HybridStateSet& that) const; - - /** - * Adds a hybridState to the set. - * - * @param state The HybridState to be added. - */ - void addState(HybridState state); - - // TODO: Application of various operations - - /** - * Joins HybridStateSets after branching. In that case, the new HybridStateSet - * is the union of the states in both old sets. If either of the old sets is - * top, the new state is top. - * - * @param other the HybridStateSet to join the current set with. - */ - void join(const HybridStateSet& other); - - /** - * Checks if there are too many HybridStates in the set. If the number of - * states exceeds the specified maximum, the state set is marked as "top", and - * all individual states tracked in the set are cleared. - */ - void enforceMaxStates(); - - /** - * Merges two HybridStateSets which have QuantumState with different qubits. - * Needs to be done before an operation entangles qubits from two - * HybridStates. - * - * @param that The HybridStateSet to be merged with this. - * @returns The new HybridStateSet. - */ - HybridStateSet mergeStates(const HybridStateSet& that) const; - - [[nodiscard("HybridStateSet::areStatesTop called but ignored.")]] - bool areStatesTop() const; - - /** - * Checks if a value is always false, i.e., false/zero if it is a classical - * value and |0> if it is a quantum value. If the value is not part of the - * HybridState, the result is false. - * - * @param v The value to be checked. - * @return Whether the value is always false. - */ - [[nodiscard("HybridStateSet::isAlwaysFalse called but ignored.")]] - bool isAlwaysFalse(Value v) const; - - /** - * Checks if a value is always false, i.e., true/nonzero if it is a classical - * value and |1> if it is a quantum value. If the value is not part of the - * HybridState, the result is false. - * - * @param v The value to be checked. - * @return Whether the value is always true. - */ - [[nodiscard("HybridStateSet::isAlwaysTrue called but ignored.")]] - bool isAlwaysTrue(Value v) const; - - /** - * Applies a classical operation on all HybridStates of the set. - * - * @param op The operation to apply. - */ - LogicalResult applyClassicalOperation(Operation* op); - - /** - * Applies an (uncontrolled) unitary gate on all HybridStates of the set. - * - * @param op The operation to apply. - */ - LogicalResult applyUnitaryOperation(UnitaryOpInterface* op); -}; - -/// Utility used by the pass analysis. -bool isZeroAttribute(Attribute attr); -bool isTrueAttribute(Attribute attr); - -} // namespace mlir::qco \ No newline at end of file From 21c9bd441ba6fa832a233eae61502a6ba1717bca Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 28 Aug 2026 19:05:39 +0200 Subject: [PATCH 12/55] :construction: Created QuantumState, assisted-by Sonnet 5 via Claude Code --- .../ConstantPropagation/QuantumState.cpp | 393 ++++++++++++++++ .../ConstantPropagation/QuantumState.hpp | 231 +++++++++ .../Transforms/Optimizations/CMakeLists.txt | 5 + .../ConstantPropagation/test_quantumState.cpp | 440 ++++++++++++++++++ 4 files changed, 1069 insertions(+) create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp create mode 100644 mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp new file mode 100644 index 0000000000..e68f3cabe5 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "QuantumState.hpp" + +#include "mlir/Dialect/QCO/Utils/Matrix.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qco { + +namespace { +/// Largest number of qubits a group can track (we use uint_64t as datatype). +constexpr unsigned MAX_GROUP_QUBITS = 63; +} // namespace + +QuantumState::QuantumState(const ArrayRef qubits, + const size_t maxNonzeroAmplitudes) + : maxNonzeroAmplitudes(maxNonzeroAmplitudes), + qubits(qubits.begin(), qubits.end()) { + if (qubits.size() > MAX_GROUP_QUBITS) { + markTop(); + return; + } + amplitudes[0] = Complex{1.0, 0.0}; +} + +QuantumState QuantumState::singletonZero(const Value qubit, + const size_t maxNonzeroAmplitudes) { + return {ArrayRef(qubit), maxNonzeroAmplitudes}; +} + +std::optional QuantumState::indexOf(const Value q) const { + for (const auto [idx, qubit] : llvm::enumerate(qubits)) { + if (qubit == q) { + return static_cast(idx); + } + } + return std::nullopt; +} + +uint64_t QuantumState::maskOf(const ArrayRef values) const { + uint64_t mask = 0; + for (const Value v : values) { + if (const auto idx = indexOf(v)) { + mask |= uint64_t{1} << *idx; + } + } + return mask; +} + +void QuantumState::markTop() { + top = true; + amplitudes.clear(); +} + +void QuantumState::forwardQubit(const Value from, const Value to) { + if (const auto idx = indexOf(from)) { + qubits[*idx] = to; + } +} + +void QuantumState::canonicalize() { + if (top) { + return; + } + SmallVector negligible; + for (const auto& [key, amp] : amplitudes) { + if (std::abs(amp) <= MATRIX_TOLERANCE) { + negligible.push_back(key); + } + } + for (const uint64_t key : negligible) { + amplitudes.erase(key); + } + if (amplitudes.size() > maxNonzeroAmplitudes) { + markTop(); + } +} + +LogicalResult QuantumState::applyMatrix1Q(const Value in, const Value out, + const Matrix2x2& matrix, + const ArrayRef ctrls) { + const auto idx = indexOf(in); + if (!idx) { + return failure(); + } + if (top) { + forwardQubit(in, out); + return success(); + } + + const uint64_t targetBit = uint64_t{1} << *idx; + const uint64_t ctrlMask = maskOf(ctrls); + + llvm::DenseMap result; + for (const auto& [key, amp] : amplitudes) { + if ((key & ctrlMask) != ctrlMask) { + result[key] += amp; + continue; + } + // Scatter this input's matrix column across both output rows. + const uint64_t base = key & ~targetBit; + const unsigned col = (key & targetBit) != 0 ? 1U : 0U; + result[base] += matrix.data[col] * amp; + result[base | targetBit] += matrix.data[2 + col] * amp; + } + + amplitudes = std::move(result); + forwardQubit(in, out); + canonicalize(); + + return success(); +} + +LogicalResult QuantumState::applyMatrix2Q(const Value in0, const Value in1, + const Value out0, const Value out1, + const Matrix4x4& matrix, + const ArrayRef ctrls) { + const auto idx0 = indexOf(in0); + const auto idx1 = indexOf(in1); + if (!idx0 || !idx1 || *idx0 == *idx1) { + return failure(); + } + if (top) { + forwardQubit(in0, out0); + forwardQubit(in1, out1); + return success(); + } + + // QCO convention: the first target is the high bit of the local 4-index. + const uint64_t hiBit = uint64_t{1} << *idx0; + const uint64_t loBit = uint64_t{1} << *idx1; + const uint64_t bothBits = hiBit | loBit; + const uint64_t ctrlMask = maskOf(ctrls); + + const auto localKey = [&](const uint64_t base, const unsigned local) { + return base | ((local & 1U) != 0U ? loBit : 0) | + ((local & 2U) != 0U ? hiBit : 0); + }; + const auto localCol = [&](const uint64_t key) { + return ((key & hiBit) != 0 ? 2U : 0U) | ((key & loBit) != 0 ? 1U : 0U); + }; + + llvm::DenseMap result; + for (const auto& [key, amp] : amplitudes) { + if ((key & ctrlMask) != ctrlMask) { + result[key] += amp; + continue; + } + // Scatter this input's matrix column across all four output rows. + const uint64_t base = key & ~bothBits; + const unsigned col = localCol(key); + for (unsigned row = 0; row < 4; ++row) { + result[localKey(base, row)] += matrix.data[(4 * row) + col] * amp; + } + } + + amplitudes = std::move(result); + forwardQubit(in0, out0); + forwardQubit(in1, out1); + canonicalize(); + + return success(); +} + +void QuantumState::applyGlobalPhase(const double phase, + const ArrayRef ctrls) { + if (top) { + return; + } + const uint64_t ctrlMask = maskOf(ctrls); + const Complex factor = std::exp(Complex{0.0, phase}); + for (auto& [key, amp] : amplitudes) { + if ((key & ctrlMask) == ctrlMask) { + amp *= factor; + } + } + canonicalize(); +} + +FailureOr> +QuantumState::measure(const Value target) const { + const auto idx = indexOf(target); + if (!idx) { + return failure(); + } + if (top) { + return SmallVector{}; + } + const uint64_t targetBit = uint64_t{1} << *idx; + + llvm::DenseMap zeroAmps; + llvm::DenseMap oneAmps; + double probZero = 0.0; + double probOne = 0.0; + for (const auto& [key, amp] : amplitudes) { + if ((key & targetBit) == 0) { + zeroAmps[key] = amp; + probZero += std::norm(amp); + } else { + oneAmps[key] = amp; + probOne += std::norm(amp); + } + } + + const auto makeBranch = [&](const unsigned bit, const double probability, + const llvm::DenseMap& amps) { + auto branch = + std::unique_ptr(new QuantumState(maxNonzeroAmplitudes)); + branch->qubits = qubits; + const double scale = 1.0 / std::sqrt(probability); + for (const auto& [key, amp] : amps) { + branch->amplitudes[key] += amp * scale; + } + branch->canonicalize(); + return MeasurementOutcome{.bit=bit, .probability=probability, .state=std::move(branch)}; + }; + + SmallVector outcomes; + if (!zeroAmps.empty()) { + outcomes.push_back(makeBranch(0, probZero, zeroAmps)); + } + if (!oneAmps.empty()) { + outcomes.push_back(makeBranch(1, probOne, oneAmps)); + } + return outcomes; +} + +FailureOr> +QuantumState::reset(const Value target) const { + auto outcomes = measure(target); + if (failed(outcomes)) { + return failure(); + } + for (auto& outcome : *outcomes) { + if (outcome.bit == 0 || outcome.state == nullptr) { + continue; + } + const auto idx = outcome.state->indexOf(target); + if (!idx) { + return failure(); + } + const uint64_t targetBit = uint64_t{1} << *idx; + llvm::DenseMap flipped; + for (const auto& [key, amp] : outcome.state->amplitudes) { + flipped[key & ~targetBit] += amp; + } + outcome.state->amplitudes = std::move(flipped); + outcome.state->canonicalize(); + } + return outcomes; +} + +QuantumState QuantumState::unify(const QuantumState& that) const { + QuantumState result(maxNonzeroAmplitudes); + result.qubits.append(qubits.begin(), qubits.end()); + result.qubits.append(that.qubits.begin(), that.qubits.end()); + + if (top || that.top || + result.qubits.size() > MAX_GROUP_QUBITS || + amplitudes.size() * that.amplitudes.size() > maxNonzeroAmplitudes) { + result.markTop(); + return result; + } + + const auto shift = qubits.size(); + for (const auto& [keyA, ampA] : amplitudes) { + for (const auto& [keyB, ampB] : that.amplitudes) { + result.amplitudes[keyA | keyB << shift] += ampA * ampB; + } + } + result.canonicalize(); + return result; +} + +bool QuantumState::isAlwaysZero(const Value q) const { + const auto idx = indexOf(q); + if (top || !idx || amplitudes.empty()) { + return false; + } + return llvm::all_of(amplitudes, [&](const auto& entry) { + return (entry.first >> *idx & uint64_t{1}) == 0; + }); +} + +bool QuantumState::isAlwaysOne(const Value q) const { + const auto idx = indexOf(q); + if (top || !idx || amplitudes.empty()) { + return false; + } + return llvm::all_of(amplitudes, [&](const auto& entry) { + return (entry.first >> *idx & uint64_t{1}) == 1; + }); +} + +bool QuantumState::hasAlwaysZeroAmplitude( + const ArrayRef> basis) const { + if (top) { + return false; + } + uint64_t mask = 0; + uint64_t wanted = 0; + for (const auto& [qubit, one] : basis) { + const auto idx = indexOf(qubit); + if (!idx) { + continue; + } + mask |= uint64_t{1} << *idx; + if (one) { + wanted |= uint64_t{1} << *idx; + } + } + return llvm::all_of(amplitudes, [&](const auto& entry) { + return (entry.first & mask) != wanted; + }); +} + +bool QuantumState::operator==(const QuantumState& that) const { + if (top || that.top) { + return top == that.top; + } + if (maxNonzeroAmplitudes != that.maxNonzeroAmplitudes || + qubits.size() != that.qubits.size() || + amplitudes.size() != that.amplitudes.size()) { + return false; + } + if (!std::equal(qubits.begin(), qubits.end(), that.qubits.begin())) { + return false; + } + return llvm::all_of(amplitudes, [&](const auto& entry) { + const auto it = that.amplitudes.find(entry.first); + return it != that.amplitudes.end() && + std::abs(entry.second - it->second) <= MATRIX_TOLERANCE; + }); +} + +void QuantumState::print(raw_ostream& os) const { + if (qubits.empty()) { + return; + } + + const std::map ordered(amplitudes.begin(), amplitudes.end()); + bool first = true; + for (const auto& [key, amp] : ordered) { + if (!first) { + os << ", "; + } + first = false; + + os << '|'; + for (size_t bit = qubits.size(); bit-- > 0;) { + os << (((key >> bit) & uint64_t{1}) != 0 ? '1' : '0'); + } + os << "> -> "; + + std::array buf{}; + std::snprintf(buf.data(), buf.size(), "%.2f", amp.real()); + const llvm::StringRef real(buf.data()); + os << (real == "-0.00" ? llvm::StringRef("0.00") : real); + + if (std::abs(amp.imag()) > MATRIX_TOLERANCE) { + os << (amp.imag() > 0 ? " + i" : " - i"); + std::snprintf(buf.data(), buf.size(), "%.2f", std::abs(amp.imag())); + os << buf.data(); + } + } +} + +} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp new file mode 100644 index 0000000000..8d0e565076 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/QCO/Utils/Matrix.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace mlir::qco { + +class QuantumState; + +/** + * @brief One branch of a measurement or reset: the observed bit, its + * probability, and the normalized post-measurement state. + */ +struct MeasurementOutcome { + unsigned bit; + double probability; + std::unique_ptr state; +}; + +/** + * @brief One entanglement group: a pure quantum state over a set of qubit SSA + * values. + * + * The state is a map from computational-basis index to complex amplitude. The + * bit at position i of a basis index refers to the qubit at position i of the + * managed vector. + * + * If the number of non-zero amplitudes exceeds the threshold + * maxNonzeroAmplitudes, or the group holds more than qubits than unsigned + * int has bits, the state collapses to top. + */ +class QuantumState { + bool top = false; + size_t maxNonzeroAmplitudes; + SmallVector qubits; + llvm::DenseMap amplitudes; + + explicit QuantumState(const size_t maxNonzeroAmplitudes) + : maxNonzeroAmplitudes(maxNonzeroAmplitudes) {} + + /// @brief Bitmask of the positions of the given values that are in the group. + [[nodiscard("QuantumState::maskOf called but ignored")]] uint64_t + maskOf(ArrayRef values) const; + + /// @brief Drops negligible amplitudes and collapses to top if the conditions + /// are met. + void canonicalize(); + +public: + /** + * @brief Builds the all-zero state |0...0> over the qubits. + * + * @param qubits The qubit values of the group, in bit-position order. + * @param maxNonzeroAmplitudes Amplitude budget before the state becomes top. + */ + QuantumState(ArrayRef qubits, size_t maxNonzeroAmplitudes); + + /// @brief Builds a QuantumState for a single-qubit in state |0>. + static QuantumState singletonZero(Value qubit, size_t maxNonzeroAmplitudes); + + [[nodiscard("QuantumState::isTop called but ignored")]] bool isTop() const { + return top; + } + [[nodiscard("QuantumState::getQubits called but ignored")]] ArrayRef + getQubits() const { + return qubits; + } + + /// @brief Whether QuantumState contains the qubit. + [[nodiscard("QuantumState::contains called but ignored")]] bool + contains(const Value q) const { + return indexOf(q).has_value(); + } + /// @brief The bit position of a qubit, if present. + [[nodiscard( + "QuantumState::indexOf called but ignored")]] std::optional + indexOf(Value q) const; + + /// @brief Collapses the state to top. + void markTop(); + + /// @brief Changes qubit from to qubit to in place. No-op if QuantumState does + /// not contain from. + void forwardQubit(Value from, Value to); + + /** + * @brief Applies a single-qubit unitary to qubit in, renaming it to qubit + * out. + * + * When ctrls is non-empty the matrix is applied only on the subspace where + * every control qubit is |1>; the rest of the state passes through. Does + * nothing but the rename when the state is top. + * + * @param in The qubit to apply the matrix to. + * @param out The qubit that in is changed to. + * @param matrix The matrix to apply to the amplitudes of in. + * @param ctrls The qubits that have to be |1> to apply the matrix. + * @return failure() if in is not in this group (a caller/propagation bug - + * the interpreter must co-locate a gate's targets before applying it); + * success() otherwise. + */ + [[nodiscard("QuantumState::applyMatrix1Q called but ignored")]] LogicalResult + applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, + ArrayRef ctrls = {}); + + /** + * @brief Applies a two-qubit unitary to in0 and in1, renaming them to out0, + * out1. + * + * Qubit ordering follows QCO's @ref Matrix4x4 convention: in0 is the high bit + * of the 4-dimensional local index, in1 the low bit. When ctrls is non-empty + * the matrix is applied only on the subspace where every control qubit is + * |1>; the rest of the state passes through. Does nothing but the renames when + * the state is top. + * + * @param in0 The high bit the matrx is applied to. + * @param in1 The low bit the matrx is applied to. + * @param out0 The qubit that in0 is changed to. + * @param out1 The qubit that in1 is changed to. + * @param matrix The matrix to apply to the amplitudes of in0 and in1. + * @param ctrls The qubits that have to be |1> to apply the matrix. + * @return failure() if in0 or in1 is not in this group, or they are the same + * bit position (a caller/propagation bug); success() otherwise. + */ + [[nodiscard("QuantumState::applyMatrix2Q called but ignored")]] LogicalResult + applyMatrix2Q(Value in0, Value in1, Value out0, Value out1, + const Matrix4x4& matrix, ArrayRef ctrls = {}); + + /** + * @brief Multiplies the amplitudes by exp(i*phase). + * + * With no controls this adds global phase. With controls, it is a relative + * phase applied only where every control qubits are |1>. + * + * @param phase The phase to add to the quantum state. + * @param ctrls The qubits that have to be |1> to apply the matrix. + */ + void applyGlobalPhase(double phase, ArrayRef ctrls = {}); + + /** + * @brief Projective measurement of a target in the computational basis. + * + * Each branch's state is re-normalized and keeps the target (now definite). + * + * @param target The target that is being measured. + * @return failure() if target is not in the group (a caller/propagation bug). + * Otherwise: an empty list if the state is top, one branch if the outcome is + * deterministic, two branches otherwise. + */ + [[nodiscard("QuantumState::measure called but ignored")]] + FailureOr> measure(Value target) const; + + /** + * @brief Reset of a target: measure, then force the qubit to |0>. + * + * Each branch's state is re-normalized and keeps the target (now in |0>). + * + * @param target The target that is being reset. + * @return failure() if target is not in the group (a caller/propagation bug). + * Otherwise: an empty list if the state is top, one branch if the outcome is + * deterministic, two branches otherwise. + */ + [[nodiscard("QuantumState::reset called but ignored")]] + FailureOr> reset(Value target) const; + + /** + * @brief Tensor product of this group with that. + * + * The result's qubits are this->getQubits() followed by that.getQubits(). + * Becomes top if either operand is top or the product exceeds this group's + * maximally allowed amplitude number. The two groups must not share qubits. + * + * @param that The QuantumState to unify this with. + */ + [[nodiscard("QuantumState::unify called but ignored")]] QuantumState + unify(const QuantumState& that) const; + + /// @brief Whether every non-zero amplitude has q set to zero. + [[nodiscard("QuantumState::isAlwaysZero called but ignored")]] bool + isAlwaysZero(Value q) const; + + /// @brief Whether every non-zero amplitude has q set to one. + [[nodiscard("QuantumState::isAlwaysOne called but ignored")]] bool + isAlwaysOne(Value q) const; + + /** + * @brief Whether the given qubit basis never occurs. + * + * @param basis Pairs of (qubit, expected bit value); qubits not in the + * group are ignored. Returns true when no non-zero amplitude matches all + * the (in-group) pairs simultaneously. + */ + [[nodiscard("QuantumState::hasZeroAmplitude called but ignored")]] bool + hasAlwaysZeroAmplitude(ArrayRef> basis) const; + + [[nodiscard("QuantumState::== called but ignored")]] bool + operator==(const QuantumState& that) const; + + /** + * @brief Human-readable dump, e.g. "|010> -> 0.71, |110> -> -0.71". + * + * Used by the enclosing HybridState / UnionTable / lattice print overrides + * and for debugging. Basis states are listed in ascending index order; bit i + * of the printed string (from the right) is getQubits()[i]. Amplitudes use + * two decimals and an "+ i" / "- i" imaginary part when non-negligible. + * Prints nothing for a group with no qubits. + */ + void print(raw_ostream& os) const; +}; + +} // namespace mlir::qco diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index 46295f85ea..9e46c15a29 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -9,6 +9,7 @@ set(target_name mqt-core-mlir-unittest-optimizations) add_executable( ${target_name} + ConstantPropagation/test_quantumState.cpp test_qco_hadamard_lifting.cpp test_qco_measurement_lifting.cpp test_qco_merge_single_qubit_rotation.cpp @@ -33,6 +34,10 @@ target_link_libraries( LLVMSupport MLIRSupportMQT) +# ConstantPropagation unit tests reach into the pass's private headers. +target_include_directories( + ${target_name} PRIVATE ${PROJECT_SOURCE_DIR}/mlir/lib/Dialect/QCO/Transforms/Optimizations) + mqt_mlir_configure_unittest_target(${target_name} REQUIRES_EH) gtest_discover_tests(${target_name} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp new file mode 100644 index 0000000000..23fd9ebd77 --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "ConstantPropagation/QuantumState.hpp" +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace { + +using namespace mlir; +using namespace mlir::qco; + +/// Renders a QuantumState through its print() method for readable assertions. +std::string printed(const QuantumState& qs) { + std::string s; + llvm::raw_string_ostream os(s); + qs.print(os); + return s; +} + +class QuantumStateTest : public testing::Test { +protected: + MLIRContext context; + QCOProgramBuilder builder; + + std::array q{}; + HOp hOp; + XOp xOp; + ZOp zOp; + SWAPOp swapOp; + DCXOp dcxOp; + + QuantumStateTest() : builder(&context) {} + + void SetUp() override { + DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + + builder.initialize(); + auto reg = builder.allocQubitRegister(4); + for (size_t i = 0; i < q.size(); ++i) { + q[i] = reg[i]; + } + const auto qt = q[0].getType(); + hOp = HOp::create(builder, builder.getLoc(), qt, q[0]); + xOp = XOp::create(builder, builder.getLoc(), qt, q[0]); + zOp = ZOp::create(builder, builder.getLoc(), qt, q[0]); + swapOp = SWAPOp::create(builder, builder.getLoc(), qt, qt, q[0], q[1]); + dcxOp = DCXOp::create(builder, builder.getLoc(), qt, qt, q[0], q[1]); + } +}; + +//===----------------------------------------------------------------------===// +// Construction +//===----------------------------------------------------------------------===// + +TEST_F(QuantumStateTest, allZeroState) { + const auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 4); + EXPECT_FALSE(qs.isTop()); + EXPECT_EQ(qs.getQubits().size(), 4U); + EXPECT_EQ(printed(qs), "|0000> -> 1.00"); +} + +//===----------------------------------------------------------------------===// +// Single-qubit gates +//===----------------------------------------------------------------------===// + +TEST_F(QuantumStateTest, applyH) { + auto qs = QuantumState::singletonZero(q[0], 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + EXPECT_EQ(printed(qs), "|0> -> 0.71, |1> -> 0.71"); +} + +TEST_F(QuantumStateTest, applyHToThirdQubit) { + auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[2], q[2], hOp.getUnitaryMatrix()).succeeded()); + EXPECT_EQ(printed(qs), "|0000> -> 0.71, |0100> -> 0.71"); +} + +TEST_F(QuantumStateTest, applyHTwiceIsIdentity) { + auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[2], q[2], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(qs.applyMatrix1Q(q[2], q[2], hOp.getUnitaryMatrix()).succeeded()); + EXPECT_EQ(printed(qs), "|0000> -> 1.00"); +} + +TEST_F(QuantumStateTest, applyHThenZ) { + auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[2], q[2], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(qs.applyMatrix1Q(q[2], q[2], zOp.getUnitaryMatrix()).succeeded()); + EXPECT_EQ(printed(qs), "|0000> -> 0.71, |0100> -> -0.71"); +} + +TEST_F(QuantumStateTest, applyHZHIsX) { + auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[2], q[2], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(qs.applyMatrix1Q(q[2], q[2], zOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(qs.applyMatrix1Q(q[2], q[2], hOp.getUnitaryMatrix()).succeeded()); + EXPECT_EQ(printed(qs), "|0100> -> 1.00"); +} + +TEST_F(QuantumStateTest, applyGatesToTwoIndependentQubits) { + auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[2], q[2], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + EXPECT_EQ(printed(qs), "|0001> -> 0.71, |0101> -> 0.71"); +} + +TEST_F(QuantumStateTest, forwardQubitRenamesInPlace) { + auto qs = QuantumState::singletonZero(q[0], 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[1], xOp.getUnitaryMatrix()).succeeded()); + EXPECT_FALSE(qs.contains(q[0])); + EXPECT_TRUE(qs.contains(q[1])); + EXPECT_TRUE(qs.isAlwaysOne(q[1])); +} + +//===----------------------------------------------------------------------===// +// Two-qubit gates +//===----------------------------------------------------------------------===// + +TEST_F(QuantumStateTest, applySwap) { + auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[1], q[1], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE( + qs.applyMatrix2Q(q[1], q[3], q[1], q[3], swapOp.getUnitaryMatrix()) + .succeeded()); + EXPECT_EQ(printed(qs), "|0000> -> 0.71, |1000> -> 0.71"); +} + +TEST_F(QuantumStateTest, applyDcxActsAsCxCx) { + auto qs = QuantumState({q[0], q[1]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(qs.applyMatrix2Q(q[0], q[1], q[0], q[1], dcxOp.getUnitaryMatrix()) + .succeeded()); + EXPECT_TRUE(qs.isAlwaysZero(q[0])); + EXPECT_TRUE(qs.isAlwaysOne(q[1])); + EXPECT_EQ(printed(qs), "|10> -> 1.00"); +} + +//===----------------------------------------------------------------------===// +// Precondition failures +//===----------------------------------------------------------------------===// + +TEST_F(QuantumStateTest, applyToQubitNotInGroupFails) { + auto qs = QuantumState::singletonZero(q[0], 4); + EXPECT_TRUE(qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix()).failed()); +} + +TEST_F(QuantumStateTest, applyTwoQubitGateToSameBitFails) { + auto qs = QuantumState({q[0], q[1]}, 4); + EXPECT_TRUE( + qs.applyMatrix2Q(q[0], q[0], q[0], q[0], swapOp.getUnitaryMatrix()) + .failed()); +} + +TEST_F(QuantumStateTest, applyToQubitNotInGroupFailsEvenWhenTop) { + auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 1); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(qs.isTop()); + EXPECT_TRUE(qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + const Value stranger = builder.allocQubit(); + EXPECT_TRUE( + qs.applyMatrix1Q(stranger, stranger, xOp.getUnitaryMatrix()).failed()); +} + +//===----------------------------------------------------------------------===// +// Controls +//===----------------------------------------------------------------------===// + +TEST_F(QuantumStateTest, controlledGateFires) { + auto qs = QuantumState({q[0], q[1]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE( + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}).succeeded()); + EXPECT_EQ(printed(qs), "|11> -> 1.00"); +} + +TEST_F(QuantumStateTest, controlledGateDoesNotFire) { + auto qs = QuantumState({q[0], q[1]}, 4); + ASSERT_TRUE( + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}).succeeded()); + EXPECT_EQ(printed(qs), "|00> -> 1.00"); +} + +TEST_F(QuantumStateTest, controlledGateOnSuperposition) { + auto qs = QuantumState({q[0], q[1]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE( + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}).succeeded()); + EXPECT_EQ(printed(qs), "|00> -> 0.71, |11> -> 0.71"); +} + +//===----------------------------------------------------------------------===// +// Amplitude budget +//===----------------------------------------------------------------------===// + +TEST_F(QuantumStateTest, exceedingAmplitudeBudgetBecomesTop) { + auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 2); + ASSERT_TRUE(qs.applyMatrix1Q(q[3], q[3], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE( + qs.applyMatrix1Q(q[2], q[2], xOp.getUnitaryMatrix(), {q[3]}).succeeded()); + EXPECT_FALSE(qs.isTop()); + ASSERT_TRUE(qs.applyMatrix1Q(q[2], q[2], hOp.getUnitaryMatrix()).succeeded()); + EXPECT_TRUE(qs.isTop()); +} + +TEST_F(QuantumStateTest, topStateStillForwardsQubits) { + auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 1); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(qs.isTop()); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[1], xOp.getUnitaryMatrix()).succeeded()); + EXPECT_FALSE(qs.contains(q[0])); + EXPECT_TRUE(qs.contains(q[1])); +} + +//===----------------------------------------------------------------------===// +// Global phase +//===----------------------------------------------------------------------===// + +TEST_F(QuantumStateTest, globalPhaseMultipliesEveryAmplitude) { + auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + qs.applyGlobalPhase(std::acos(-1.0)); + EXPECT_EQ(printed(qs), "|0000> -> -0.71, |0001> -> -0.71"); +} + +TEST_F(QuantumStateTest, controleldGlobalPhaseMultipliesNotEveryAmplitude) { + auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + qs.applyGlobalPhase(std::acos(-1.0), {q[0]}); + EXPECT_EQ(printed(qs), "|0000> -> 0.71, |0001> -> -0.71"); +} + +//===----------------------------------------------------------------------===// +// Measurement +//===----------------------------------------------------------------------===// + +TEST_F(QuantumStateTest, measureDeterministicZero) { + const auto qs = QuantumState::singletonZero(q[0], 2); + const auto result = qs.measure(q[0]); + ASSERT_TRUE(succeeded(result)); + const auto& outcomes = *result; + ASSERT_EQ(outcomes.size(), 1U); + EXPECT_EQ(outcomes[0].bit, 0U); + EXPECT_DOUBLE_EQ(outcomes[0].probability, 1.0); + EXPECT_TRUE(*outcomes[0].state == qs); +} + +TEST_F(QuantumStateTest, measureDeterministicOne) { + auto qs = QuantumState::singletonZero(q[0], 2); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + const auto result = qs.measure(q[0]); + ASSERT_TRUE(succeeded(result)); + const auto& outcomes = *result; + ASSERT_EQ(outcomes.size(), 1U); + EXPECT_EQ(outcomes[0].bit, 1U); + EXPECT_DOUBLE_EQ(outcomes[0].probability, 1.0); + EXPECT_TRUE(*outcomes[0].state == qs); +} + +TEST_F(QuantumStateTest, measureSuperpositionSplits) { + auto qs = QuantumState({q[0], q[1]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE( + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}).succeeded()); + const auto result = qs.measure(q[0]); + ASSERT_TRUE(succeeded(result)); + const auto& outcomes = *result; + ASSERT_EQ(outcomes.size(), 2U); + EXPECT_EQ(outcomes[0].bit, 0U); + EXPECT_DOUBLE_EQ(outcomes[0].probability, 0.5); + EXPECT_EQ(printed(*outcomes[0].state), "|00> -> 1.00"); + EXPECT_EQ(outcomes[1].bit, 1U); + EXPECT_DOUBLE_EQ(outcomes[1].probability, 0.5); + EXPECT_EQ(printed(*outcomes[1].state), "|11> -> 1.00"); +} + +TEST_F(QuantumStateTest, measureQubitNotInGroupFails) { + const auto qs = QuantumState::singletonZero(q[0], 2); + EXPECT_TRUE(failed(qs.measure(q[1]))); +} + +TEST_F(QuantumStateTest, measureOnTopStateYieldsNoBranches) { + auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 1); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(qs.isTop()); + const auto result = qs.measure(q[0]); + ASSERT_TRUE(succeeded(result)); + EXPECT_TRUE(result->empty()); +} + +//===----------------------------------------------------------------------===// +// Reset +//===----------------------------------------------------------------------===// + +TEST_F(QuantumStateTest, resetDeterministicZero) { + const auto qs = QuantumState::singletonZero(q[0], 2); + const auto result = qs.reset(q[0]); + ASSERT_TRUE(succeeded(result)); + const auto& outcomes = *result; + ASSERT_EQ(outcomes.size(), 1U); + EXPECT_EQ(outcomes[0].bit, 0U); + EXPECT_EQ(printed(*outcomes[0].state), "|0> -> 1.00"); +} + +TEST_F(QuantumStateTest, resetDeterministicOneForcesZero) { + auto qs = QuantumState::singletonZero(q[0], 2); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + const auto result = qs.reset(q[0]); + ASSERT_TRUE(succeeded(result)); + const auto& outcomes = *result; + ASSERT_EQ(outcomes.size(), 1U); + EXPECT_EQ(outcomes[0].bit, 1U); + EXPECT_EQ(printed(*outcomes[0].state), "|0> -> 1.00"); +} + +TEST_F(QuantumStateTest, resetSuperpositionForcesTargetToZero) { + auto qs = QuantumState({q[0], q[1]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE( + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}).succeeded()); + const auto result = qs.reset(q[0]); + ASSERT_TRUE(succeeded(result)); + const auto& outcomes = *result; + ASSERT_EQ(outcomes.size(), 2U); + EXPECT_EQ(printed(*outcomes[0].state), "|00> -> 1.00"); + EXPECT_DOUBLE_EQ(outcomes[1].probability, 0.5); + EXPECT_EQ(printed(*outcomes[1].state), "|10> -> 1.00"); +} + +TEST_F(QuantumStateTest, resetQubitNotInGroupFails) { + const auto qs = QuantumState::singletonZero(q[0], 2); + EXPECT_TRUE(failed(qs.reset(q[1]))); +} + +TEST_F(QuantumStateTest, resetOnTopStateYieldsNoBranches) { + auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 1); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(qs.isTop()); + const auto result = qs.reset(q[0]); + ASSERT_TRUE(succeeded(result)); + EXPECT_TRUE(result->empty()); +} + +//===----------------------------------------------------------------------===// +// unify +//===----------------------------------------------------------------------===// + +TEST_F(QuantumStateTest, unifyTensorsTwoGroups) { + auto a = QuantumState::singletonZero(q[0], 10); + ASSERT_TRUE(a.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + const auto b = QuantumState::singletonZero(q[1], 10); + const auto unified = a.unify(b); + EXPECT_EQ(unified.getQubits().size(), 2U); + EXPECT_EQ(printed(unified), "|00> -> 0.71, |01> -> 0.71"); +} + +TEST_F(QuantumStateTest, unifyExceedingBudgetIsTop) { + auto a = QuantumState({q[0], q[1]}, 3); + ASSERT_TRUE(a.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + auto b = QuantumState({q[2], q[3]}, 3); + ASSERT_TRUE(b.applyMatrix1Q(q[2], q[2], hOp.getUnitaryMatrix()).succeeded()); + EXPECT_FALSE(a.isTop()); + EXPECT_FALSE(b.isTop()); + EXPECT_TRUE(a.unify(b).isTop()); +} + +//===----------------------------------------------------------------------===// +// Queries +//===----------------------------------------------------------------------===// + +TEST_F(QuantumStateTest, alwaysZeroAndAlwaysOne) { + auto qs = QuantumState({q[0], q[1]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + EXPECT_TRUE(qs.isAlwaysOne(q[0])); + EXPECT_FALSE(qs.isAlwaysZero(q[0])); + EXPECT_TRUE(qs.isAlwaysZero(q[1])); + EXPECT_FALSE(qs.isAlwaysOne(q[1])); + + ASSERT_TRUE(qs.applyMatrix1Q(q[1], q[1], hOp.getUnitaryMatrix()).succeeded()); + EXPECT_FALSE(qs.isAlwaysZero(q[1])); + EXPECT_FALSE(qs.isAlwaysOne(q[1])); +} + +TEST_F(QuantumStateTest, hasAlwaysZeroAmplitude) { + auto qs = QuantumState({q[0], q[1]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE( + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}).succeeded()); + EXPECT_TRUE(qs.hasAlwaysZeroAmplitude({{q[0], false}, {q[1], true}})); + EXPECT_FALSE(qs.hasAlwaysZeroAmplitude({{q[0], true}, {q[1], true}})); +} + +//===----------------------------------------------------------------------===// +// Equality +//===----------------------------------------------------------------------===// + +TEST_F(QuantumStateTest, equalityIgnoresNegligibleDifferences) { + auto a = QuantumState({q[0], q[1], q[2], q[3]}, 4); + const auto b = QuantumState({q[0], q[1], q[2], q[3]}, 4); + ASSERT_TRUE(a.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(a.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()) + .succeeded()); + EXPECT_TRUE(a == b); +} + +TEST_F(QuantumStateTest, topStatesAreEqual) { + auto a = QuantumState({q[0], q[1], q[2], q[3]}, 1); + auto b = QuantumState({q[0], q[1], q[2], q[3]}, 1); + a.markTop(); + b.markTop(); + EXPECT_TRUE(a == b); + EXPECT_FALSE(a == QuantumState({q[0], q[1], q[2], q[3]}, 4)); +} + +} // namespace From 3ea51c869f83e21f0858eb2c9328afaa8fce099c Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 28 Aug 2026 20:16:52 +0200 Subject: [PATCH 13/55] :construction: QuantumState only applies relative phases, assisted-by Sonnet 5 via Claude Code --- .../ConstantPropagation/QuantumState.cpp | 32 ++++++++++++------- .../ConstantPropagation/QuantumState.hpp | 21 +++++++----- .../ConstantPropagation/test_quantumState.cpp | 19 ++++++----- 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index e68f3cabe5..bd4cc988ff 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -15,16 +15,16 @@ #include #include #include +#include #include #include +#include #include #include #include -#include #include #include -#include #include #include #include @@ -187,10 +187,18 @@ LogicalResult QuantumState::applyMatrix2Q(const Value in0, const Value in1, return success(); } -void QuantumState::applyGlobalPhase(const double phase, - const ArrayRef ctrls) { +LogicalResult QuantumState::applyControlledPhase(const double phase, + const ArrayRef ctrls) { + if (ctrls.empty()) { + return failure(); + } + for (const Value c : ctrls) { + if (!contains(c)) { + return failure(); + } + } if (top) { - return; + return success(); } const uint64_t ctrlMask = maskOf(ctrls); const Complex factor = std::exp(Complex{0.0, phase}); @@ -200,6 +208,7 @@ void QuantumState::applyGlobalPhase(const double phase, } } canonicalize(); + return success(); } FailureOr> @@ -377,15 +386,14 @@ void QuantumState::print(raw_ostream& os) const { } os << "> -> "; - std::array buf{}; - std::snprintf(buf.data(), buf.size(), "%.2f", amp.real()); - const llvm::StringRef real(buf.data()); - os << (real == "-0.00" ? llvm::StringRef("0.00") : real); + SmallString<16> buf; + llvm::raw_svector_ostream(buf) << llvm::format("%.2f", amp.real()); + const llvm::StringRef real(buf); + os << (real == "-0.00" ? StringRef("0.00") : real); if (std::abs(amp.imag()) > MATRIX_TOLERANCE) { - os << (amp.imag() > 0 ? " + i" : " - i"); - std::snprintf(buf.data(), buf.size(), "%.2f", std::abs(amp.imag())); - os << buf.data(); + os << (amp.imag() > 0 ? " + i" : " - i") + << llvm::format("%.2f", std::abs(amp.imag())); } } } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index 8d0e565076..6a5d2cace0 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -130,8 +130,8 @@ class QuantumState { * Qubit ordering follows QCO's @ref Matrix4x4 convention: in0 is the high bit * of the 4-dimensional local index, in1 the low bit. When ctrls is non-empty * the matrix is applied only on the subspace where every control qubit is - * |1>; the rest of the state passes through. Does nothing but the renames when - * the state is top. + * |1>; the rest of the state passes through. Does nothing but the renames + * when the state is top. * * @param in0 The high bit the matrx is applied to. * @param in1 The low bit the matrx is applied to. @@ -147,15 +147,20 @@ class QuantumState { const Matrix4x4& matrix, ArrayRef ctrls = {}); /** - * @brief Multiplies the amplitudes by exp(i*phase). + * @brief Multiplies by exp(i*phase) the amplitudes where every control is + * |1>. * - * With no controls this adds global phase. With controls, it is a relative - * phase applied only where every control qubits are |1>. + * This function applies a relative phase when there is a controlled global + * phase. An uncontrolled (global) phase is physically unobservable and not + * recoverable from an amplitude map, so it is tracked by HybridState instead + * and rejected here. * - * @param phase The phase to add to the quantum state. - * @param ctrls The qubits that have to be |1> to apply the matrix. + * @param phase The phase to apply. + * @param ctrls The qubits that all have to be |1> for the phase to apply. + * @return failure() if ctrls is empty or a control is not in this group. */ - void applyGlobalPhase(double phase, ArrayRef ctrls = {}); + [[nodiscard("QuantumState::applyControlledPhase called but ignored")]] + LogicalResult applyControlledPhase(double phase, ArrayRef ctrls); /** * @brief Projective measurement of a target in the computational basis. diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 23fd9ebd77..46cdeeb738 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -238,23 +238,26 @@ TEST_F(QuantumStateTest, topStateStillForwardsQubits) { } //===----------------------------------------------------------------------===// -// Global phase +// Controlled phase //===----------------------------------------------------------------------===// -TEST_F(QuantumStateTest, globalPhaseMultipliesEveryAmplitude) { - auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 4); - ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); - qs.applyGlobalPhase(std::acos(-1.0)); - EXPECT_EQ(printed(qs), "|0000> -> -0.71, |0001> -> -0.71"); +TEST_F(QuantumStateTest, uncontrolledPhaseIsRejected) { + auto qs = QuantumState::singletonZero(q[0], 4); + EXPECT_TRUE(qs.applyControlledPhase(std::acos(-1.0), {}).failed()); } -TEST_F(QuantumStateTest, controleldGlobalPhaseMultipliesNotEveryAmplitude) { +TEST_F(QuantumStateTest, controlledPhaseAffectsOnlyControlledSubspace) { auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 4); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); - qs.applyGlobalPhase(std::acos(-1.0), {q[0]}); + ASSERT_TRUE(qs.applyControlledPhase(std::acos(-1.0), {q[0]}).succeeded()); EXPECT_EQ(printed(qs), "|0000> -> 0.71, |0001> -> -0.71"); } +TEST_F(QuantumStateTest, controlledPhaseOnQubitNotInGroupFails) { + auto qs = QuantumState::singletonZero(q[0], 4); + EXPECT_TRUE(qs.applyControlledPhase(1.0, {q[1]}).failed()); +} + //===----------------------------------------------------------------------===// // Measurement //===----------------------------------------------------------------------===// From 64454ebc3f326fd31bb1d5591133fdcfbdebde97 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 28 Aug 2026 20:44:27 +0200 Subject: [PATCH 14/55] :construction: QuantumState fails if controls are not in state, assisted-by Sonnet 5 via Claude Code --- .../ConstantPropagation/QuantumState.cpp | 10 ++++++++++ .../ConstantPropagation/QuantumState.hpp | 11 ++++++----- .../ConstantPropagation/test_quantumState.cpp | 6 ++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index bd4cc988ff..dae7c9e30a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -108,6 +108,11 @@ LogicalResult QuantumState::applyMatrix1Q(const Value in, const Value out, if (!idx) { return failure(); } + for (const Value c : ctrls) { + if (!contains(c)) { + return failure(); + } + } if (top) { forwardQubit(in, out); return success(); @@ -145,6 +150,11 @@ LogicalResult QuantumState::applyMatrix2Q(const Value in0, const Value in1, if (!idx0 || !idx1 || *idx0 == *idx1) { return failure(); } + for (const Value c : ctrls) { + if (!contains(c)) { + return failure(); + } + } if (top) { forwardQubit(in0, out0); forwardQubit(in1, out1); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index 6a5d2cace0..0caf8d3b1d 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -115,9 +115,9 @@ class QuantumState { * @param out The qubit that in is changed to. * @param matrix The matrix to apply to the amplitudes of in. * @param ctrls The qubits that have to be |1> to apply the matrix. - * @return failure() if in is not in this group (a caller/propagation bug - - * the interpreter must co-locate a gate's targets before applying it); - * success() otherwise. + * @return failure() if in or a control is not in this group (a + * caller/propagation bug - the interpreter must co-locate a gate's targets and + * controls before applying it); success() otherwise. */ [[nodiscard("QuantumState::applyMatrix1Q called but ignored")]] LogicalResult applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, @@ -139,8 +139,9 @@ class QuantumState { * @param out1 The qubit that in1 is changed to. * @param matrix The matrix to apply to the amplitudes of in0 and in1. * @param ctrls The qubits that have to be |1> to apply the matrix. - * @return failure() if in0 or in1 is not in this group, or they are the same - * bit position (a caller/propagation bug); success() otherwise. + * @return failure() if in0, in1, or a control is not in this group, or in0 and + * in1 are the same bit position (a caller/propagation bug); success() + * otherwise. */ [[nodiscard("QuantumState::applyMatrix2Q called but ignored")]] LogicalResult applyMatrix2Q(Value in0, Value in1, Value out0, Value out1, diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 46cdeeb738..28cf05fdd7 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -177,6 +177,12 @@ TEST_F(QuantumStateTest, applyTwoQubitGateToSameBitFails) { .failed()); } +TEST_F(QuantumStateTest, applyWithControlNotInGroupFails) { + auto qs = QuantumState({q[0], q[1]}, 4); + EXPECT_TRUE( + qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {q[2]}).failed()); +} + TEST_F(QuantumStateTest, applyToQubitNotInGroupFailsEvenWhenTop) { auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 1); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); From 3d8ea29ae459d773ff95d3b46d5889c3a482a89c Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 28 Aug 2026 22:38:45 +0200 Subject: [PATCH 15/55] :construction: QuantumState renames input, assisted-by Sonnet 5 via Claude Code --- .../ConstantPropagation/QuantumState.cpp | 62 ++++++++++----- .../ConstantPropagation/QuantumState.hpp | 62 +++++++++------ .../ConstantPropagation/test_quantumState.cpp | 79 +++++++++++++------ 3 files changed, 137 insertions(+), 66 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index dae7c9e30a..4a8f1f13ba 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -83,6 +83,13 @@ void QuantumState::forwardQubit(const Value from, const Value to) { } } +void QuantumState::forwardQubits(const ArrayRef from, + const ArrayRef to) { + for (const auto [f, t] : llvm::zip(from, to)) { + forwardQubit(f, t); + } +} + void QuantumState::canonicalize() { if (top) { return; @@ -103,23 +110,25 @@ void QuantumState::canonicalize() { LogicalResult QuantumState::applyMatrix1Q(const Value in, const Value out, const Matrix2x2& matrix, - const ArrayRef ctrls) { + const ArrayRef ctrlsIn, + const ArrayRef ctrlsOut) { const auto idx = indexOf(in); - if (!idx) { + if (!idx || ctrlsOut.size() != ctrlsIn.size()) { return failure(); } - for (const Value c : ctrls) { + for (const Value c : ctrlsIn) { if (!contains(c)) { return failure(); } } if (top) { forwardQubit(in, out); + forwardQubits(ctrlsIn, ctrlsOut); return success(); } const uint64_t targetBit = uint64_t{1} << *idx; - const uint64_t ctrlMask = maskOf(ctrls); + const uint64_t ctrlMask = maskOf(ctrlsIn); llvm::DenseMap result; for (const auto& [key, amp] : amplitudes) { @@ -136,6 +145,7 @@ LogicalResult QuantumState::applyMatrix1Q(const Value in, const Value out, amplitudes = std::move(result); forwardQubit(in, out); + forwardQubits(ctrlsIn, ctrlsOut); canonicalize(); return success(); @@ -144,13 +154,14 @@ LogicalResult QuantumState::applyMatrix1Q(const Value in, const Value out, LogicalResult QuantumState::applyMatrix2Q(const Value in0, const Value in1, const Value out0, const Value out1, const Matrix4x4& matrix, - const ArrayRef ctrls) { + const ArrayRef ctrlsIn, + const ArrayRef ctrlsOut) { const auto idx0 = indexOf(in0); const auto idx1 = indexOf(in1); - if (!idx0 || !idx1 || *idx0 == *idx1) { + if (!idx0 || !idx1 || *idx0 == *idx1 || ctrlsOut.size() != ctrlsIn.size()) { return failure(); } - for (const Value c : ctrls) { + for (const Value c : ctrlsIn) { if (!contains(c)) { return failure(); } @@ -158,6 +169,7 @@ LogicalResult QuantumState::applyMatrix2Q(const Value in0, const Value in1, if (top) { forwardQubit(in0, out0); forwardQubit(in1, out1); + forwardQubits(ctrlsIn, ctrlsOut); return success(); } @@ -165,7 +177,7 @@ LogicalResult QuantumState::applyMatrix2Q(const Value in0, const Value in1, const uint64_t hiBit = uint64_t{1} << *idx0; const uint64_t loBit = uint64_t{1} << *idx1; const uint64_t bothBits = hiBit | loBit; - const uint64_t ctrlMask = maskOf(ctrls); + const uint64_t ctrlMask = maskOf(ctrlsIn); const auto localKey = [&](const uint64_t base, const unsigned local) { return base | ((local & 1U) != 0U ? loBit : 0) | @@ -192,42 +204,49 @@ LogicalResult QuantumState::applyMatrix2Q(const Value in0, const Value in1, amplitudes = std::move(result); forwardQubit(in0, out0); forwardQubit(in1, out1); + forwardQubits(ctrlsIn, ctrlsOut); canonicalize(); return success(); } -LogicalResult QuantumState::applyControlledPhase(const double phase, - const ArrayRef ctrls) { - if (ctrls.empty()) { +LogicalResult +QuantumState::applyControlledPhase(const double phase, + const ArrayRef ctrlsIn, + const ArrayRef ctrlsOut) { + if (ctrlsIn.empty() || + (!ctrlsOut.empty() && ctrlsOut.size() != ctrlsIn.size())) { return failure(); } - for (const Value c : ctrls) { + for (const Value c : ctrlsIn) { if (!contains(c)) { return failure(); } } if (top) { + forwardQubits(ctrlsIn, ctrlsOut); return success(); } - const uint64_t ctrlMask = maskOf(ctrls); + const uint64_t ctrlMask = maskOf(ctrlsIn); const Complex factor = std::exp(Complex{0.0, phase}); for (auto& [key, amp] : amplitudes) { if ((key & ctrlMask) == ctrlMask) { amp *= factor; } } + forwardQubits(ctrlsIn, ctrlsOut); canonicalize(); return success(); } FailureOr> -QuantumState::measure(const Value target) const { - const auto idx = indexOf(target); +QuantumState::measure(const Value in, const Value out) { + const auto idx = indexOf(in); if (!idx) { return failure(); } if (top) { + forwardQubit(in, out); return SmallVector{}; } const uint64_t targetBit = uint64_t{1} << *idx; @@ -251,12 +270,14 @@ QuantumState::measure(const Value target) const { auto branch = std::unique_ptr(new QuantumState(maxNonzeroAmplitudes)); branch->qubits = qubits; + branch->forwardQubit(in, out); const double scale = 1.0 / std::sqrt(probability); for (const auto& [key, amp] : amps) { branch->amplitudes[key] += amp * scale; } branch->canonicalize(); - return MeasurementOutcome{.bit=bit, .probability=probability, .state=std::move(branch)}; + return MeasurementOutcome{ + .bit = bit, .probability = probability, .state = std::move(branch)}; }; SmallVector outcomes; @@ -270,8 +291,8 @@ QuantumState::measure(const Value target) const { } FailureOr> -QuantumState::reset(const Value target) const { - auto outcomes = measure(target); +QuantumState::reset(const Value in, const Value out) { + auto outcomes = measure(in, out); if (failed(outcomes)) { return failure(); } @@ -279,7 +300,7 @@ QuantumState::reset(const Value target) const { if (outcome.bit == 0 || outcome.state == nullptr) { continue; } - const auto idx = outcome.state->indexOf(target); + const auto idx = outcome.state->indexOf(out); if (!idx) { return failure(); } @@ -299,8 +320,7 @@ QuantumState QuantumState::unify(const QuantumState& that) const { result.qubits.append(qubits.begin(), qubits.end()); result.qubits.append(that.qubits.begin(), that.qubits.end()); - if (top || that.top || - result.qubits.size() > MAX_GROUP_QUBITS || + if (top || that.top || result.qubits.size() > MAX_GROUP_QUBITS || amplitudes.size() * that.amplitudes.size() > maxNonzeroAmplitudes) { result.markTop(); return result; diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index 0caf8d3b1d..ea0bdc8de1 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -103,6 +103,10 @@ class QuantumState { /// not contain from. void forwardQubit(Value from, Value to); + /// @brief forwardQubit for each from[i] -> to[i]. to is empty (no rename) or + /// the same length as from. + void forwardQubits(ArrayRef from, ArrayRef to); + /** * @brief Applies a single-qubit unitary to qubit in, renaming it to qubit * out. @@ -114,14 +118,17 @@ class QuantumState { * @param in The qubit to apply the matrix to. * @param out The qubit that in is changed to. * @param matrix The matrix to apply to the amplitudes of in. - * @param ctrls The qubits that have to be |1> to apply the matrix. + * @param ctrlsIn The qubits that have to be |1> to apply the matrix. + * @param ctrlsOut The controls' post-gate names (empty = unchanged, else same + * length as ctrlsIn). * @return failure() if in or a control is not in this group (a - * caller/propagation bug - the interpreter must co-locate a gate's targets and - * controls before applying it); success() otherwise. + * caller/propagation bug - the interpreter must co-locate a gate's targets + * and controls before applying it), or the control in/out lengths mismatch; + * success() otherwise. */ [[nodiscard("QuantumState::applyMatrix1Q called but ignored")]] LogicalResult applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, - ArrayRef ctrls = {}); + ArrayRef ctrlsIn = {}, ArrayRef ctrlsOut = {}); /** * @brief Applies a two-qubit unitary to in0 and in1, renaming them to out0, @@ -138,14 +145,17 @@ class QuantumState { * @param out0 The qubit that in0 is changed to. * @param out1 The qubit that in1 is changed to. * @param matrix The matrix to apply to the amplitudes of in0 and in1. - * @param ctrls The qubits that have to be |1> to apply the matrix. - * @return failure() if in0, in1, or a control is not in this group, or in0 and - * in1 are the same bit position (a caller/propagation bug); success() - * otherwise. + * @param ctrlsIn The qubits that have to be |1> to apply the matrix. + * @param ctrlsOut The controls' post-gate names (empty = unchanged, else same + * length as ctrlsIn). + * @return failure() if in0, in1, or a control is not in this group, in0 and + * in1 are the same bit position, or the control in/out lengths mismatch (a + * caller/propagation bug); success() otherwise. */ [[nodiscard("QuantumState::applyMatrix2Q called but ignored")]] LogicalResult applyMatrix2Q(Value in0, Value in1, Value out0, Value out1, - const Matrix4x4& matrix, ArrayRef ctrls = {}); + const Matrix4x4& matrix, ArrayRef ctrlsIn = {}, + ArrayRef ctrlsOut = {}); /** * @brief Multiplies by exp(i*phase) the amplitudes where every control is @@ -157,37 +167,45 @@ class QuantumState { * and rejected here. * * @param phase The phase to apply. - * @param ctrls The qubits that all have to be |1> for the phase to apply. - * @return failure() if ctrls is empty or a control is not in this group. + * @param ctrlsIn The qubits that all have to be |1> for the phase to apply. + * @param ctrlsOut The controls' post-gate names (empty = unchanged, else same + * length as ctrlsIn). + * @return failure() if ctrlsIn is empty, a control is not in this group, or + * the control in/out lengths mismatch. */ [[nodiscard("QuantumState::applyControlledPhase called but ignored")]] - LogicalResult applyControlledPhase(double phase, ArrayRef ctrls); + LogicalResult applyControlledPhase(double phase, ArrayRef ctrlsIn, + ArrayRef ctrlsOut = {}); /** - * @brief Projective measurement of a target in the computational basis. + * @brief Projective measurement of qubit in in the computational basis. * - * Each branch's state is re-normalized and keeps the target (now definite). + * Each branch's state is re-normalized and holds the measured qubit (now + * definite) under its post-measurement name out. * - * @param target The target that is being measured. - * @return failure() if target is not in the group (a caller/propagation bug). + * @param in The qubit that is being measured. + * @param out The measured qubit's post-measurement name. + * @return failure() if in is not in the group (a caller/propagation bug). * Otherwise: an empty list if the state is top, one branch if the outcome is * deterministic, two branches otherwise. */ [[nodiscard("QuantumState::measure called but ignored")]] - FailureOr> measure(Value target) const; + FailureOr> measure(Value in, Value out); /** - * @brief Reset of a target: measure, then force the qubit to |0>. + * @brief Reset of qubit in: measure, then force the qubit to |0>. * - * Each branch's state is re-normalized and keeps the target (now in |0>). + * Each branch's state is re-normalized and holds the reset qubit (now |0>) + * under its post-reset name out. * - * @param target The target that is being reset. - * @return failure() if target is not in the group (a caller/propagation bug). + * @param in The qubit that is being reset. + * @param out The reset qubit's post-reset name. + * @return failure() if in is not in the group (a caller/propagation bug). * Otherwise: an empty list if the state is top, one branch if the outcome is * deterministic, two branches otherwise. */ [[nodiscard("QuantumState::reset called but ignored")]] - FailureOr> reset(Value target) const; + FailureOr> reset(Value in, Value out); /** * @brief Tensor product of this group with that. diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 28cf05fdd7..744979ab84 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -201,14 +201,16 @@ TEST_F(QuantumStateTest, controlledGateFires) { auto qs = QuantumState({q[0], q[1]}, 4); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); ASSERT_TRUE( - qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}).succeeded()); + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}) + .succeeded()); EXPECT_EQ(printed(qs), "|11> -> 1.00"); } TEST_F(QuantumStateTest, controlledGateDoesNotFire) { auto qs = QuantumState({q[0], q[1]}, 4); ASSERT_TRUE( - qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}).succeeded()); + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}) + .succeeded()); EXPECT_EQ(printed(qs), "|00> -> 1.00"); } @@ -216,10 +218,29 @@ TEST_F(QuantumStateTest, controlledGateOnSuperposition) { auto qs = QuantumState({q[0], q[1]}, 4); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); ASSERT_TRUE( - qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}).succeeded()); + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}) + .succeeded()); EXPECT_EQ(printed(qs), "|00> -> 0.71, |11> -> 0.71"); } +TEST_F(QuantumStateTest, appliedGateRenamesControls) { + auto qs = QuantumState({q[0], q[1]}, 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE( + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[2]}) + .succeeded()); + EXPECT_FALSE(qs.contains(q[0])); + EXPECT_TRUE(qs.contains(q[2])); + EXPECT_TRUE(qs.isAlwaysOne(q[1])); +} + +TEST_F(QuantumStateTest, controlInOutLengthMismatchFails) { + auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 4); + EXPECT_TRUE( + qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {q[1]}, {q[2], q[3]}) + .failed()); +} + //===----------------------------------------------------------------------===// // Amplitude budget //===----------------------------------------------------------------------===// @@ -228,7 +249,8 @@ TEST_F(QuantumStateTest, exceedingAmplitudeBudgetBecomesTop) { auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 2); ASSERT_TRUE(qs.applyMatrix1Q(q[3], q[3], hOp.getUnitaryMatrix()).succeeded()); ASSERT_TRUE( - qs.applyMatrix1Q(q[2], q[2], xOp.getUnitaryMatrix(), {q[3]}).succeeded()); + qs.applyMatrix1Q(q[2], q[2], xOp.getUnitaryMatrix(), {q[3]}, {q[3]}) + .succeeded()); EXPECT_FALSE(qs.isTop()); ASSERT_TRUE(qs.applyMatrix1Q(q[2], q[2], hOp.getUnitaryMatrix()).succeeded()); EXPECT_TRUE(qs.isTop()); @@ -269,8 +291,8 @@ TEST_F(QuantumStateTest, controlledPhaseOnQubitNotInGroupFails) { //===----------------------------------------------------------------------===// TEST_F(QuantumStateTest, measureDeterministicZero) { - const auto qs = QuantumState::singletonZero(q[0], 2); - const auto result = qs.measure(q[0]); + auto qs = QuantumState::singletonZero(q[0], 2); + const auto result = qs.measure(q[0], q[0]); ASSERT_TRUE(succeeded(result)); const auto& outcomes = *result; ASSERT_EQ(outcomes.size(), 1U); @@ -279,10 +301,19 @@ TEST_F(QuantumStateTest, measureDeterministicZero) { EXPECT_TRUE(*outcomes[0].state == qs); } +TEST_F(QuantumStateTest, measureRenamesMeasuredQubit) { + auto qs = QuantumState::singletonZero(q[0], 2); + const auto result = qs.measure(q[0], q[1]); + ASSERT_TRUE(succeeded(result)); + ASSERT_EQ(result->size(), 1U); + EXPECT_FALSE(result->front().state->contains(q[0])); + EXPECT_TRUE(result->front().state->contains(q[1])); +} + TEST_F(QuantumStateTest, measureDeterministicOne) { auto qs = QuantumState::singletonZero(q[0], 2); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); - const auto result = qs.measure(q[0]); + const auto result = qs.measure(q[0], q[0]); ASSERT_TRUE(succeeded(result)); const auto& outcomes = *result; ASSERT_EQ(outcomes.size(), 1U); @@ -295,8 +326,9 @@ TEST_F(QuantumStateTest, measureSuperpositionSplits) { auto qs = QuantumState({q[0], q[1]}, 4); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); ASSERT_TRUE( - qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}).succeeded()); - const auto result = qs.measure(q[0]); + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}) + .succeeded()); + const auto result = qs.measure(q[0], q[0]); ASSERT_TRUE(succeeded(result)); const auto& outcomes = *result; ASSERT_EQ(outcomes.size(), 2U); @@ -309,15 +341,15 @@ TEST_F(QuantumStateTest, measureSuperpositionSplits) { } TEST_F(QuantumStateTest, measureQubitNotInGroupFails) { - const auto qs = QuantumState::singletonZero(q[0], 2); - EXPECT_TRUE(failed(qs.measure(q[1]))); + auto qs = QuantumState::singletonZero(q[0], 2); + EXPECT_TRUE(failed(qs.measure(q[1], q[1]))); } TEST_F(QuantumStateTest, measureOnTopStateYieldsNoBranches) { auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 1); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); ASSERT_TRUE(qs.isTop()); - const auto result = qs.measure(q[0]); + const auto result = qs.measure(q[0], q[0]); ASSERT_TRUE(succeeded(result)); EXPECT_TRUE(result->empty()); } @@ -327,19 +359,21 @@ TEST_F(QuantumStateTest, measureOnTopStateYieldsNoBranches) { //===----------------------------------------------------------------------===// TEST_F(QuantumStateTest, resetDeterministicZero) { - const auto qs = QuantumState::singletonZero(q[0], 2); - const auto result = qs.reset(q[0]); + auto qs = QuantumState::singletonZero(q[0], 2); + const auto result = qs.reset(q[0], q[1]); ASSERT_TRUE(succeeded(result)); const auto& outcomes = *result; ASSERT_EQ(outcomes.size(), 1U); EXPECT_EQ(outcomes[0].bit, 0U); EXPECT_EQ(printed(*outcomes[0].state), "|0> -> 1.00"); + EXPECT_FALSE(outcomes[0].state->contains(q[0])); + EXPECT_TRUE(outcomes[0].state->contains(q[1])); } TEST_F(QuantumStateTest, resetDeterministicOneForcesZero) { auto qs = QuantumState::singletonZero(q[0], 2); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); - const auto result = qs.reset(q[0]); + const auto result = qs.reset(q[0], q[0]); ASSERT_TRUE(succeeded(result)); const auto& outcomes = *result; ASSERT_EQ(outcomes.size(), 1U); @@ -351,8 +385,8 @@ TEST_F(QuantumStateTest, resetSuperpositionForcesTargetToZero) { auto qs = QuantumState({q[0], q[1]}, 4); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); ASSERT_TRUE( - qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}).succeeded()); - const auto result = qs.reset(q[0]); + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}).succeeded()); + const auto result = qs.reset(q[0], q[0]); ASSERT_TRUE(succeeded(result)); const auto& outcomes = *result; ASSERT_EQ(outcomes.size(), 2U); @@ -362,15 +396,15 @@ TEST_F(QuantumStateTest, resetSuperpositionForcesTargetToZero) { } TEST_F(QuantumStateTest, resetQubitNotInGroupFails) { - const auto qs = QuantumState::singletonZero(q[0], 2); - EXPECT_TRUE(failed(qs.reset(q[1]))); + auto qs = QuantumState::singletonZero(q[0], 2); + EXPECT_TRUE(failed(qs.reset(q[1], q[1]))); } TEST_F(QuantumStateTest, resetOnTopStateYieldsNoBranches) { auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 1); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); ASSERT_TRUE(qs.isTop()); - const auto result = qs.reset(q[0]); + const auto result = qs.reset(q[0], q[0]); ASSERT_TRUE(succeeded(result)); EXPECT_TRUE(result->empty()); } @@ -419,7 +453,7 @@ TEST_F(QuantumStateTest, hasAlwaysZeroAmplitude) { auto qs = QuantumState({q[0], q[1]}, 4); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); ASSERT_TRUE( - qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}).succeeded()); + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}).succeeded()); EXPECT_TRUE(qs.hasAlwaysZeroAmplitude({{q[0], false}, {q[1], true}})); EXPECT_FALSE(qs.hasAlwaysZeroAmplitude({{q[0], true}, {q[1], true}})); } @@ -432,8 +466,7 @@ TEST_F(QuantumStateTest, equalityIgnoresNegligibleDifferences) { auto a = QuantumState({q[0], q[1], q[2], q[3]}, 4); const auto b = QuantumState({q[0], q[1], q[2], q[3]}, 4); ASSERT_TRUE(a.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); - ASSERT_TRUE(a.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()) - .succeeded()); + ASSERT_TRUE(a.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); EXPECT_TRUE(a == b); } From 04e9600188e1cd5cc5383fab7c90c11c05fbf642 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 28 Aug 2026 23:29:37 +0200 Subject: [PATCH 16/55] :construction: Added HybridState, assisted-by Sonnet 5 via Claude Code --- .../ConstantPropagation/HybridState.cpp | 355 ++++++++++++++++ .../ConstantPropagation/HybridState.hpp | 303 +++++++++++++ .../Transforms/Optimizations/CMakeLists.txt | 1 + .../ConstantPropagation/test_hybridState.cpp | 398 ++++++++++++++++++ 4 files changed, 1057 insertions(+) create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp create mode 100644 mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp new file mode 100644 index 0000000000..04adfca67e --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "HybridState.hpp" + +#include "QuantumState.hpp" +#include "mlir/Dialect/QCO/Utils/Matrix.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace mlir::qco { + +namespace { + +/// @brief Whether ctrlsOut is a valid rename target for ctrlsIn: empty (no +/// rename), or the same length. +bool ctrlRenameOk(const ArrayRef ctrlsIn, + const ArrayRef ctrlsOut) { + return ctrlsOut.empty() || ctrlsOut.size() == ctrlsIn.size(); +} + +/// @brief Truthiness of a resolved classical constant (non-zero == true), or +/// nullopt if attr is not an integer/index/bool/float constant. +std::optional classicalTruth(const Attribute attr) { + if (const auto ia = dyn_cast(attr)) { + return !ia.getValue().isZero(); + } + if (const auto fa = dyn_cast(attr)) { + return !fa.getValue().isZero(); + } + return std::nullopt; +} +} // namespace + +//===----------------------------------------------------------------------===// +// Observers +//===----------------------------------------------------------------------===// + +std::optional HybridState::getClassical(const Value v) const { + const auto it = classical.find(v); + if (it == classical.end()) { + return std::nullopt; + } + return it->second; +} + +//===----------------------------------------------------------------------===// +// Mutation +//===----------------------------------------------------------------------===// + +void HybridState::setClassical(const Value v, const Attribute attr) { + classical[v] = attr; +} + +HybridState HybridState::tensor(const HybridState& other) const { + HybridState result(state.unify(other.state), maxNonzeroAmplitudes, + probability * other.probability); + result.globalPhase = globalPhase * other.globalPhase; + result.classical = classical; + for (const auto& [v, attr] : other.classical) { + result.classical[v] = attr; + } + return result; +} + +//===----------------------------------------------------------------------===// +// Classical-control handling +//===----------------------------------------------------------------------===// + +FailureOr +HybridState::classicalControlsHold(const ArrayRef pos, + const ArrayRef neg) const { + for (const Value p : pos) { + const auto attr = getClassical(p); + if (!attr) { + return failure(); + } + const auto truth = classicalTruth(*attr); + if (!truth) { + return failure(); + } + if (!*truth) { + return false; + } + } + for (const Value n : neg) { + const auto attr = getClassical(n); + if (!attr) { + return failure(); + } + const auto truth = classicalTruth(*attr); + if (!truth) { + return failure(); + } + if (*truth) { + return false; + } + } + return true; +} + +//===----------------------------------------------------------------------===// +// Gate application +//===----------------------------------------------------------------------===// + +LogicalResult HybridState::applyMatrix1Q( + const Value in, const Value out, const Matrix2x2& matrix, + const ArrayRef quantumCtrlsIn, const ArrayRef quantumCtrlsOut, + const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) { + if (!ctrlRenameOk(quantumCtrlsIn, quantumCtrlsOut) || !state.contains(in)) { + return failure(); + } + const auto hold = classicalControlsHold(posClassicalCtrls, negClassicalCtrls); + if (failed(hold)) { + return failure(); + } + if (*hold) { + return state.applyMatrix1Q(in, out, matrix, quantumCtrlsIn, quantumCtrlsOut); + } + // Classical control false: the gate is skipped, only the identities thread on. + state.forwardQubit(in, out); + state.forwardQubits(quantumCtrlsIn, quantumCtrlsOut); + return success(); +} + +LogicalResult +HybridState::applyMatrix2Q(const Value in0, const Value in1, const Value out0, + const Value out1, const Matrix4x4& matrix, + const ArrayRef quantumCtrlsIn, + const ArrayRef quantumCtrlsOut, + const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) { + if (!ctrlRenameOk(quantumCtrlsIn, quantumCtrlsOut) || !state.contains(in0) || + !state.contains(in1)) { + return failure(); + } + const auto hold = classicalControlsHold(posClassicalCtrls, negClassicalCtrls); + if (failed(hold)) { + return failure(); + } + if (*hold) { + return state.applyMatrix2Q(in0, in1, out0, out1, matrix, quantumCtrlsIn, + quantumCtrlsOut); + } + state.forwardQubit(in0, out0); + state.forwardQubit(in1, out1); + state.forwardQubits(quantumCtrlsIn, quantumCtrlsOut); + return success(); +} + +LogicalResult +HybridState::addGlobalPhase(const double theta, + const ArrayRef quantumCtrlsIn, + const ArrayRef quantumCtrlsOut, + const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) { + if (!ctrlRenameOk(quantumCtrlsIn, quantumCtrlsOut)) { + return failure(); + } + const auto hold = classicalControlsHold(posClassicalCtrls, negClassicalCtrls); + if (failed(hold)) { + return failure(); + } + if (*hold) { + if (!quantumCtrlsIn.empty()) { + return state.applyControlledPhase(theta, quantumCtrlsIn, quantumCtrlsOut); + } + globalPhase *= std::exp(Complex{0.0, theta}); + return success(); + } + state.forwardQubits(quantumCtrlsIn, quantumCtrlsOut); + return success(); +} + +//===----------------------------------------------------------------------===// +// Measurement / reset +//===----------------------------------------------------------------------===// + +LogicalResult +HybridState::measureQubit(const Value in, const Value out, + const Value classicalResult, + const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) { + if (!state.contains(in)) { + return failure(); + } + const auto hold = classicalControlsHold(posClassicalCtrls, negClassicalCtrls); + if (failed(hold)) { + return failure(); + } + if (*hold) { + auto branches = state.measure(in, out); + if (failed(branches)) { + return failure(); + } + if (branches->size() == 1) { + const auto resultType = dyn_cast(classicalResult.getType()); + if (!resultType) { + return failure(); + } + setClassical(classicalResult, + IntegerAttr::get(resultType, branches->front().bit)); + state = std::move(*branches->front().state); + return success(); + } + if (branches->size() == 2) { + state.markTop(); // This will be handled in a later version + } + // branches->empty() => state was already top. + } + state.forwardQubit(in, out); + return success(); +} + +LogicalResult HybridState::resetQubit(const Value in, const Value out, + const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) { + if (!state.contains(in)) { + return failure(); + } + const auto hold = classicalControlsHold(posClassicalCtrls, negClassicalCtrls); + if (failed(hold)) { + return failure(); + } + if (*hold) { + auto branches = state.reset(in, out); + if (failed(branches)) { + return failure(); + } + // One outcome, or two that agree = `in` was unentangled: reset is exact and + // the branch state (already named `out`) is the result. Two that disagree = + // the reduced state after tracing out `in` is mixed. + if (branches->size() == 1) { + state = std::move(*branches->front().state); + return success(); + } + if (branches->size() == 2) { + state.markTop(); + } + // branches->empty() => state was already top. + } + state.forwardQubit(in, out); + return success(); +} + +//===----------------------------------------------------------------------===// +// Queries +//===----------------------------------------------------------------------===// + +bool HybridState::isQubitAlwaysZero(const Value q) const { + return state.isAlwaysZero(q); +} + +bool HybridState::isQubitAlwaysOne(const Value q) const { + return state.isAlwaysOne(q); +} + +bool HybridState::isClassicalTrue(const Value v) const { + const auto attr = getClassical(v); + return attr && classicalTruth(*attr).value_or(false); +} + +bool HybridState::isClassicalFalse(const Value v) const { + const auto attr = getClassical(v); + if (!attr) { + return false; + } + const auto truth = classicalTruth(*attr); + return truth && !*truth; +} + +bool HybridState::areControlsSatisfiable( + const ArrayRef quantumCtrls, const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) const { + for (const Value pc : posClassicalCtrls) { + if (isClassicalFalse(pc)) { + return false; + } + } + for (const Value nc : negClassicalCtrls) { + if (isClassicalTrue(nc)) { + return false; + } + } + if (quantumCtrls.empty()) { + return true; + } + SmallVector> assignment; + for (const Value qc : quantumCtrls) { + if (!state.contains(qc)) { + return false; + } + assignment.emplace_back(qc, true); + } + return !state.hasAlwaysZeroAmplitude(assignment); +} + +//===----------------------------------------------------------------------===// +// Comparison / dump +//===----------------------------------------------------------------------===// + +bool HybridState::operator==(const HybridState& other) const { + if (std::abs(probability - other.probability) > MATRIX_TOLERANCE || + std::abs(globalPhase - other.globalPhase) > MATRIX_TOLERANCE || + classical.size() != other.classical.size() || state != other.state) { + return false; + } + for (const auto& [v, attr] : classical) { + const auto it = other.classical.find(v); + if (it == other.classical.end() || it->second != attr) { + return false; + } + } + return true; +} + +void HybridState::print(raw_ostream& os) const { + os << "p=" << llvm::format("%.4f", probability); + if (std::abs(globalPhase - Complex{1.0, 0.0}) > MATRIX_TOLERANCE) { + os << " phase=(" << llvm::format("%.4f", globalPhase.real()) << "," + << llvm::format("%.4f", globalPhase.imag()) << ")"; + } + os << " ["; + state.print(os); + os << "]"; + if (!classical.empty()) { + os << " classical:"; + for (const auto& [v, attr] : classical) { + os << " " << attr; + } + } +} + +} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp new file mode 100644 index 0000000000..f7511ec5b0 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "QuantumState.hpp" + +#include +#include +#include +#include +#include + +#include + +namespace mlir::qco { + +/** + * @brief One correlated subsystem-alternative. + * + * Holds a single QuantumState (one entanglement group's qubits, possibly none) + * together with the classical values correlated with it, this branch's + * probability, and its accumulated globalPhase. + * + * The enclosing UnionTable owns the partition: two HybridStates, whose qubit + * sets are disjoint, are tensor factors, two whose qubit sets are equal are + * alternatives of one probabilistic disjunction. A HybridState never sees a + * qubit outside its own state. + * + * Every mutating operation accepts positive and negative classical controls; if + * they do not hold in this branch, the operation is skipped (the qubit renames + * still happen). An unresolved classical control is a failure(). + */ +class HybridState { + size_t maxNonzeroAmplitudes; + double probability; + Complex globalPhase{1.0, 0.0}; + QuantumState state; + llvm::DenseMap classical; + + /** Whether the classical controls permit the operation in this branch: + * failure() if any is unresolved, else true (apply) / false (skip). + * @param pos The classical values that need to be true (i.e., nonzero). + * @param neg The classical values that need to be false (i.e., zero). + * + * @returns failure if one of the given Values is not present in the state. + * Success and whether the controls hold if all values are present. + */ + [[nodiscard( + "HybridState::classicalControlsHold called but ignored")]] FailureOr + classicalControlsHold(ArrayRef pos, ArrayRef neg) const; + +public: + /** + * @param state The quantum state of this subsystem (may hold no qubits). + * @param maxNonzeroAmplitudes Budget for QuantumStates created here (reset). + * @param probability This alternative's weight within its slot (1 if sole). + */ + HybridState(QuantumState state, const size_t maxNonzeroAmplitudes, + const double probability) + : maxNonzeroAmplitudes(maxNonzeroAmplitudes), probability(probability), + state(std::move(state)) {} + + //===--------------------------------------------------------------------===// + // Observers + //===--------------------------------------------------------------------===// + + /// @brief Whether the quantum state of this branch is top. + [[nodiscard("HybridState::isTop called but ignored")]] bool isTop() const { + return state.isTop(); + } + + [[nodiscard("HybridState::getProbability called but ignored")]] double + getProbability() const { + return probability; + } + [[nodiscard("HybridState::getGlobalPhase called but ignored")]] Complex + getGlobalPhase() const { + return globalPhase; + } + // @brief The qubits this subsystem covers (the UnionTable's partition unit). + [[nodiscard("HybridState::getQubits called but ignored")]] ArrayRef + getQubits() const { + return state.getQubits(); + } + [[nodiscard("HybridState::hasQubit called but ignored")]] bool + hasQubit(const Value q) const { + return state.contains(q); + } + [[nodiscard("HybridState::getClassical called but ignored")]] + std::optional getClassical(Value v) const; + + //===--------------------------------------------------------------------===// + // Mutation + //===--------------------------------------------------------------------===// + + /** + * @brief Records the resolved constant of a classical value (overwrites). + * + * @param v The value whose attribute is changed. + * @param attr The new attribute for v. + */ + void setClassical(Value v, Attribute attr); + + /** + * @brief Multiplies this branch's probability by factor. + * + * @param factor The factor to multiply the probability with. + */ + void scaleProbability(const double factor) { probability *= factor; } + + /** + * @brief Combines this subsystem with a disjoint one into a single + * HybridState. + * + * The qubit sets must be disjointed. Probabilities and global phases + * multiply; classical maps merge (other wins on a key collision). Becomes top + * if the tensor product exceeds the amplitude budget. + * + * @param other The Hybrid state to merge this HybridState with. + */ + [[nodiscard("HybridState::tensor called but ignored")]] HybridState + tensor(const HybridState& other) const; + + //===--------------------------------------------------------------------===// + // Gate application + //===--------------------------------------------------------------------===// + + /** + * @brief Applies a single-qubit unitary to in (renamed to out). + * + * in and all quantum controls must already be in this branch's state. + * quantumCtrlsOut, if non-empty, matches quantumCtrlsIn in size and gives the + * controls' post-gate values. + * + * @param in The qubit to apply the matrix to. + * @param out The qubit that in is changed to. + * @param matrix The matrix to apply to the amplitudes of in. + * @param quantumCtrlsIn The qubits that have to be |1> to apply the matrix. + * @param quantumCtrlsOut The qubits that quantumCtrlsIn are changed to. + * @param posClassicalCtrls The classical values that have to be true + * (nonzero) to apply the matrix. + * @param negClassicalCtrls The classical values that have to be false (zero) + * to apply the matrix. + * @return failure() if a target/control qubit is not in this state, the + * control in/out lengths mismatch, or a classical control is unresolved. + */ + [[nodiscard("HybridState::applyMatrix1Q called but ignored")]] + LogicalResult applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, + ArrayRef quantumCtrlsIn = {}, + ArrayRef quantumCtrlsOut = {}, + ArrayRef posClassicalCtrls = {}, + ArrayRef negClassicalCtrls = {}); + + /** + * @brief Applies a two-qubit unitary to in (renamed to out). + * + * in0, in1, and all quantum controls must already be in this branch's state. + * quantumCtrlsOut, if non-empty, matches quantumCtrlsIn in size and gives the + * controls' post-gate values. + * + * @param in0 The high qubit to apply the matrix to. + * @param in1 The low qubit to apply the matrix to. + * @param out0 The qubit that in0 is changed to. + * @param out1 The qubit that in1 is changed to. + * @param matrix The matrix to apply to the amplitudes of in. + * @param quantumCtrlsIn The qubits that have to be |1> to apply the matrix. + * @param quantumCtrlsOut The qubits that quantumCtrlsIn are changed to. + * @param posClassicalCtrls The classical values that have to be true + * (nonzero) to apply the matrix. + * @param negClassicalCtrls The classical values that have to be false (zero) + * to apply the matrix. + * @return failure() if a target/control qubit is not in this state, the + * control in/out lengths mismatch, or a classical control is unresolved. + */ + [[nodiscard("HybridState::applyMatrix2Q called but ignored")]] + LogicalResult applyMatrix2Q(Value in0, Value in1, Value out0, Value out1, + const Matrix4x4& matrix, + ArrayRef quantumCtrlsIn = {}, + ArrayRef quantumCtrlsOut = {}, + ArrayRef posClassicalCtrls = {}, + ArrayRef negClassicalCtrls = {}); + + /** + * @brief Adds a global phase exp(i*theta). + * + * Uncontrolled: accumulated into globalPhase. With quantum controls: a + * relative phase on the subspace where every control is |1>. + * + * @param theta The phase to add. + * @param quantumCtrlsIn The qubits that have to be |1> to apply the matrix. + * @param quantumCtrlsOut The qubits that quantumCtrlsIn are changed to. + * @param posClassicalCtrls The classical values that have to be true + * (nonzero) to apply the matrix. + * @param negClassicalCtrls The classical values that have to be false (zero) + * to apply the matrix. + * @return failure() if a control qubit is not in this state or a classical + * control is unresolved. + */ + [[nodiscard("HybridState::addGlobalPhase called but ignored")]] + LogicalResult addGlobalPhase(double theta, + ArrayRef quantumCtrlsIn = {}, + ArrayRef quantumCtrlsOut = {}, + ArrayRef posClassicalCtrls = {}, + ArrayRef negClassicalCtrls = {}); + + //===--------------------------------------------------------------------===// + // Measurement / reset + //===--------------------------------------------------------------------===// + + /** + * @brief Measures in (renamed to out), recording the outcome in + * classicalResult. + * + * If in is deterministic, classicalResult is set to the exact i1 value; + * otherwise the state is marked top, and classicalResult left unknown. + * + * @param in The qubit to be measured, + * @param out The value to change in to. + * @param classicalResult The classical value to save the result of the + * measurement in. + * @param posClassicalCtrls The classical values that have to be true + * (nonzero) to apply the matrix. + * @param negClassicalCtrls The classical values that have to be false (zero) + * to apply the matrix. + * @return failure() if in is not in this state or a classical control is + * unresolved. + */ + [[nodiscard("HybridState::measureQubit called but ignored")]] + LogicalResult measureQubit(Value in, Value out, Value classicalResult, + ArrayRef posClassicalCtrls = {}, + ArrayRef negClassicalCtrls = {}); + + /** + * @brief Resets in to |0> (renamed to out). + * + * Exact when this state is deterministic; otherwise the state is marked top + * (the reduced state is mixed). + * + * @param in The qubit to be measured, + * @param out The value to change in to. + * @param posClassicalCtrls The classical values that have to be true + * (nonzero) to apply the matrix. + * @param negClassicalCtrls The classical values that have to be false (zero) + * to apply the matrix. + * @return failure() if in is not in this state or a classical control is + * unresolved. + */ + [[nodiscard("HybridState::resetQubit called but ignored")]] LogicalResult + resetQubit(Value in, Value out, ArrayRef posClassicalCtrls = {}, + ArrayRef negClassicalCtrls = {}); + + //===--------------------------------------------------------------------===// + // Queries + //===--------------------------------------------------------------------===// + + [[nodiscard("HybridState::isAlwaysZero called but ignored")]] bool + isQubitAlwaysZero(Value q) const; + [[nodiscard("HybridState::isAlwaysOne called but ignored")]] bool + isQubitAlwaysOne(Value q) const; + + /// @brief Whether v is a known non-zero classical constant in this branch. + [[nodiscard("HybridState::isClassicalTrue called but ignored")]] bool + isClassicalTrue(Value v) const; + /// @brief Whether v is a known zero classical constant in this branch. + [[nodiscard("HybridState::isClassicalFalse called but ignored")]] bool + isClassicalFalse(Value v) const; + + /** + * Whether the given controls can all hold simultaneously in this branch + * (positive classical not provably false, negative not provably true, quantum + * controls jointly possible). + * + * @param quantumCtrls The qubits that have to be |1>. + * @param posClassicalCtrls The classical values that have to be true + * (nonzero) to apply the matrix. + * @param negClassicalCtrls The classical values that have to be false (zero) + * to apply the matrix. + */ + [[nodiscard("HybridState::areControlsSatisfiable called but ignored")]] bool + areControlsSatisfiable(ArrayRef quantumCtrls, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) const; + + //===--------------------------------------------------------------------===// + // Comparison / dump + //===--------------------------------------------------------------------===// + + /// @brief Structural equality within @ref MATRIX_TOLERANCE (join de-dup). + [[nodiscard("HybridState::== called but ignored")]] bool + operator==(const HybridState& other) const; + + void print(raw_ostream& os) const; +}; + +} // namespace mlir::qco diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index 9e46c15a29..c4a7d05b75 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -9,6 +9,7 @@ set(target_name mqt-core-mlir-unittest-optimizations) add_executable( ${target_name} + ConstantPropagation/test_hybridState.cpp ConstantPropagation/test_quantumState.cpp test_qco_hadamard_lifting.cpp test_qco_measurement_lifting.cpp diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp new file mode 100644 index 0000000000..28896f6696 --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "ConstantPropagation/HybridState.hpp" +#include "ConstantPropagation/QuantumState.hpp" +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace { + +using namespace mlir; +using namespace mlir::qco; + +constexpr size_t BUDGET = 16; + +std::string printed(const HybridState& hs) { + std::string s; + llvm::raw_string_ostream os(s); + hs.print(os); + return s; +} + +class HybridStateTest : public testing::Test { +protected: + MLIRContext context; + QCOProgramBuilder builder; + + std::array q{}; + Value cA; + Value cB; + HOp hOp; + XOp xOp; + DCXOp dcxOp; + + HybridStateTest() : builder(&context) {} + + void SetUp() override { + DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + + builder.initialize(); + auto reg = builder.allocQubitRegister(4); + for (size_t i = 0; i < q.size(); ++i) { + q[i] = reg[i]; + } + cA = builder.boolConstant(false); + cB = builder.boolConstant(true); + const auto qt = q[0].getType(); + hOp = HOp::create(builder, builder.getLoc(), qt, q[0]); + xOp = XOp::create(builder, builder.getLoc(), qt, q[0]); + dcxOp = DCXOp::create(builder, builder.getLoc(), qt, qt, q[0], q[1]); + } + + static HybridState make(const ArrayRef qubits, + const double probability = 1.0) { + return HybridState(QuantumState(qubits, BUDGET), BUDGET, probability); + } +}; + +//===----------------------------------------------------------------------===// +// Construction / classical values +//===----------------------------------------------------------------------===// + +TEST_F(HybridStateTest, holdsItsQubits) { + const auto hs = make({q[0], q[1]}); + EXPECT_TRUE(hs.hasQubit(q[0])); + EXPECT_FALSE(hs.hasQubit(q[2])); + EXPECT_EQ(hs.getQubits().size(), 2U); + EXPECT_TRUE(hs.isQubitAlwaysZero(q[0])); + EXPECT_FALSE(hs.isTop()); +} + +TEST_F(HybridStateTest, setAndGetClassical) { + auto hs = make({}); + EXPECT_FALSE(hs.getClassical(cA).has_value()); + + hs.setClassical(cA, builder.getBoolAttr(true)); + ASSERT_TRUE(hs.getClassical(cA).has_value()); + EXPECT_TRUE(hs.isClassicalTrue(cA)); + EXPECT_FALSE(hs.isClassicalFalse(cA)); + + hs.setClassical(cA, builder.getBoolAttr(false)); + EXPECT_FALSE(hs.isClassicalTrue(cA)); + EXPECT_TRUE(hs.isClassicalFalse(cA)); +} + +//===----------------------------------------------------------------------===// +// Gate application +//===----------------------------------------------------------------------===// + +TEST_F(HybridStateTest, applyMatrix1Q) { + auto hs = make({q[0]}); + ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + EXPECT_FALSE(hs.isQubitAlwaysZero(q[0])); +} + +TEST_F(HybridStateTest, applyToQubitNotInStateFails) { + auto hs = make({q[0]}); + EXPECT_TRUE(hs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix()).failed()); +} + +TEST_F(HybridStateTest, applyMatrix2Q) { + auto hs = make({q[0], q[1]}); + ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(hs.applyMatrix2Q(q[0], q[1], q[0], q[1], dcxOp.getUnitaryMatrix()) + .succeeded()); + EXPECT_TRUE(hs.isQubitAlwaysZero(q[0])); + EXPECT_TRUE(hs.isQubitAlwaysOne(q[1])); +} + +TEST_F(HybridStateTest, quantumControlledGate) { + auto hs = make({q[0], q[1]}); + ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE( + hs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}) + .succeeded()); + EXPECT_TRUE(hs.isQubitAlwaysOne(q[1])); +} + +TEST_F(HybridStateTest, controlRenameUpdatesState) { + auto hs = make({q[0], q[1]}); + ASSERT_TRUE( + hs.applyMatrix1Q(q[1], q[3], xOp.getUnitaryMatrix(), {q[0]}, {q[2]}) + .succeeded()); + EXPECT_FALSE(hs.hasQubit(q[0])); + EXPECT_FALSE(hs.hasQubit(q[1])); + EXPECT_TRUE(hs.hasQubit(q[2])); + EXPECT_TRUE(hs.hasQubit(q[3])); +} + +TEST_F(HybridStateTest, controlInOutLengthMismatchFails) { + auto hs = make({q[0], q[1]}); + EXPECT_TRUE( + hs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[2], q[3]}) + .failed()); +} + +//===----------------------------------------------------------------------===// +// Classical controls +//===----------------------------------------------------------------------===// + +TEST_F(HybridStateTest, positiveClassicalControlHoldsAppliesGate) { + auto hs = make({q[0]}); + hs.setClassical(cA, builder.getBoolAttr(true)); + ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {}, {}, {cA}) + .succeeded()); + EXPECT_TRUE(hs.isQubitAlwaysOne(q[0])); +} + +TEST_F(HybridStateTest, positiveClassicalControlFailsSkipsGateButRenames) { + auto hs = make({q[0]}); + hs.setClassical(cA, builder.getBoolAttr(false)); + ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[1], xOp.getUnitaryMatrix(), {}, {}, {cA}) + .succeeded()); + EXPECT_FALSE(hs.hasQubit(q[0])); + EXPECT_TRUE(hs.hasQubit(q[1])); + EXPECT_TRUE(hs.isQubitAlwaysZero(q[1])); +} + +TEST_F(HybridStateTest, negativeClassicalControlHoldsAppliesGate) { + auto hs = make({q[0]}); + hs.setClassical(cA, builder.getBoolAttr(false)); + ASSERT_TRUE( + hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {}, {}, {}, {cA}) + .succeeded()); + EXPECT_TRUE(hs.isQubitAlwaysOne(q[0])); +} + +TEST_F(HybridStateTest, negativeClassicalControlFailsSkipsGate) { + auto hs = make({q[0]}); + hs.setClassical(cA, builder.getBoolAttr(true)); + ASSERT_TRUE( + hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {}, {}, {}, {cA}) + .succeeded()); + EXPECT_TRUE(hs.isQubitAlwaysZero(q[0])); +} + +TEST_F(HybridStateTest, unresolvedClassicalControlFails) { + auto hs = make({q[0]}); + EXPECT_TRUE(hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {}, {}, {cA}) + .failed()); +} + +TEST_F(HybridStateTest, floatClassicalControlIsSupported) { + auto hs = make({q[0]}); + const Value fc = builder.floatConstant(2.5); + + hs.setClassical(fc, builder.getF64FloatAttr(2.5)); + EXPECT_TRUE(hs.isClassicalTrue(fc)); + ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {}, {}, {fc}) + .succeeded()); + EXPECT_TRUE(hs.isQubitAlwaysOne(q[0])); + + hs.setClassical(fc, builder.getF64FloatAttr(0.0)); + EXPECT_TRUE(hs.isClassicalFalse(fc)); +} + +//===----------------------------------------------------------------------===// +// Global phase +//===----------------------------------------------------------------------===// + +TEST_F(HybridStateTest, uncontrolledGlobalPhaseAccumulates) { + auto hs = make({q[0]}); + ASSERT_TRUE(hs.addGlobalPhase(std::acos(-1.0)).succeeded()); + EXPECT_LT(std::abs(hs.getGlobalPhase() - Complex{-1.0, 0.0}), 1e-9); +} + +TEST_F(HybridStateTest, quantumControlledPhaseIsNotGlobal) { + auto hs = make({q[0], q[1]}); + ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(hs.addGlobalPhase(std::acos(-1.0), {q[0]}).succeeded()); + EXPECT_LT(std::abs(hs.getGlobalPhase() - Complex{1.0, 0.0}), 1e-9); +} + +TEST_F(HybridStateTest, globalPhaseSkippedByClassicalControl) { + auto hs = make({q[0]}); + hs.setClassical(cA, builder.getBoolAttr(false)); + ASSERT_TRUE(hs.addGlobalPhase(std::acos(-1.0), {}, {}, {cA}).succeeded()); + EXPECT_LT(std::abs(hs.getGlobalPhase() - Complex{1.0, 0.0}), 1e-9); +} + +//===----------------------------------------------------------------------===// +// tensor +//===----------------------------------------------------------------------===// + +TEST_F(HybridStateTest, tensorCombinesDisjointSubsystems) { + auto a = make({q[0]}); + ASSERT_TRUE(a.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + a.setClassical(cA, builder.getBoolAttr(true)); + auto b = make({q[1]}); + b.setClassical(cB, builder.getBoolAttr(false)); + + const auto ab = a.tensor(b); + EXPECT_EQ(ab.getQubits().size(), 2U); + EXPECT_TRUE(ab.hasQubit(q[0])); + EXPECT_TRUE(ab.hasQubit(q[1])); + EXPECT_TRUE(ab.isClassicalTrue(cA)); + EXPECT_TRUE(ab.isClassicalFalse(cB)); +} + +TEST_F(HybridStateTest, tensorMultipliesProbabilities) { + const auto a = make({q[0]}, 0.5); + const auto b = make({q[1]}, 0.5); + EXPECT_DOUBLE_EQ(a.tensor(b).getProbability(), 0.25); +} + +//===----------------------------------------------------------------------===// +// Measurement +//===----------------------------------------------------------------------===// + +TEST_F(HybridStateTest, measureDeterministicRecordsClassical) { + auto hs = make({q[0]}); + ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(hs.measureQubit(q[0], q[1], cA).succeeded()); + EXPECT_TRUE(hs.isClassicalTrue(cA)); + EXPECT_TRUE(hs.hasQubit(q[1])); + EXPECT_FALSE(hs.hasQubit(q[0])); +} + +TEST_F(HybridStateTest, measureSuperpositionTopsAndLeavesResultUnknown) { + auto hs = make({q[0]}); + ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(hs.measureQubit(q[0], q[1], cA).succeeded()); + EXPECT_FALSE(hs.getClassical(cA).has_value()); + EXPECT_TRUE(hs.isTop()); +} + +TEST_F(HybridStateTest, measureSkippedByClassicalControl) { + auto hs = make({q[0]}); + hs.setClassical(cB, builder.getBoolAttr(false)); + ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(hs.measureQubit(q[0], q[1], cA, {cB}).succeeded()); + EXPECT_FALSE(hs.getClassical(cA).has_value()); + EXPECT_TRUE(hs.hasQubit(q[1])); +} + +TEST_F(HybridStateTest, measureUnseededFails) { + auto hs = make({q[0]}); + EXPECT_TRUE(hs.measureQubit(q[1], q[2], cA).failed()); +} + +//===----------------------------------------------------------------------===// +// Reset +//===----------------------------------------------------------------------===// + +TEST_F(HybridStateTest, resetSingletonForcesZero) { + auto hs = make({q[0]}); + ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(hs.resetQubit(q[0], q[1]).succeeded()); + EXPECT_TRUE(hs.isQubitAlwaysZero(q[1])); +} + +TEST_F(HybridStateTest, resetDeterministicOneInLargerState) { + auto hs = make({q[0], q[1]}); + ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE( + hs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}) + .succeeded()); + ASSERT_TRUE(hs.resetQubit(q[0], q[2]).succeeded()); + EXPECT_TRUE(hs.isQubitAlwaysZero(q[2])); + EXPECT_TRUE(hs.isQubitAlwaysOne(q[1])); + EXPECT_FALSE(hs.isTop()); +} + +TEST_F(HybridStateTest, resetSuperpositionInLargerStateTops) { + auto hs = make({q[0], q[1]}); + ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE( + hs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}) + .succeeded()); + ASSERT_TRUE(hs.resetQubit(q[0], q[2]).succeeded()); + EXPECT_TRUE(hs.isTop()); +} + +TEST_F(HybridStateTest, resetUnseededFails) { + auto hs = make({q[0]}); + EXPECT_TRUE(hs.resetQubit(q[1], q[2]).failed()); +} + +//===----------------------------------------------------------------------===// +// Control satisfiability +//===----------------------------------------------------------------------===// + +TEST_F(HybridStateTest, controlsSatisfiableClassical) { + auto hs = make({}); + hs.setClassical(cA, builder.getBoolAttr(true)); + hs.setClassical(cB, builder.getBoolAttr(false)); + EXPECT_TRUE(hs.areControlsSatisfiable({}, {cA}, {cB})); + EXPECT_FALSE(hs.areControlsSatisfiable({}, {cB}, {})); + EXPECT_FALSE(hs.areControlsSatisfiable({}, {}, {cA})); +} + +TEST_F(HybridStateTest, controlsSatisfiableQuantum) { + auto hs = make({q[0], q[1]}); + ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + EXPECT_TRUE(hs.areControlsSatisfiable({q[0]}, {}, {})); + EXPECT_FALSE(hs.areControlsSatisfiable({q[1]}, {}, {})); + + ASSERT_TRUE(hs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix()).succeeded()); + EXPECT_TRUE(hs.areControlsSatisfiable({q[0], q[1]}, {}, {})); +} + +TEST_F(HybridStateTest, controlsSatisfiableQubitNotInStateIsFalse) { + const auto hs = make({q[0]}); + EXPECT_FALSE(hs.areControlsSatisfiable({q[1]}, {}, {})); +} + +//===----------------------------------------------------------------------===// +// Equality / print +//===----------------------------------------------------------------------===// + +TEST_F(HybridStateTest, equalityConsidersEverything) { + auto a = make({q[0]}, 0.5); + auto b = make({q[0]}, 0.5); + EXPECT_TRUE(a == b); + + EXPECT_FALSE(a == make({q[0]}, 0.25)); + + b.setClassical(cA, builder.getBoolAttr(true)); + EXPECT_FALSE(a == b); + + auto c = make({q[0]}, 0.5); + ASSERT_TRUE(c.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + EXPECT_FALSE(a == c); +} + +TEST_F(HybridStateTest, printIsNonEmpty) { + auto hs = make({q[0]}); + hs.setClassical(cA, builder.getBoolAttr(false)); + EXPECT_NE(printed(hs).find("p=1.0000"), std::string::npos); +} + +} // namespace From 2f42ce10137fc14c0d0cf38683c95e740fbd46eb Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sat, 29 Aug 2026 08:29:32 +0200 Subject: [PATCH 17/55] :construction: Added functions for HybridState, assisted-by Sonnet 5 via Claude Code --- .../ConstantPropagation/HybridState.cpp | 22 ++++++++++-- .../ConstantPropagation/HybridState.hpp | 34 +++++++++++++++++- .../ConstantPropagation/test_hybridState.cpp | 35 +++++++++++++++++++ 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 04adfca67e..1682d4b4f3 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -72,6 +72,18 @@ void HybridState::setClassical(const Value v, const Attribute attr) { classical[v] = attr; } +void HybridState::forwardValue(const Value from, const Value to) { + state.forwardQubit(from, to); + const auto it = classical.find(from); + if (it != classical.end()) { + const Attribute attr = it->second; + classical.erase(it); + classical[to] = attr; + } +} + +void HybridState::markStateTop() { state.markTop(); } + HybridState HybridState::tensor(const HybridState& other) const { HybridState result(state.unify(other.state), maxNonzeroAmplitudes, probability * other.probability); @@ -320,9 +332,8 @@ bool HybridState::areControlsSatisfiable( // Comparison / dump //===----------------------------------------------------------------------===// -bool HybridState::operator==(const HybridState& other) const { - if (std::abs(probability - other.probability) > MATRIX_TOLERANCE || - std::abs(globalPhase - other.globalPhase) > MATRIX_TOLERANCE || +bool HybridState::sameConfiguration(const HybridState& other) const { + if (std::abs(globalPhase - other.globalPhase) > MATRIX_TOLERANCE || classical.size() != other.classical.size() || state != other.state) { return false; } @@ -335,6 +346,11 @@ bool HybridState::operator==(const HybridState& other) const { return true; } +bool HybridState::operator==(const HybridState& other) const { + return std::abs(probability - other.probability) <= MATRIX_TOLERANCE && + sameConfiguration(other); +} + void HybridState::print(raw_ostream& os) const { os << "p=" << llvm::format("%.4f", probability); if (std::abs(globalPhase - Complex{1.0, 0.0}) > MATRIX_TOLERANCE) { diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index f7511ec5b0..0638908652 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -109,6 +109,15 @@ class HybridState { */ void setClassical(Value v, Attribute attr); + /** + * @brief Renames from to to, whether it is this branch's qubit or one of its + * classical keys. No-op if from is not present. + * + * @param from The value being replaced. + * @param to The value it is replaced with. + */ + void forwardValue(Value from, Value to); + /** * @brief Multiplies this branch's probability by factor. * @@ -116,6 +125,18 @@ class HybridState { */ void scaleProbability(const double factor) { probability *= factor; } + /** + * @brief Sets this branch's probability (its weight within its slot). + * + * @param newProbability The new probability. + */ + void setProbability(const double newProbability) { + probability = newProbability; + } + + /// @brief Collapses this branch's QuantumState to top; classical facts stay. + void markStateTop(); + /** * @brief Combines this subsystem with a disjoint one into a single * HybridState. @@ -293,7 +314,18 @@ class HybridState { // Comparison / dump //===--------------------------------------------------------------------===// - /// @brief Structural equality within @ref MATRIX_TOLERANCE (join de-dup). + /** + * Whether the two branches carry the same state, global phase, and classical + * facts - everything except their probability. The de-dup key when merging + * alternatives in UnionTable::join. + * + * @param other The HybridState to compare this one with. + */ + [[nodiscard("HybridState::sameConfiguration called but ignored")]] bool + sameConfiguration(const HybridState& other) const; + + /// @brief sameConfiguration and equal probability, both within + /// MATRIX_TOLERANCE. [[nodiscard("HybridState::== called but ignored")]] bool operator==(const HybridState& other) const; diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp index 28896f6696..b8d9c4b4d6 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -389,6 +389,41 @@ TEST_F(HybridStateTest, equalityConsidersEverything) { EXPECT_FALSE(a == c); } +TEST_F(HybridStateTest, sameConfigurationIgnoresProbability) { + auto a = make({q[0]}, 0.5); + const auto b = make({q[0]}, 0.25); + EXPECT_TRUE(a.sameConfiguration(b)); + EXPECT_FALSE(a == b); + + a.setClassical(cA, builder.getBoolAttr(true)); + EXPECT_FALSE(a.sameConfiguration(b)); +} + +TEST_F(HybridStateTest, setProbabilityReplacesTheWeight) { + auto hs = make({q[0]}, 0.5); + hs.setProbability(0.2); + EXPECT_DOUBLE_EQ(hs.getProbability(), 0.2); +} + +TEST_F(HybridStateTest, markStateTopKeepsClassicalFacts) { + auto hs = make({q[0]}); + hs.setClassical(cA, builder.getBoolAttr(true)); + hs.markStateTop(); + EXPECT_TRUE(hs.isTop()); + EXPECT_TRUE(hs.isClassicalTrue(cA)); +} + +TEST_F(HybridStateTest, forwardValueRenamesQubitAndClassical) { + auto hs = make({q[0]}); + hs.setClassical(cA, builder.getBoolAttr(true)); + hs.forwardValue(q[0], q[1]); + hs.forwardValue(cA, cB); + EXPECT_FALSE(hs.hasQubit(q[0])); + EXPECT_TRUE(hs.hasQubit(q[1])); + EXPECT_FALSE(hs.getClassical(cA).has_value()); + EXPECT_TRUE(hs.isClassicalTrue(cB)); +} + TEST_F(HybridStateTest, printIsNonEmpty) { auto hs = make({q[0]}); hs.setClassical(cA, builder.getBoolAttr(false)); From 73026267b8212ab7e2e989a1b6de3781904cfff5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sat, 29 Aug 2026 08:59:44 +0200 Subject: [PATCH 18/55] :construction: Added UnionTable.hpp, assisted-by Sonnet 5 via Claude Code --- .../ConstantPropagation/UnionTable.hpp | 377 ++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp new file mode 100644 index 0000000000..12c1419b8d --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "HybridState.hpp" +#include "QuantumState.hpp" + +#include +#include +#include +#include +#include +#include + +namespace mlir::qco { + +/** + * @brief Which controls a controlled operation could drop without changing its + * effect on the current state. + * + * completelySuperfluous means the operation can never fire (some control is + * provably unsatisfiable), so the whole operation is dead. Otherwise, the two + * sets list the individual controls that always hold and can be stripped. + */ +struct SuperfluousResult { + bool completelySuperfluous = false; + llvm::DenseSet superfluousQubits; + llvm::DenseSet superfluousClassicalValues; +}; + +/** + * @brief The abstract state of a whole program point: a probability + * distribution over correlated subsystems. + * + * A UnionTable is a flat list of HybridState "HybridStates" partitioned into + * *slots*: + * - HybridStates in different slots are unentangled **tensor factors**; the + * full state is their product. + * - HybridStates in the same slot are **alternatives** of one probabilistic + * disjunction; their probabilities sum to one. + * + * The slot of a qubit-bearing HybridState is every HybridState with the exact + * same qubit set. Each purely classical HybridState has its own slot. + * + * Operations take matrix-level arguments (no Operation*); the analysis maps + * gates to matrices and target/output SSA values. Before a multi-qubit or + * controlled operation, the touched slots are coalesced into one (alternatives + * multiply out via HybridState::tensor); if that exceeds maxHybridStates all + * states in the new slot collapse to top. A target or control value that is + * absent from the table is a caller/propagation bug and yields failure(); the + * analysis seeds every qubit before first use. + */ +class UnionTable { + bool allTop = false; + size_t maxNonzeroAmplitudes; + size_t maxHybridStates; + SmallVector hybridStates; + + /** + * @brief Indices of the HybridStates that mention v (as a qubit or as a + * classical key), ascending. + * + * @param v The value to be checked for + * @returns The indices of the states with v + */ + [[nodiscard( + "UnionTable::statesWith called but ignored")]] SmallVector + statesWith(Value v) const; + + /** + * @brief The slot index belongs to (itself included), ascending. + * + * @param index The index to be checked for + * @returns The indices of the slots that index belongs to. + */ + [[nodiscard("UnionTable::slotOf called but ignored")]] SmallVector + slotOf(unsigned index) const; + + /** + * @brief The distinct slots touched by any of the values, each as an + * ascending index list. + * + * @param values The values whose slots are collected. + */ + [[nodiscard("UnionTable::slotsTouchedBy called but ignored")]] SmallVector< + SmallVector> + slotsTouchedBy(ArrayRef values) const; + + /** + * @brief Fuses every slot touched by values into a single slot. + * + * The new slot's alternatives are the cartesian product of the fused slots' + * alternatives, combined with HybridState::tensor (probabilities and global + * phases multiply). Collapses the table to allTop if the product exceeds + * maxHybridStates. Values absent from the table are ignored. + * + * @param values The values whose entries should be merged. + */ + void mergeSlots(ArrayRef values); + +public: + /** + * @param maxNonzeroAmplitudes Per-QuantumState amplitude budget before it + * collapses to top. + * @param maxHybridStates Per-UnionTable HybridState budget before the whole + * table collapses to allTop. + */ + UnionTable(const size_t maxNonzeroAmplitudes, const size_t maxHybridStates) + : maxNonzeroAmplitudes(maxNonzeroAmplitudes), + maxHybridStates(maxHybridStates) {} + + //===--------------------------------------------------------------------===// + // Seeding + //===--------------------------------------------------------------------===// + + /** + * @brief Adds qubit in state |0> as its own factor. No-op if it is already + * tracked or the table is allTop. + * + * @param qubit The qubit to be added. + */ + void seedQubit(Value qubit); + + /** + * @brief Records value as the resolved classical constant attr in its own + * factor (overwrites an existing entry). No-op if the table is allTop. + * + * @param value The value to be saved. + * @param attr The attribute that value should get. + */ + void seedClassical(Value value, Attribute attr); + + /** + * @brief Whether v is tracked as a qubit or a classical value. + * + * @param v The value to be checked for. + * @returns True if v is already tracked. + */ + [[nodiscard("UnionTable::isTracked called but ignored")]] bool + isTracked(Value v) const; + + //===--------------------------------------------------------------------===// + // SSA forwarding + //===--------------------------------------------------------------------===// + + /** + * @brief Renames from to to everywhere (qubit or clasical). No-op if from is + * not present. + * + * @param from The value being replaced. + * @param to The value it is replaced with. + */ + void forwardValue(Value from, Value to); + + /** + * @brief forwardValue for each from[i] -> to[i]. + * + * @param from The values being replaced. + * @param to The values from are replaced with. + */ + void forwardValues(ArrayRef from, ArrayRef to); + + //===--------------------------------------------------------------------===// + // Operation propagation + //===--------------------------------------------------------------------===// + + /** + * @brief Applies a single-qubit unitary to in (renamed to out). + * + * @param in The qubit to apply the matrix to. + * @param out The qubit that in is changed to. + * @param matrix The matrix to apply to the amplitudes of in. + * @param quantumCtrlsIn The qubits that have to be |1> to apply the matrix. + * @param quantumCtrlsOut The qubits that quantumCtrlsIn are changed to. + * @param posClassicalCtrls The classical values that have to be true + * (nonzero) to apply the matrix. + * @param negClassicalCtrls The classical values that have to be false (zero) + * to apply the matrix. + * @return failure() if a target/control qubit is not in this state, the + * control in/out lengths mismatch, or a classical control is unresolved. + */ + [[nodiscard("UnionTable::applyMatrix1Q called but ignored")]] LogicalResult + applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, + ArrayRef quantumCtrlsIn = {}, + ArrayRef quantumCtrlsOut = {}, + ArrayRef posClassicalCtrls = {}, + ArrayRef negClassicalCtrls = {}); + + /** + * @brief Applies a two-qubit unitary to in0, in1 (renamed to out0, out1), + * following QCO's Matrix4x4 convention (in0 = high bit). + * + * @param in0 The high qubit to apply the matrix to. + * @param in1 The low qubit to apply the matrix to. + * @param out0 The qubit that in0 is changed to. + * @param out1 The qubit that in1 is changed to. + * @param matrix The matrix to apply to the amplitudes of in. + * @param quantumCtrlsIn The qubits that have to be |1> to apply the matrix. + * @param quantumCtrlsOut The qubits that quantumCtrlsIn are changed to. + * @param posClassicalCtrls The classical values that have to be true + * (nonzero) to apply the matrix. + * @param negClassicalCtrls The classical values that have to be false (zero) + * to apply the matrix. + * @return failure() if a target/control qubit is not in this state, the + * control in/out lengths mismatch, or a classical control is unresolved. + */ + [[nodiscard("UnionTable::applyMatrix2Q called but ignored")]] LogicalResult + applyMatrix2Q(Value in0, Value in1, Value out0, Value out1, + const Matrix4x4& matrix, ArrayRef quantumCtrlsIn = {}, + ArrayRef quantumCtrlsOut = {}, + ArrayRef posClassicalCtrls = {}, + ArrayRef negClassicalCtrls = {}); + + /** + * @brief Adds a global phase exp(i*theta). + * + * Uncontrolled: accumulated into one representative HybridState's global + * phase. With quantum controls: a relative phase on the controlled subspace. + * + * @param theta The phase to add. + * @param quantumCtrlsIn The qubits that have to be |1> to apply the matrix. + * @param quantumCtrlsOut The qubits that quantumCtrlsIn are changed to. + * @param posClassicalCtrls The classical values that have to be true + * (nonzero) to apply the matrix. + * @param negClassicalCtrls The classical values that have to be false (zero) + * to apply the matrix. + * @return failure() if a control qubit is not in this state or a classical + * control is unresolved. + */ + [[nodiscard("UnionTable::addGlobalPhase called but ignored")]] LogicalResult + addGlobalPhase(double theta, ArrayRef quantumCtrlsIn = {}, + ArrayRef quantumCtrlsOut = {}, + ArrayRef posClassicalCtrls = {}, + ArrayRef negClassicalCtrls = {}); + + /** + * @brief Measures in (renamed to out), recording the outcome in + * classicalResult. + * + * Per alternative: an exact bit if in is deterministic there, otherwise that + * alternative's QuantumState collapses to top, and the result stays unknown. + * + * @param in The qubit to be measured. + * @param out The value to change in to. + * @param classicalResult The classical value to save the result of the + * measurement in. + * @param posClassicalCtrls The classical values that have to be true + * (nonzero) to apply the measurement. + * @param negClassicalCtrls The classical values that have to be false (zero) + * to apply the measurement. + * @return failure() if in is absent or a classical control is unresolved. + */ + [[nodiscard("UnionTable::measureQubit called but ignored")]] LogicalResult + measureQubit(Value in, Value out, Value classicalResult, + ArrayRef posClassicalCtrls = {}, + ArrayRef negClassicalCtrls = {}); + + /** + * @brief Resets in to |0> (renamed to out). + * + * Exact per alternative when in is deterministic there, otherwise that + * alternative's QuantumState collapses to top. + * + * @param in The qubit to be reset. + * @param out The value to change in to. + * @param posClassicalCtrls The classical values that have to be true + * (nonzero) to apply the reset. + * @param negClassicalCtrls The classical values that have to be false (zero) + * to apply the reset. + * @return failure() if in is absent or a classical control is unresolved. + */ + [[nodiscard("UnionTable::resetQubit called but ignored")]] LogicalResult + resetQubit(Value in, Value out, ArrayRef posClassicalCtrls = {}, + ArrayRef negClassicalCtrls = {}); + + /** + * @brief Collapses the QuantumState of every alternative in the slots that + * hold qubits to top. The analysis' fallback for an operation whose effect it + * cannot represent. + * + * @param qubits The qubits whose slots should collapse to top. + */ + void markQubitsTop(ArrayRef qubits); + + //===--------------------------------------------------------------------===// + // Queries + //===--------------------------------------------------------------------===// + + [[nodiscard("UnionTable::isQubitAlwaysOne called but ignored")]] bool + isQubitAlwaysOne(Value q) const; + [[nodiscard("UnionTable::isQubitAlwaysZero called but ignored")]] bool + isQubitAlwaysZero(Value q) const; + [[nodiscard("UnionTable::isClassicalAlwaysTrue called but ignored")]] bool + isClassicalAlwaysTrue(Value v) const; + [[nodiscard("UnionTable::isClassicalAlwaysFalse called but ignored")]] bool + isClassicalAlwaysFalse(Value v) const; + + /** + * @brief Whether the controls can all hold at once somewhere in the + * distribution. + * + * @param quantumCtrls The qubits that have to be |1>. + * @param posClassicalCtrls The classical values that have to be true + * (nonzero). + * @param negClassicalCtrls The classical values that have to be false (zero). + * @returns True if the control configuration is satisfiable. + */ + [[nodiscard("UnionTable::areControlsSatisfiable called but ignored")]] bool + areControlsSatisfiable(ArrayRef quantumCtrls, + ArrayRef posClassicalCtrls = {}, + ArrayRef negClassicalCtrls = {}) const; + + /** + * @brief Which of the given (positive quantum / positive classical / negative + * classical) controls are redundant in the current state. + * + * @param quantumCtrls The qubits that have to be |1>. + * @param posClassicalCtrls The classical values that have to be true + * (nonzero). + * @param negClassicalCtrls The classical values that have to be false (zero). + * @returns Whether the whole operation is superfluous (controls will never be + * satisfied), or if there are parts of the controls that are superfluous. + */ + [[nodiscard("UnionTable::getSuperfluousControls called but ignored")]] + SuperfluousResult + getSuperfluousControls(ArrayRef quantumCtrls, + ArrayRef posClassicalCtrls = {}, + ArrayRef negClassicalCtrls = {}) const; + + //===--------------------------------------------------------------------===// + // Lattice support + //===--------------------------------------------------------------------===// + + /** + * @brief Reconciles this state with other coming from a sibling control-flow + * path (the two branches of a non-constant qco.if). + * + * Slots are matched by qubit set. Matching slots merge their alternatives + * (probability-weighted, deduplicated, renormalized); a classical-only fact + * survives only if other asserts it too. The table collapses to allTop if the + * entanglement structure differs or maxHybridStates is exceeded. + * + * The caller aligns yielded SSA names (via forwardValues) before calling. + * + * @param other The UnionTable to join this with. + */ + void join(const UnionTable& other); + + /// @brief Collapses the whole table: no quantum or classical facts survive. + void markAllTop(); + + [[nodiscard("UnionTable::isAllTop called but ignored")]] bool + isAllTop() const { + return allTop; + } + + /// @brief Whether every tracked QuantumState is top (classical facts may + /// remain). + [[nodiscard("UnionTable::areStatesAllTop called but ignored")]] bool + areStatesAllTop() const; + + /// @brief Order-independent structural equality (drives lattice convergence). + [[nodiscard("UnionTable::== called but ignored")]] bool + operator==(const UnionTable& other) const; + + void print(raw_ostream& os) const; +}; + +} // namespace mlir::qco From 947dc5f936e26a1cae519dbce3565b5ec02f8a8a Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sat, 29 Aug 2026 11:00:24 +0200 Subject: [PATCH 19/55] :construction: Added UnionTable, assisted-by Sonnet 5 via Claude Code --- .../ConstantPropagation/HybridState.cpp | 13 + .../ConstantPropagation/HybridState.hpp | 10 + .../ConstantPropagation/UnionTable.cpp | 705 ++++++++++++++++++ .../ConstantPropagation/UnionTable.hpp | 134 ++-- .../Transforms/Optimizations/CMakeLists.txt | 1 + .../ConstantPropagation/test_hybridState.cpp | 14 + .../ConstantPropagation/test_unionTable.cpp | 418 +++++++++++ 7 files changed, 1231 insertions(+), 64 deletions(-) create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp create mode 100644 mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 1682d4b4f3..21b6ed9ce6 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -84,6 +84,19 @@ void HybridState::forwardValue(const Value from, const Value to) { void HybridState::markStateTop() { state.markTop(); } +void HybridState::intersectClassical(const HybridState& other) { + SmallVector disagreeing; + for (const auto& [v, attr] : classical) { + const auto it = other.classical.find(v); + if (it == other.classical.end() || it->second != attr) { + disagreeing.push_back(v); + } + } + for (const Value v : disagreeing) { + classical.erase(v); + } +} + HybridState HybridState::tensor(const HybridState& other) const { HybridState result(state.unify(other.state), maxNonzeroAmplitudes, probability * other.probability); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index 0638908652..5c1c00b07d 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -137,6 +137,16 @@ class HybridState { /// @brief Collapses this branch's QuantumState to top; classical facts stay. void markStateTop(); + /** + * @brief Drops every classical fact other does not hold identically. + * + * Used to build a sound representative when a disjunction of alternatives is + * collapsed: only the facts every alternative agrees on may be kept. + * + * @param other The branch to intersect this one's classical facts with. + */ + void intersectClassical(const HybridState& other); + /** * @brief Combines this subsystem with a disjoint one into a single * HybridState. diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp new file mode 100644 index 0000000000..fff935ae08 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -0,0 +1,705 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "UnionTable.hpp" + +#include "HybridState.hpp" +#include "QuantumState.hpp" +#include "mlir/Dialect/QCO/Utils/Matrix.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace mlir::qco { + +namespace { +/// The qubit set of a slot, order-normalized, so two slots (from sibling +/// control-flow paths) can be matched. +std::vector qubitKey(const UnionTable::Slot& slot) { + std::vector key; + key.reserve(slot.front().getQubits().size()); + for (const Value q : slot.front().getQubits()) { + key.push_back(q.getAsOpaquePointer()); + } + llvm::sort(key); + return key; +} +} // namespace + +//===----------------------------------------------------------------------===// +// Partition helpers +//===----------------------------------------------------------------------===// + +std::optional UnionTable::slotIndexContaining(const Value v) const { + for (const auto& [i, slot] : llvm::enumerate(slots)) { + if (slot.front().hasQubit(v)) { + return static_cast(i); + } + for (const auto& hs : slot) { + if (hs.getClassical(v).has_value()) { + return static_cast(i); + } + } + } + return std::nullopt; +} + +SmallVector +UnionTable::slotsTouchedBy(const ArrayRef values) const { + SmallVector result; + for (const Value v : values) { + if (const auto i = slotIndexContaining(v)) { + if (!llvm::is_contained(result, *i)) { + result.push_back(*i); + } + } + } + llvm::sort(result); + return result; +} + +HybridState UnionTable::reducedRepresentative(const Slot& slot) { + HybridState representative = slot.front(); + for (size_t j = 1; j < slot.size(); ++j) { + representative.intersectClassical(slot[j]); + } + return representative; +} + +void UnionTable::mergeSlots(const ArrayRef values) { + if (allTop) { + return; + } + const auto touched = slotsTouchedBy(values); + if (touched.size() <= 1) { + return; + } + + size_t product = 1; + bool overflow = false; + for (const unsigned i : touched) { + if (product > maxHybridStates / slots[i].size()) { + overflow = true; + break; + } + product *= slots[i].size(); + } + + Slot fused; + if (overflow) { + const auto toppedRepresentative = [](const Slot& slot) { + HybridState representative = reducedRepresentative(slot); + representative.markStateTop(); + return representative; + }; + HybridState top = toppedRepresentative(slots[touched.front()]); + for (size_t k = 1; k < touched.size(); ++k) { + top = top.tensor(toppedRepresentative(slots[touched[k]])); + } + top.setProbability(1.0); + fused.push_back(std::move(top)); + } else { + fused = slots[touched.front()]; + for (size_t k = 1; k < touched.size(); ++k) { + const Slot& next = slots[touched[k]]; + Slot combined; + combined.reserve(fused.size() * next.size()); + for (const auto& a : fused) { + for (const auto& b : next) { + combined.push_back(a.tensor(b)); + } + } + fused = std::move(combined); + } + } + + // Erase the merged slots high-index-first, then append the fused one. + for (size_t k = touched.size(); k-- > 0;) { + slots.erase(slots.begin() + touched[k]); + } + slots.push_back(std::move(fused)); +} + +UnionTable::Slot UnionTable::mergeAlternatives(const Slot& a, const Slot& b) { + Slot merged; + const auto absorb = [&merged](const Slot& side) { + for (const auto& hs : side) { + HybridState* match = nullptr; + for (auto& candidate : merged) { + if (candidate.sameConfiguration(hs)) { + match = &candidate; + break; + } + } + if (match != nullptr) { + match->setProbability(match->getProbability() + hs.getProbability()); + } else { + merged.push_back(hs); + } + } + }; + absorb(a); + absorb(b); + + double sum = 0.0; + for (const auto& hs : merged) { + sum += hs.getProbability(); + } + if (sum > MATRIX_TOLERANCE) { + for (auto& hs : merged) { + hs.scaleProbability(1.0 / sum); + } + } + return merged; +} + +bool UnionTable::sameSlot(const Slot& a, const Slot& b) { + if (a.size() != b.size()) { + return false; + } + SmallVector used(b.size(), false); + for (const auto& lhs : a) { + bool matched = false; + for (unsigned j = 0; j < b.size(); ++j) { + if (!used[j] && lhs == b[j]) { + used[j] = true; + matched = true; + break; + } + } + if (!matched) { + return false; + } + } + return true; +} + +//===----------------------------------------------------------------------===// +// Seeding +//===----------------------------------------------------------------------===// + +void UnionTable::seedQubit(const Value qubit) { + if (allTop || isTracked(qubit)) { + return; + } + Slot slot; + slot.emplace_back(QuantumState::singletonZero(qubit, maxNonzeroAmplitudes), + maxNonzeroAmplitudes, 1.0); + slots.push_back(std::move(slot)); +} + +void UnionTable::seedClassical(const Value value, const Attribute attr) { + if (allTop) { + return; + } + if (const auto i = slotIndexContaining(value)) { + for (auto& hs : slots[*i]) { + hs.setClassical(value, attr); + } + return; + } + Slot slot; + slot.emplace_back(QuantumState(ArrayRef{}, maxNonzeroAmplitudes), + maxNonzeroAmplitudes, 1.0); + slot.back().setClassical(value, attr); + slots.push_back(std::move(slot)); +} + +bool UnionTable::isTracked(const Value v) const { + return slotIndexContaining(v).has_value(); +} + +//===----------------------------------------------------------------------===// +// SSA forwarding +//===----------------------------------------------------------------------===// + +void UnionTable::forwardValue(const Value from, const Value to) { + for (auto& slot : slots) { + for (auto& hs : slot) { + hs.forwardValue(from, to); + } + } +} + +void UnionTable::forwardValues(const ArrayRef from, + const ArrayRef to) { + for (const auto [f, t] : llvm::zip(from, to)) { + forwardValue(f, t); + } +} + +//===----------------------------------------------------------------------===// +// Operation propagation +//===----------------------------------------------------------------------===// + +LogicalResult UnionTable::applyMatrix1Q( + const Value in, const Value out, const Matrix2x2& matrix, + const ArrayRef quantumCtrlsIn, const ArrayRef quantumCtrlsOut, + const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) { + if (allTop) { + return success(); + } + if (!isTracked(in)) { + return failure(); + } + + SmallVector touched{in}; + touched.append(quantumCtrlsIn.begin(), quantumCtrlsIn.end()); + touched.append(posClassicalCtrls.begin(), posClassicalCtrls.end()); + touched.append(negClassicalCtrls.begin(), negClassicalCtrls.end()); + mergeSlots(touched); + if (allTop) { + return success(); + } + + for (auto& hs : slots[*slotIndexContaining(in)]) { + if (failed(hs.applyMatrix1Q(in, out, matrix, quantumCtrlsIn, + quantumCtrlsOut, posClassicalCtrls, + negClassicalCtrls))) { + return failure(); + } + } + return success(); +} + +LogicalResult +UnionTable::applyMatrix2Q(const Value in0, const Value in1, const Value out0, + const Value out1, const Matrix4x4& matrix, + const ArrayRef quantumCtrlsIn, + const ArrayRef quantumCtrlsOut, + const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) { + if (allTop) { + return success(); + } + if (!isTracked(in0) || !isTracked(in1)) { + return failure(); + } + + SmallVector touched{in0, in1}; + touched.append(quantumCtrlsIn.begin(), quantumCtrlsIn.end()); + touched.append(posClassicalCtrls.begin(), posClassicalCtrls.end()); + touched.append(negClassicalCtrls.begin(), negClassicalCtrls.end()); + mergeSlots(touched); + if (allTop) { + return success(); + } + + for (auto& hs : slots[*slotIndexContaining(in0)]) { + if (failed(hs.applyMatrix2Q(in0, in1, out0, out1, matrix, quantumCtrlsIn, + quantumCtrlsOut, posClassicalCtrls, + negClassicalCtrls))) { + return failure(); + } + } + return success(); +} + +LogicalResult +UnionTable::addGlobalPhase(const double theta, + const ArrayRef quantumCtrlsIn, + const ArrayRef quantumCtrlsOut, + const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) { + if (allTop) { + return success(); + } + if (quantumCtrlsIn.empty() && !quantumCtrlsOut.empty()) { + return failure(); + } + + SmallVector touched(quantumCtrlsIn.begin(), quantumCtrlsIn.end()); + touched.append(posClassicalCtrls.begin(), posClassicalCtrls.end()); + touched.append(negClassicalCtrls.begin(), negClassicalCtrls.end()); + mergeSlots(touched); + if (allTop) { + return success(); + } + + if (!quantumCtrlsIn.empty() || !posClassicalCtrls.empty() || + !negClassicalCtrls.empty()) { + Value anchor; + if (!quantumCtrlsIn.empty()) { + anchor = quantumCtrlsIn.front(); + } else { + anchor = posClassicalCtrls.empty() ? negClassicalCtrls.front() + : posClassicalCtrls.front(); + } + for (auto& hs : slots[*slotIndexContaining(anchor)]) { + if (failed(hs.addGlobalPhase(theta, quantumCtrlsIn, quantumCtrlsOut, + posClassicalCtrls, negClassicalCtrls))) { + return failure(); + } + } + return success(); + } + + if (slots.empty()) { + Slot slot; + slot.emplace_back(QuantumState(ArrayRef{}, maxNonzeroAmplitudes), + maxNonzeroAmplitudes, 1.0); + slots.push_back(std::move(slot)); + } + return slots.front().front().addGlobalPhase(theta); +} + +LogicalResult +UnionTable::measureQubit(const Value in, const Value out, + const Value classicalResult, + const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) { + if (allTop) { + return success(); + } + if (!isTracked(in)) { + return failure(); + } + + SmallVector touched{in}; + touched.push_back(classicalResult); + touched.append(posClassicalCtrls.begin(), posClassicalCtrls.end()); + touched.append(negClassicalCtrls.begin(), negClassicalCtrls.end()); + mergeSlots(touched); + if (allTop) { + return success(); + } + + for (auto& hs : slots[*slotIndexContaining(in)]) { + if (failed(hs.measureQubit(in, out, classicalResult, posClassicalCtrls, + negClassicalCtrls))) { + return failure(); + } + } + return success(); +} + +LogicalResult UnionTable::resetQubit(const Value in, const Value out, + const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) { + if (allTop) { + return success(); + } + if (!isTracked(in)) { + return failure(); + } + + SmallVector touched{in}; + touched.append(posClassicalCtrls.begin(), posClassicalCtrls.end()); + touched.append(negClassicalCtrls.begin(), negClassicalCtrls.end()); + mergeSlots(touched); + if (allTop) { + return success(); + } + + for (auto& hs : slots[*slotIndexContaining(in)]) { + if (failed(hs.resetQubit(in, out, posClassicalCtrls, negClassicalCtrls))) { + return failure(); + } + } + return success(); +} + +void UnionTable::markQubitsTop(const ArrayRef qubits) { + if (allTop) { + return; + } + llvm::DenseSet done; + for (const Value q : qubits) { + const auto i = slotIndexContaining(q); + if (i && done.insert(*i).second) { + for (auto& hs : slots[*i]) { + hs.markStateTop(); + } + } + } +} + +//===----------------------------------------------------------------------===// +// Queries +//===----------------------------------------------------------------------===// + +bool UnionTable::isQubitAlwaysOne(const Value q) const { + if (allTop) { + return false; + } + const auto i = slotIndexContaining(q); + return i && llvm::all_of(slots[*i], [&](const HybridState& hs) { + return hs.isQubitAlwaysOne(q); + }); +} + +bool UnionTable::isQubitAlwaysZero(const Value q) const { + if (allTop) { + return false; + } + const auto i = slotIndexContaining(q); + return i && llvm::all_of(slots[*i], [&](const HybridState& hs) { + return hs.isQubitAlwaysZero(q); + }); +} + +bool UnionTable::isClassicalAlwaysTrue(const Value v) const { + if (allTop) { + return false; + } + const auto i = slotIndexContaining(v); + return i && llvm::all_of(slots[*i], [&](const HybridState& hs) { + return hs.isClassicalTrue(v); + }); +} + +bool UnionTable::isClassicalAlwaysFalse(const Value v) const { + if (allTop) { + return false; + } + const auto i = slotIndexContaining(v); + return i && llvm::all_of(slots[*i], [&](const HybridState& hs) { + return hs.isClassicalFalse(v); + }); +} + +bool UnionTable::areControlsSatisfiable( + const ArrayRef quantumCtrls, const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) const { + if (allTop) { + return true; + } + + SmallVector all(quantumCtrls.begin(), quantumCtrls.end()); + all.append(posClassicalCtrls.begin(), posClassicalCtrls.end()); + all.append(negClassicalCtrls.begin(), negClassicalCtrls.end()); + + for (const unsigned si : slotsTouchedBy(all)) { + const Slot& slot = slots[si]; + const auto hasClassical = [&](const Value c) { + return llvm::any_of(slot, [&](const HybridState& hs) { + return hs.getClassical(c).has_value(); + }); + }; + + SmallVector quantum; + for (const Value c : quantumCtrls) { + if (slot.front().hasQubit(c)) { + quantum.push_back(c); + } + } + SmallVector pos; + for (const Value c : posClassicalCtrls) { + if (hasClassical(c)) { + pos.push_back(c); + } + } + SmallVector neg; + for (const Value c : negClassicalCtrls) { + if (hasClassical(c)) { + neg.push_back(c); + } + } + if (quantum.empty() && pos.empty() && neg.empty()) { + continue; + } + const bool anySatisfiable = llvm::any_of(slot, [&](const HybridState& hs) { + return hs.areControlsSatisfiable(quantum, pos, neg); + }); + if (!anySatisfiable) { + return false; + } + } + return true; +} + +SuperfluousResult UnionTable::getSuperfluousControls( + const ArrayRef quantumCtrls, const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) const { + SuperfluousResult result; + if (!areControlsSatisfiable(quantumCtrls, posClassicalCtrls, + negClassicalCtrls)) { + result.completelySuperfluous = true; + return result; + } + for (const Value q : quantumCtrls) { + if (isQubitAlwaysOne(q)) { + result.superfluousQubits.insert(q); + } + } + for (const Value p : posClassicalCtrls) { + if (isClassicalAlwaysTrue(p)) { + result.superfluousClassicalValues.insert(p); + } + } + for (const Value n : negClassicalCtrls) { + if (isClassicalAlwaysFalse(n)) { + result.superfluousClassicalValues.insert(n); + } + } + return result; +} + +//===----------------------------------------------------------------------===// +// Lattice support +//===----------------------------------------------------------------------===// + +void UnionTable::join(const UnionTable& other) { + if (allTop || other.allTop) { + markAllTop(); + return; + } + + SmallVector myQuantum; + SmallVector myClassical; + for (const auto& [i, slot] : llvm::enumerate(slots)) { + (slot.front().getQubits().empty() ? myClassical : myQuantum) + .push_back(static_cast(i)); + } + SmallVector theirQuantum; + SmallVector theirClassical; + for (const auto& [i, slot] : llvm::enumerate(other.slots)) { + (slot.front().getQubits().empty() ? theirClassical : theirQuantum) + .push_back(static_cast(i)); + } + + if (myQuantum.size() != theirQuantum.size()) { + markAllTop(); // different entanglement structure + return; + } + + SmallVector merged; + + for (const unsigned mi : myQuantum) { + const auto key = qubitKey(slots[mi]); + const Slot* theirs = nullptr; + for (const unsigned ti : theirQuantum) { + if (qubitKey(other.slots[ti]) == key) { + theirs = &other.slots[ti]; + break; + } + } + if (theirs == nullptr) { + markAllTop(); + return; + } + + Slot combined = mergeAlternatives(slots[mi], *theirs); + if (combined.size() > maxHybridStates) { + // Too many alternatives for this factor: collapse just this slot to top. + HybridState top = reducedRepresentative(combined); + top.markStateTop(); + top.setProbability(1.0); + combined.clear(); + combined.push_back(std::move(top)); + } + merged.push_back(std::move(combined)); + } + + // A purely classical fact survives only if the other branch asserts the same + // one; otherwise it becomes unknown (it is simply dropped). + for (const unsigned mi : myClassical) { + const bool inBoth = llvm::any_of(theirClassical, [&](const unsigned ti) { + return llvm::any_of(other.slots[ti], [&](const HybridState& theirHs) { + return slots[mi].front().sameConfiguration(theirHs); + }); + }); + if (inBoth) { + Slot slot; + slot.push_back(slots[mi].front()); + slot.back().setProbability(1.0); + merged.push_back(std::move(slot)); + } + } + + slots = std::move(merged); +} + +void UnionTable::markAllTop() { + allTop = true; + slots.clear(); +} + +bool UnionTable::areStatesAllTop() const { + if (allTop) { + return true; + } + bool sawQuantum = false; + for (const auto& slot : slots) { + if (slot.front().getQubits().empty()) { + continue; + } + sawQuantum = true; + for (const auto& hs : slot) { + if (!hs.isTop()) { + return false; + } + } + } + return sawQuantum; +} + +bool UnionTable::operator==(const UnionTable& other) const { + if (allTop || other.allTop) { + return allTop == other.allTop; + } + if (slots.size() != other.slots.size()) { + return false; + } + SmallVector used(other.slots.size(), false); + for (const auto& mine : slots) { + bool matched = false; + for (unsigned j = 0; j < other.slots.size(); ++j) { + if (!used[j] && sameSlot(mine, other.slots[j])) { + used[j] = true; + matched = true; + break; + } + } + if (!matched) { + return false; + } + } + return true; +} + +void UnionTable::print(raw_ostream& os) const { + if (allTop) { + os << ""; + return; + } + if (slots.empty()) { + os << ""; + return; + } + bool firstSlot = true; + for (const auto& slot : slots) { + if (!firstSlot) { + os << "\n---\n"; + } + firstSlot = false; + bool firstAlt = true; + for (const auto& hs : slot) { + if (!firstAlt) { + os << "\n"; + } + firstAlt = false; + hs.print(os); + } + } +} + +} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index 12c1419b8d..f604ae6a74 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -20,6 +20,8 @@ #include #include +#include + namespace mlir::qco { /** @@ -40,78 +42,78 @@ struct SuperfluousResult { * @brief The abstract state of a whole program point: a probability * distribution over correlated subsystems. * - * A UnionTable is a flat list of HybridState "HybridStates" partitioned into - * *slots*: - * - HybridStates in different slots are unentangled **tensor factors**; the - * full state is their product. - * - HybridStates in the same slot are **alternatives** of one probabilistic - * disjunction; their probabilities sum to one. + * A UnionTable is a list of *slots*. Each slot is a non-empty list of + * @ref HybridState "HybridStates": + * - Slots are unentangled **tensor factors**; the full state is their product. + * - The HybridStates within a slot are **alternatives** of one probabilistic + * disjunction; they share a qubit set and their probabilities sum to one. * - * The slot of a qubit-bearing HybridState is every HybridState with the exact - * same qubit set. Each purely classical HybridState has its own slot. + * Qubit sets of different slots are disjoint. A slot with no qubits is a + * purely classical factor. * * Operations take matrix-level arguments (no Operation*); the analysis maps * gates to matrices and target/output SSA values. Before a multi-qubit or - * controlled operation, the touched slots are coalesced into one (alternatives - * multiply out via HybridState::tensor); if that exceeds maxHybridStates all - * states in the new slot collapse to top. A target or control value that is - * absent from the table is a caller/propagation bug and yields failure(); the - * analysis seeds every qubit before first use. + * controlled operation the touched slots are merged into one (alternatives + * multiply out via HybridState::tensor); if that exceeds maxHybridStates the + * whole table collapses to allTop. A target or control value absent from the + * table is a caller/propagation bug and yields failure(); the analysis seeds + * every qubit before first use. */ class UnionTable { +public: + using Slot = SmallVector; + +private: bool allTop = false; size_t maxNonzeroAmplitudes; size_t maxHybridStates; - SmallVector hybridStates; + SmallVector slots; - /** - * @brief Indices of the HybridStates that mention v (as a qubit or as a - * classical key), ascending. - * - * @param v The value to be checked for - * @returns The indices of the states with v - */ - [[nodiscard( - "UnionTable::statesWith called but ignored")]] SmallVector - statesWith(Value v) const; + /// @brief Index of the slot that holds v (as a qubit or a classical key). + [[nodiscard("UnionTable::slotIndexContaining called but ignored")]] + std::optional slotIndexContaining(Value v) const; - /** - * @brief The slot index belongs to (itself included), ascending. - * - * @param index The index to be checked for - * @returns The indices of the slots that index belongs to. - */ - [[nodiscard("UnionTable::slotOf called but ignored")]] SmallVector - slotOf(unsigned index) const; + /// @brief The distinct slot indices touched by any of values, ascending. + [[nodiscard("UnionTable::slotsTouchedBy called but ignored")]] + SmallVector slotsTouchedBy(ArrayRef values) const; /** - * @brief The distinct slots touched by any of the values, each as an - * ascending index list. + * @brief Merges every slot touched by values into a single slot. + * + * The merged slot's alternatives are the cartesian product of the merged + * slots' alternatives, combined with HybridState::tensor (probabilities and + * global phases multiply). If the product would exceed maxHybridStates only + * the merged slots collapse to a single top state (untouched slots are left + * alone). Values absent from the table are ignored. * - * @param values The values whose slots are collected. + * @param values The values whose slots should be merged. */ - [[nodiscard("UnionTable::slotsTouchedBy called but ignored")]] SmallVector< - SmallVector> - slotsTouchedBy(ArrayRef values) const; + void mergeSlots(ArrayRef values); + + /// @brief A single HybridState standing in for a slot's disjunction: its + /// first alternative, keeping only the classical facts every alternative + /// agrees on. + [[nodiscard("UnionTable::reducedRepresentative called but ignored")]] + static HybridState reducedRepresentative(const Slot& slot); /** - * @brief Fuses every slot touched by values into a single slot. - * - * The new slot's alternatives are the cartesian product of the fused slots' - * alternatives, combined with HybridState::tensor (probabilities and global - * phases multiply). Collapses the table to allTop if the product exceeds - * maxHybridStates. Values absent from the table are ignored. - * - * @param values The values whose entries should be merged. + * Combines the alternatives of two slots coming from sibling control-flow + * paths: matching configurations are de-duplicated, the result is + * renormalized to sum one. */ - void mergeSlots(ArrayRef values); + [[nodiscard("UnionTable::mergeAlternatives called but ignored")]] + static Slot mergeAlternatives(const Slot& a, const Slot& b); + + /// @brief Order-independent equality of two slots' alternatives. + [[nodiscard("UnionTable::sameSlot called but ignored")]] + static bool sameSlot(const Slot& a, const Slot& b); public: /** * @param maxNonzeroAmplitudes Per-QuantumState amplitude budget before it * collapses to top. - * @param maxHybridStates Per-UnionTable HybridState budget before the whole - * table collapses to allTop. + * @param maxHybridStates Per-slot alternative budget before the whole slot + * collapses to allTop. */ UnionTable(const size_t maxNonzeroAmplitudes, const size_t maxHybridStates) : maxNonzeroAmplitudes(maxNonzeroAmplitudes), @@ -152,7 +154,7 @@ class UnionTable { //===--------------------------------------------------------------------===// /** - * @brief Renames from to to everywhere (qubit or clasical). No-op if from is + * @brief Renames from to to everywhere (qubit or classical). No-op if from is * not present. * * @param from The value being replaced. @@ -184,8 +186,8 @@ class UnionTable { * (nonzero) to apply the matrix. * @param negClassicalCtrls The classical values that have to be false (zero) * to apply the matrix. - * @return failure() if a target/control qubit is not in this state, the - * control in/out lengths mismatch, or a classical control is unresolved. + * @return failure() if a target/control value is absent, the control in/out + * lengths mismatch, or a classical control is unresolved. */ [[nodiscard("UnionTable::applyMatrix1Q called but ignored")]] LogicalResult applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, @@ -209,8 +211,9 @@ class UnionTable { * (nonzero) to apply the matrix. * @param negClassicalCtrls The classical values that have to be false (zero) * to apply the matrix. - * @return failure() if a target/control qubit is not in this state, the - * control in/out lengths mismatch, or a classical control is unresolved. + * @return failure() if a target/control value is absent, the two targets + * coincide, the control in/out lengths mismatch, or a classical control is + * unresolved. */ [[nodiscard("UnionTable::applyMatrix2Q called but ignored")]] LogicalResult applyMatrix2Q(Value in0, Value in1, Value out0, Value out1, @@ -226,14 +229,14 @@ class UnionTable { * phase. With quantum controls: a relative phase on the controlled subspace. * * @param theta The phase to add. - * @param quantumCtrlsIn The qubits that have to be |1> to apply the matrix. + * @param quantumCtrlsIn The qubits that have to be |1> for the phase. * @param quantumCtrlsOut The qubits that quantumCtrlsIn are changed to. * @param posClassicalCtrls The classical values that have to be true - * (nonzero) to apply the matrix. + * (nonzero) for the phase. * @param negClassicalCtrls The classical values that have to be false (zero) - * to apply the matrix. - * @return failure() if a control qubit is not in this state or a classical - * control is unresolved. + * for the phase. + * @return failure() if a control value is absent, the control in/out lengths + * mismatch, or a classical control is unresolved. */ [[nodiscard("UnionTable::addGlobalPhase called but ignored")]] LogicalResult addGlobalPhase(double theta, ArrayRef quantumCtrlsIn = {}, @@ -250,8 +253,7 @@ class UnionTable { * * @param in The qubit to be measured. * @param out The value to change in to. - * @param classicalResult The classical value to save the result of the - * measurement in. + * @param classicalResult The classical value to record the outcome in. * @param posClassicalCtrls The classical values that have to be true * (nonzero) to apply the measurement. * @param negClassicalCtrls The classical values that have to be false (zero) @@ -307,6 +309,10 @@ class UnionTable { * @brief Whether the controls can all hold at once somewhere in the * distribution. * + * A conjunction over disjoint factors (each factor must be satisfiable), + * disjunction over a slot's alternatives (any alternative suffices). Controls + * absent from the table are treated as possibly satisfiable. + * * @param quantumCtrls The qubits that have to be |1>. * @param posClassicalCtrls The classical values that have to be true * (nonzero). @@ -326,8 +332,8 @@ class UnionTable { * @param posClassicalCtrls The classical values that have to be true * (nonzero). * @param negClassicalCtrls The classical values that have to be false (zero). - * @returns Whether the whole operation is superfluous (controls will never be - * satisfied), or if there are parts of the controls that are superfluous. + * @returns Whether the whole operation is superfluous (controls can never be + * satisfied), plus the individual controls that always hold. */ [[nodiscard("UnionTable::getSuperfluousControls called but ignored")]] SuperfluousResult @@ -346,7 +352,7 @@ class UnionTable { * Slots are matched by qubit set. Matching slots merge their alternatives * (probability-weighted, deduplicated, renormalized); a classical-only fact * survives only if other asserts it too. The table collapses to allTop if the - * entanglement structure differs or maxHybridStates is exceeded. + * entanglement structure differs or a slot exceeds maxHybridStates. * * The caller aligns yielded SSA names (via forwardValues) before calling. * diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index c4a7d05b75..0deaf3c402 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -11,6 +11,7 @@ add_executable( ${target_name} ConstantPropagation/test_hybridState.cpp ConstantPropagation/test_quantumState.cpp + ConstantPropagation/test_unionTable.cpp test_qco_hadamard_lifting.cpp test_qco_measurement_lifting.cpp test_qco_merge_single_qubit_rotation.cpp diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp index b8d9c4b4d6..b3c31623c1 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -413,6 +413,20 @@ TEST_F(HybridStateTest, markStateTopKeepsClassicalFacts) { EXPECT_TRUE(hs.isClassicalTrue(cA)); } +TEST_F(HybridStateTest, intersectClassicalKeepsOnlyAgreedFacts) { + auto a = make({q[0]}); + a.setClassical(cA, builder.getBoolAttr(true)); + a.setClassical(cB, builder.getBoolAttr(true)); + + auto b = make({q[0]}); + b.setClassical(cA, builder.getBoolAttr(true)); // agrees + b.setClassical(cB, builder.getBoolAttr(false)); // disagrees + + a.intersectClassical(b); + EXPECT_TRUE(a.isClassicalTrue(cA)); + EXPECT_FALSE(a.getClassical(cB).has_value()); +} + TEST_F(HybridStateTest, forwardValueRenamesQubitAndClassical) { auto hs = make({q[0]}); hs.setClassical(cA, builder.getBoolAttr(true)); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp new file mode 100644 index 0000000000..1ac7caf244 --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "ConstantPropagation/UnionTable.hpp" +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace { + +using namespace mlir; +using namespace mlir::qco; + +std::string printed(const UnionTable& ut) { + std::string s; + llvm::raw_string_ostream os(s); + ut.print(os); + return s; +} + +class UnionTableTest : public testing::Test { +protected: + MLIRContext context; + QCOProgramBuilder builder; + + std::array q{}; + HOp hOp; + XOp xOp; + DCXOp dcxOp; + + UnionTableTest() : builder(&context) {} + + void SetUp() override { + DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + + builder.initialize(); + auto reg = builder.allocQubitRegister(8); + for (size_t i = 0; i < q.size(); ++i) { + q[i] = reg[i]; + } + const auto qt = q[0].getType(); + hOp = HOp::create(builder, builder.getLoc(), qt, q[0]); + xOp = XOp::create(builder, builder.getLoc(), qt, q[0]); + dcxOp = DCXOp::create(builder, builder.getLoc(), qt, qt, q[0], q[1]); + } + + static UnionTable make(const size_t maxAmplitudes = 16, + const size_t maxHybridStates = 8) { + return {maxAmplitudes, maxHybridStates}; + } +}; + +//===----------------------------------------------------------------------===// +// Seeding +//===----------------------------------------------------------------------===// + +TEST_F(UnionTableTest, seedQubitStartsInZero) { + auto ut = make(); + ut.seedQubit(q[0]); + EXPECT_TRUE(ut.isTracked(q[0])); + EXPECT_TRUE(ut.isQubitAlwaysZero(q[0])); + EXPECT_FALSE(ut.isQubitAlwaysOne(q[0])); + EXPECT_FALSE(ut.areStatesAllTop()); +} + +TEST_F(UnionTableTest, seedQubitCantBeCalledTwice) { + auto ut = make(); + ut.seedQubit(q[0]); + ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ut.seedQubit(q[0]); // must not reset the qubit back to |0> + EXPECT_TRUE(ut.isQubitAlwaysOne(q[0])); +} + +TEST_F(UnionTableTest, seedClassicalRecordsConstant) { + auto ut = make(); + const Value c = builder.boolConstant(true); + ut.seedClassical(c, builder.getBoolAttr(true)); + EXPECT_TRUE(ut.isTracked(c)); + EXPECT_TRUE(ut.isClassicalAlwaysTrue(c)); + EXPECT_FALSE(ut.isClassicalAlwaysFalse(c)); +} + +TEST_F(UnionTableTest, untrackedValueQueriesAreFalse) { + const auto ut = make(); + EXPECT_FALSE(ut.isTracked(q[0])); + EXPECT_FALSE(ut.isQubitAlwaysZero(q[0])); + EXPECT_FALSE(ut.isClassicalAlwaysTrue(q[0])); +} + +//===----------------------------------------------------------------------===// +// Factorisation +//===----------------------------------------------------------------------===// + +TEST_F(UnionTableTest, independentQubitsStayFactored) { + auto ut = make(); + ut.seedQubit(q[0]); + ut.seedQubit(q[1]); + ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + EXPECT_TRUE(ut.isQubitAlwaysOne(q[0])); + EXPECT_TRUE(ut.isQubitAlwaysZero(q[1])); + // Two independent factors print on two lines (a coalesced pair would be one). + EXPECT_NE(printed(ut).find('\n'), std::string::npos); +} + +TEST_F(UnionTableTest, twoQubitGateMergeTargets) { + auto ut = make(); + ut.seedQubit(q[0]); + ut.seedQubit(q[1]); + ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(ut.applyMatrix2Q(q[0], q[1], q[0], q[1], dcxOp.getUnitaryMatrix()) + .succeeded()); + EXPECT_TRUE(ut.isQubitAlwaysZero(q[0])); + EXPECT_TRUE(ut.isQubitAlwaysOne(q[1])); +} + +TEST_F(UnionTableTest, controlledGateFiresAcrossSlots) { + auto ut = make(); + ut.seedQubit(q[0]); + ut.seedQubit(q[1]); + ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE( + ut.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}) + .succeeded()); + EXPECT_TRUE(ut.isQubitAlwaysOne(q[1])); +} + +TEST_F(UnionTableTest, controlledGateDoesNotFireWhenControlIsZero) { + auto ut = make(); + ut.seedQubit(q[0]); + ut.seedQubit(q[1]); + ASSERT_TRUE( + ut.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}) + .succeeded()); + EXPECT_TRUE(ut.isQubitAlwaysZero(q[1])); +} + +TEST_F(UnionTableTest, applyToUnseededQubitFails) { + auto ut = make(); + ut.seedQubit(q[0]); + EXPECT_TRUE(ut.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix()).failed()); +} + +//===----------------------------------------------------------------------===// +// Classical controls +//===----------------------------------------------------------------------===// + +TEST_F(UnionTableTest, classicalControlSkipsGate) { + auto ut = make(); + ut.seedQubit(q[0]); + const Value c = builder.boolConstant(false); + ut.seedClassical(c, builder.getBoolAttr(false)); + ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {}, {}, {c}) + .succeeded()); + EXPECT_TRUE(ut.isQubitAlwaysZero(q[0])); +} + +TEST_F(UnionTableTest, unresolvedClassicalControlFails) { + auto ut = make(); + ut.seedQubit(q[0]); + const Value c = + builder.boolConstant(false); + EXPECT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {}, {}, {c}) + .failed()); +} + +//===----------------------------------------------------------------------===// +// Measurement / reset +//===----------------------------------------------------------------------===// + +TEST_F(UnionTableTest, measureDeterministicRecordsBit) { + auto ut = make(); + ut.seedQubit(q[0]); + const Value result = builder.boolConstant(false); + ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(ut.measureQubit(q[0], q[0], result).succeeded()); + EXPECT_TRUE(ut.isClassicalAlwaysTrue(result)); +} + +TEST_F(UnionTableTest, measureSuperpositionTopsTheState) { + auto ut = make(); + ut.seedQubit(q[0]); + const Value result = builder.boolConstant(false); + ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(ut.measureQubit(q[0], q[0], result).succeeded()); + EXPECT_TRUE(ut.areStatesAllTop()); + EXPECT_FALSE(ut.isClassicalAlwaysTrue(result)); +} + +TEST_F(UnionTableTest, resetForcesZero) { + auto ut = make(); + ut.seedQubit(q[0]); + ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(ut.resetQubit(q[0], q[0]).succeeded()); + EXPECT_TRUE(ut.isQubitAlwaysZero(q[0])); +} + +//===----------------------------------------------------------------------===// +// Global phase +//===----------------------------------------------------------------------===// + +TEST_F(UnionTableTest, globalPhaseIsRecordedOnce) { + auto ut = make(); + ut.seedQubit(q[0]); + ASSERT_TRUE(ut.addGlobalPhase(std::numbers::pi).succeeded()); + EXPECT_NE(printed(ut).find("phase="), std::string::npos); +} + +//===----------------------------------------------------------------------===// +// Control analysis +//===----------------------------------------------------------------------===// + +TEST_F(UnionTableTest, controlsSatisfiableWhenBothCanBeOne) { + auto ut = make(); + ut.seedQubit(q[0]); + ut.seedQubit(q[1]); + ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(ut.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix()).succeeded()); + EXPECT_TRUE(ut.areControlsSatisfiable({q[0], q[1]})); +} + +TEST_F(UnionTableTest, controlsUnsatisfiableWhenAQubitIsAlwaysZero) { + auto ut = make(); + ut.seedQubit(q[0]); + ut.seedQubit(q[1]); + ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + EXPECT_FALSE(ut.areControlsSatisfiable({q[0], q[1]})); +} + +TEST_F(UnionTableTest, negativeClassicalControlSatisfiedByFalseConstant) { + auto ut = make(); + const Value c = builder.boolConstant(false); + ut.seedClassical(c, builder.getBoolAttr(false)); + EXPECT_FALSE(ut.areControlsSatisfiable({}, {c})); + EXPECT_TRUE(ut.areControlsSatisfiable({}, {}, {c})); +} + +TEST_F(UnionTableTest, superfluousControlsListsAlwaysOneQubit) { + auto ut = make(); + ut.seedQubit(q[0]); + ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + const auto result = ut.getSuperfluousControls({q[0]}); + EXPECT_FALSE(result.completelySuperfluous); + EXPECT_TRUE(result.superfluousQubits.contains(q[0])); +} + +TEST_F(UnionTableTest, superfluousControlsFlagsDeadGate) { + auto ut = make(); + ut.seedQubit(q[0]); + const auto result = ut.getSuperfluousControls({q[0]}); + EXPECT_TRUE(result.completelySuperfluous); +} + +//===----------------------------------------------------------------------===// +// markQubitsTop / forwarding +//===----------------------------------------------------------------------===// + +TEST_F(UnionTableTest, markQubitsTopClearsQuantumInfo) { + auto ut = make(); + ut.seedQubit(q[0]); + ut.markQubitsTop({q[0]}); + EXPECT_TRUE(ut.areStatesAllTop()); + EXPECT_FALSE(ut.isQubitAlwaysZero(q[0])); +} + +TEST_F(UnionTableTest, forwardValueRenamesQubit) { + auto ut = make(); + ut.seedQubit(q[0]); + ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + ut.forwardValue(q[0], q[1]); + EXPECT_FALSE(ut.isTracked(q[0])); + EXPECT_TRUE(ut.isQubitAlwaysOne(q[1])); +} + +//===----------------------------------------------------------------------===// +// join +//===----------------------------------------------------------------------===// + +TEST_F(UnionTableTest, joinOfAgreeingBranchesKeepsTheFact) { + auto a = make(); + a.seedQubit(q[0]); + ASSERT_TRUE(a.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + auto b = make(); + b.seedQubit(q[0]); + ASSERT_TRUE(b.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + + a.join(b); + EXPECT_TRUE(a.isQubitAlwaysOne(q[0])); + EXPECT_FALSE(a.isAllTop()); +} + +TEST_F(UnionTableTest, joinOfDisagreeingBranchesIsAProbabilisticSplit) { + auto a = make(); + a.seedQubit(q[0]); + auto b = make(); + b.seedQubit(q[0]); + ASSERT_TRUE(b.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + + a.join(b); + EXPECT_FALSE(a.isQubitAlwaysZero(q[0])); + EXPECT_FALSE(a.isQubitAlwaysOne(q[0])); + EXPECT_FALSE(a.isAllTop()); + EXPECT_NE(printed(a).find("p=0.5000"), std::string::npos); +} + +TEST_F(UnionTableTest, joinOfDifferentEntanglementStructureTops) { + auto a = make(); + a.seedQubit(q[0]); + a.seedQubit(q[1]); + ASSERT_TRUE(a.applyMatrix2Q(q[0], q[1], q[0], q[1], dcxOp.getUnitaryMatrix()) + .succeeded()); + auto b = make(); + b.seedQubit(q[0]); + b.seedQubit(q[1]); + + a.join(b); + EXPECT_TRUE(a.isAllTop()); +} + +TEST_F(UnionTableTest, joinKeepsClassicalFactOnlyWhenShared) { + const Value c = builder.boolConstant(true); + + auto a = make(); + a.seedClassical(c, builder.getBoolAttr(true)); + auto agree = make(); + agree.seedClassical(c, builder.getBoolAttr(true)); + a.join(agree); + EXPECT_TRUE(a.isClassicalAlwaysTrue(c)); + + auto d = make(); + d.seedClassical(c, builder.getBoolAttr(true)); + auto disagree = make(); + disagree.seedClassical(c, builder.getBoolAttr(false)); + d.join(disagree); + EXPECT_FALSE(d.isClassicalAlwaysTrue(c)); + EXPECT_FALSE(d.isClassicalAlwaysFalse(c)); +} + +TEST_F(UnionTableTest, joinOverflowingAFactorTopsOnlyThatFactor) { + auto a = make(16, 2); + a.seedQubit(q[0]); + a.seedQubit(q[1]); + auto b = make(16, 2); + b.seedQubit(q[0]); + b.seedQubit(q[1]); + ASSERT_TRUE(b.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + a.join(b); + ASSERT_FALSE(a.isAllTop()); + + auto c = make(16, 2); + c.seedQubit(q[0]); + c.seedQubit(q[1]); + ASSERT_TRUE(c.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + a.join(c); + + EXPECT_FALSE(a.isAllTop()); + EXPECT_FALSE(a.isQubitAlwaysZero(q[0])); // {q0} factor collapsed to top + EXPECT_TRUE(a.isQubitAlwaysZero(q[1])); // {q1} factor reconciled normally +} + +//===----------------------------------------------------------------------===// +// Equality +//===----------------------------------------------------------------------===// + +TEST_F(UnionTableTest, equalityIsOrderIndependent) { + auto a = make(); + a.seedQubit(q[0]); + a.seedQubit(q[1]); + auto b = make(); + b.seedQubit(q[1]); + b.seedQubit(q[0]); + EXPECT_TRUE(a == b); +} + +TEST_F(UnionTableTest, equalitySeesAppliedGates) { + auto a = make(); + a.seedQubit(q[0]); + auto b = make(); + b.seedQubit(q[0]); + ASSERT_TRUE(b.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); + EXPECT_FALSE(a == b); +} + +TEST_F(UnionTableTest, markAllTopIsAbsorbing) { + auto ut = make(); + ut.seedQubit(q[0]); + ut.markAllTop(); + EXPECT_TRUE(ut.isAllTop()); + EXPECT_TRUE(ut.areStatesAllTop()); + EXPECT_EQ(printed(ut), ""); + ut.seedQubit(q[1]); + EXPECT_FALSE(ut.isTracked(q[1])); +} + +} // namespace From 2ac8801c05e48365ce11a586aa0233278e95a7e0 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sat, 29 Aug 2026 12:54:03 +0200 Subject: [PATCH 20/55] :construction: Added handling of values instead of classical constants, assisted-by Sonnet 5 via Claude Code --- .../ConstantPropagation/HybridState.cpp | 53 ++++++++++++++++--- .../ConstantPropagation/HybridState.hpp | 30 +++++++---- .../ConstantPropagation/test_hybridState.cpp | 31 +++++++++-- 3 files changed, 95 insertions(+), 19 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 21b6ed9ce6..78ce99299f 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -15,11 +15,14 @@ #include #include +#include #include #include #include #include #include +#include +#include #include #include @@ -42,14 +45,26 @@ bool ctrlRenameOk(const ArrayRef ctrlsIn, /// @brief Truthiness of a resolved classical constant (non-zero == true), or /// nullopt if attr is not an integer/index/bool/float constant. std::optional classicalTruth(const Attribute attr) { - if (const auto ia = dyn_cast(attr)) { + if (const auto ia = dyn_cast_if_present(attr)) { return !ia.getValue().isZero(); } - if (const auto fa = dyn_cast(attr)) { + if (const auto fa = dyn_cast_if_present(attr)) { return !fa.getValue().isZero(); } return std::nullopt; } + +/// @brief Numeric value of a resolved classical constant, or nullopt if attr is +/// not an integer/index/bool/float constant. +std::optional classicalDouble(const Attribute attr) { + if (const auto ia = dyn_cast_if_present(attr)) { + return static_cast(ia.getValue().getSExtValue()); + } + if (const auto fa = dyn_cast_if_present(attr)) { + return fa.getValueAsDouble(); + } + return std::nullopt; +} } // namespace //===----------------------------------------------------------------------===// @@ -161,9 +176,10 @@ LogicalResult HybridState::applyMatrix1Q( return failure(); } if (*hold) { - return state.applyMatrix1Q(in, out, matrix, quantumCtrlsIn, quantumCtrlsOut); + return state.applyMatrix1Q(in, out, matrix, quantumCtrlsIn, + quantumCtrlsOut); } - // Classical control false: the gate is skipped, only the identities thread on. + // Classical control false: only the identities thread on. state.forwardQubit(in, out); state.forwardQubits(quantumCtrlsIn, quantumCtrlsOut); return success(); @@ -195,7 +211,7 @@ HybridState::applyMatrix2Q(const Value in0, const Value in1, const Value out0, } LogicalResult -HybridState::addGlobalPhase(const double theta, +HybridState::addGlobalPhase(const Value theta, const ArrayRef quantumCtrlsIn, const ArrayRef quantumCtrlsOut, const ArrayRef posClassicalCtrls, @@ -207,17 +223,40 @@ HybridState::addGlobalPhase(const double theta, if (failed(hold)) { return failure(); } + const auto angle = classicalDouble(classical.lookup(theta)); + if (!angle) { + return failure(); + } if (*hold) { if (!quantumCtrlsIn.empty()) { - return state.applyControlledPhase(theta, quantumCtrlsIn, quantumCtrlsOut); + return state.applyControlledPhase(*angle, quantumCtrlsIn, + quantumCtrlsOut); } - globalPhase *= std::exp(Complex{0.0, theta}); + globalPhase *= std::exp(Complex{0.0, *angle}); return success(); } state.forwardQubits(quantumCtrlsIn, quantumCtrlsOut); return success(); } +void HybridState::propagateClassical(Operation* const op) { + SmallVector operands; + operands.reserve(op->getNumOperands()); + for (const Value operand : op->getOperands()) { + operands.push_back(classical.lookup(operand)); + } + SmallVector folded; + if (failed(op->fold(operands, folded)) || + folded.size() != op->getNumResults()) { + return; + } + for (const auto& [result, foldResult] : llvm::zip(op->getResults(), folded)) { + if (const auto attr = dyn_cast(foldResult)) { + setClassical(result, attr); + } + } +} + //===----------------------------------------------------------------------===// // Measurement / reset //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index 5c1c00b07d..ef2d0d22f6 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -220,28 +220,40 @@ class HybridState { ArrayRef negClassicalCtrls = {}); /** - * @brief Adds a global phase exp(i*theta). + * @brief Adds a global phase exp(i*theta), where theta is a classical value + * resolved from this branch's constants. * * Uncontrolled: accumulated into globalPhase. With quantum controls: a * relative phase on the subspace where every control is |1>. * - * @param theta The phase to add. - * @param quantumCtrlsIn The qubits that have to be |1> to apply the matrix. + * @param theta The classical value holding the rotation angle in radians. + * @param quantumCtrlsIn The qubits that have to be |1> to apply the phase. * @param quantumCtrlsOut The qubits that quantumCtrlsIn are changed to. * @param posClassicalCtrls The classical values that have to be true - * (nonzero) to apply the matrix. + * (nonzero) to apply the phase. * @param negClassicalCtrls The classical values that have to be false (zero) - * to apply the matrix. - * @return failure() if a control qubit is not in this state or a classical - * control is unresolved. + * to apply the phase. + * @return failure() if a control qubit is not in this state, a classical + * control is unresolved, or - when the phase would apply - theta is not a + * resolved constant (each indicates a propagation bug). */ [[nodiscard("HybridState::addGlobalPhase called but ignored")]] - LogicalResult addGlobalPhase(double theta, - ArrayRef quantumCtrlsIn = {}, + LogicalResult addGlobalPhase(Value theta, ArrayRef quantumCtrlsIn = {}, ArrayRef quantumCtrlsOut = {}, ArrayRef posClassicalCtrls = {}, ArrayRef negClassicalCtrls = {}); + /** + * @brief Folds a classical operation using this branch's resolved constants + * and records any constant results. + * + * Operands not resolved in this branch are passed to the folder as unknown; a + * result that does not fold to a constant is left untracked. + * + * @param op The classical operation to fold (its operands and results). + */ + void propagateClassical(Operation* op); + //===--------------------------------------------------------------------===// // Measurement / reset //===--------------------------------------------------------------------===// diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp index b3c31623c1..d39d4c2c31 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -226,24 +226,49 @@ TEST_F(HybridStateTest, floatClassicalControlIsSupported) { TEST_F(HybridStateTest, uncontrolledGlobalPhaseAccumulates) { auto hs = make({q[0]}); - ASSERT_TRUE(hs.addGlobalPhase(std::acos(-1.0)).succeeded()); + const Value theta = builder.floatConstant(std::acos(-1.0)); + hs.setClassical(theta, builder.getF64FloatAttr(std::acos(-1.0))); + ASSERT_TRUE(hs.addGlobalPhase(theta).succeeded()); EXPECT_LT(std::abs(hs.getGlobalPhase() - Complex{-1.0, 0.0}), 1e-9); } TEST_F(HybridStateTest, quantumControlledPhaseIsNotGlobal) { auto hs = make({q[0], q[1]}); + const Value theta = builder.floatConstant(std::acos(-1.0)); + hs.setClassical(theta, builder.getF64FloatAttr(std::acos(-1.0))); ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); - ASSERT_TRUE(hs.addGlobalPhase(std::acos(-1.0), {q[0]}).succeeded()); + ASSERT_TRUE(hs.addGlobalPhase(theta, {q[0]}).succeeded()); EXPECT_LT(std::abs(hs.getGlobalPhase() - Complex{1.0, 0.0}), 1e-9); } TEST_F(HybridStateTest, globalPhaseSkippedByClassicalControl) { auto hs = make({q[0]}); + const Value theta = builder.floatConstant(std::acos(-1.0)); + hs.setClassical(theta, builder.getF64FloatAttr(std::acos(-1.0))); hs.setClassical(cA, builder.getBoolAttr(false)); - ASSERT_TRUE(hs.addGlobalPhase(std::acos(-1.0), {}, {}, {cA}).succeeded()); + ASSERT_TRUE(hs.addGlobalPhase(theta, {}, {}, {cA}).succeeded()); EXPECT_LT(std::abs(hs.getGlobalPhase() - Complex{1.0, 0.0}), 1e-9); } +TEST_F(HybridStateTest, globalPhaseFailsWhenThetaUnresolved) { + auto hs = make({q[0]}); + const Value theta = builder.floatConstant(std::acos(-1.0)); // never seeded + EXPECT_TRUE(hs.addGlobalPhase(theta).failed()); +} + +TEST_F(HybridStateTest, propagateClassicalFoldsConstants) { + auto hs = make({}); + const Value lhs = builder.intConstant(3); + const Value rhs = builder.intConstant(4); + hs.setClassical(lhs, builder.getIntegerAttr(lhs.getType(), 3)); + hs.setClassical(rhs, builder.getIntegerAttr(rhs.getType(), 4)); + auto add = arith::AddIOp::create(builder, builder.getLoc(), lhs, rhs); + hs.propagateClassical(add.getOperation()); + const auto folded = hs.getClassical(add.getResult()); + ASSERT_TRUE(folded.has_value()); + EXPECT_EQ(dyn_cast(*folded).getInt(), 7); +} + //===----------------------------------------------------------------------===// // tensor //===----------------------------------------------------------------------===// From 6f58de793747c5804eb7b96f2566bd1cc604fa25 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sat, 29 Aug 2026 12:58:06 +0200 Subject: [PATCH 21/55] :construction: Added handling of values instead of classical constants, assisted-by Sonnet 5 via Claude Code --- .../ConstantPropagation/UnionTable.cpp | 66 ++++++++++++------- .../ConstantPropagation/UnionTable.hpp | 38 ++++++++--- .../ConstantPropagation/test_unionTable.cpp | 16 ++++- 3 files changed, 86 insertions(+), 34 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index fff935ae08..e4fa05d726 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -314,7 +315,7 @@ UnionTable::applyMatrix2Q(const Value in0, const Value in1, const Value out0, } LogicalResult -UnionTable::addGlobalPhase(const double theta, +UnionTable::addGlobalPhase(const Value theta, const ArrayRef quantumCtrlsIn, const ArrayRef quantumCtrlsOut, const ArrayRef posClassicalCtrls, @@ -322,11 +323,13 @@ UnionTable::addGlobalPhase(const double theta, if (allTop) { return success(); } - if (quantumCtrlsIn.empty() && !quantumCtrlsOut.empty()) { + if ((quantumCtrlsIn.empty() && !quantumCtrlsOut.empty()) || + !isTracked(theta)) { return failure(); } - SmallVector touched(quantumCtrlsIn.begin(), quantumCtrlsIn.end()); + SmallVector touched{theta}; + touched.append(quantumCtrlsIn.begin(), quantumCtrlsIn.end()); touched.append(posClassicalCtrls.begin(), posClassicalCtrls.end()); touched.append(negClassicalCtrls.begin(), negClassicalCtrls.end()); mergeSlots(touched); @@ -334,31 +337,46 @@ UnionTable::addGlobalPhase(const double theta, return success(); } - if (!quantumCtrlsIn.empty() || !posClassicalCtrls.empty() || - !negClassicalCtrls.empty()) { - Value anchor; - if (!quantumCtrlsIn.empty()) { - anchor = quantumCtrlsIn.front(); - } else { - anchor = posClassicalCtrls.empty() ? negClassicalCtrls.front() - : posClassicalCtrls.front(); - } - for (auto& hs : slots[*slotIndexContaining(anchor)]) { - if (failed(hs.addGlobalPhase(theta, quantumCtrlsIn, quantumCtrlsOut, - posClassicalCtrls, negClassicalCtrls))) { - return failure(); - } + Value anchor = theta; + if (!quantumCtrlsIn.empty()) { + anchor = quantumCtrlsIn.front(); + } else if (!posClassicalCtrls.empty()) { + anchor = posClassicalCtrls.front(); + } else if (!negClassicalCtrls.empty()) { + anchor = negClassicalCtrls.front(); + } + + const auto slot = slotIndexContaining(anchor); + if (!slot) { + return failure(); + } + for (auto& hs : slots[*slot]) { + if (failed(hs.addGlobalPhase(theta, quantumCtrlsIn, quantumCtrlsOut, + posClassicalCtrls, negClassicalCtrls))) { + return failure(); } - return success(); } + return success(); +} - if (slots.empty()) { - Slot slot; - slot.emplace_back(QuantumState(ArrayRef{}, maxNonzeroAmplitudes), - maxNonzeroAmplitudes, 1.0); - slots.push_back(std::move(slot)); +void UnionTable::propagateClassical(Operation* const op) { + if (allTop) { + return; + } + const SmallVector operands(op->getOperands().begin(), + op->getOperands().end()); + mergeSlots(operands); + if (allTop) { + return; + } + for (const Value operand : operands) { + if (const auto slot = slotIndexContaining(operand)) { + for (auto& hs : slots[*slot]) { + hs.propagateClassical(op); + } + return; + } } - return slots.front().front().addGlobalPhase(theta); } LogicalResult diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index f604ae6a74..c8a1a2733e 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -65,8 +65,8 @@ class UnionTable { private: bool allTop = false; - size_t maxNonzeroAmplitudes; - size_t maxHybridStates; + size_t maxNonzeroAmplitudes = 0; + size_t maxHybridStates = 0; SmallVector slots; /// @brief Index of the slot that holds v (as a qubit or a classical key). @@ -119,6 +119,13 @@ class UnionTable { : maxNonzeroAmplitudes(maxNonzeroAmplitudes), maxHybridStates(maxHybridStates) {} + /** + * @brief A zero-budget table (every merge overflows to top). The dataflow + * framework needs a default-constructible lattice payload; the analysis + * overwrites it with a budgeted table before any real state flows through. + */ + UnionTable() = default; + //===--------------------------------------------------------------------===// // Seeding //===--------------------------------------------------------------------===// @@ -223,27 +230,40 @@ class UnionTable { ArrayRef negClassicalCtrls = {}); /** - * @brief Adds a global phase exp(i*theta). + * @brief Adds a global phase exp(i*theta), where theta is a classical value. * - * Uncontrolled: accumulated into one representative HybridState's global - * phase. With quantum controls: a relative phase on the controlled subspace. + * The slot holding theta (and any controls) is coalesced, then each + * alternative resolves theta from its own constants and applies the phase - + * uncontrolled into its global phase, controlled as a relative phase. * - * @param theta The phase to add. + * @param theta The classical value holding the rotation angle in radians. * @param quantumCtrlsIn The qubits that have to be |1> for the phase. * @param quantumCtrlsOut The qubits that quantumCtrlsIn are changed to. * @param posClassicalCtrls The classical values that have to be true * (nonzero) for the phase. * @param negClassicalCtrls The classical values that have to be false (zero) * for the phase. - * @return failure() if a control value is absent, the control in/out lengths - * mismatch, or a classical control is unresolved. + * @return failure() if theta or a control value is absent, the control in/out + * lengths mismatch, or theta / a classical control is not a resolved constant + * where the phase would apply (each indicating a propagation bug). */ [[nodiscard("UnionTable::addGlobalPhase called but ignored")]] LogicalResult - addGlobalPhase(double theta, ArrayRef quantumCtrlsIn = {}, + addGlobalPhase(Value theta, ArrayRef quantumCtrlsIn = {}, ArrayRef quantumCtrlsOut = {}, ArrayRef posClassicalCtrls = {}, ArrayRef negClassicalCtrls = {}); + /** + * @brief Folds a classical operation across the distribution. + * + * Merges the slots of op 's tracked operands, then folds op per alternative + * with that alternative's constants, recording any constant results. A result + * that does not fold stays untracked. + * + * @param op The classical operation to propagate. + */ + void propagateClassical(Operation* op); + /** * @brief Measures in (renamed to out), recording the outcome in * classicalResult. diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 1ac7caf244..9f108026f7 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -225,10 +225,24 @@ TEST_F(UnionTableTest, resetForcesZero) { TEST_F(UnionTableTest, globalPhaseIsRecordedOnce) { auto ut = make(); ut.seedQubit(q[0]); - ASSERT_TRUE(ut.addGlobalPhase(std::numbers::pi).succeeded()); + const Value theta = builder.floatConstant(std::numbers::pi); + ut.seedClassical(theta, builder.getF64FloatAttr(std::numbers::pi)); + ASSERT_TRUE(ut.addGlobalPhase(theta).succeeded()); EXPECT_NE(printed(ut).find("phase="), std::string::npos); } +TEST_F(UnionTableTest, propagateClassicalFoldsAcrossSlots) { + auto ut = make(); + const Value lhs = builder.intConstant(2); + const Value rhs = builder.intConstant(5); + ut.seedClassical(lhs, builder.getIntegerAttr(lhs.getType(), 2)); + ut.seedClassical(rhs, builder.getIntegerAttr(rhs.getType(), 5)); + auto add = arith::AddIOp::create(builder, builder.getLoc(), lhs, rhs); + ut.propagateClassical(add.getOperation()); + EXPECT_TRUE(ut.isTracked(add.getResult())); + EXPECT_FALSE(ut.isClassicalAlwaysFalse(add.getResult())); +} + //===----------------------------------------------------------------------===// // Control analysis //===----------------------------------------------------------------------===// From 09e695b4254ae35688a3e33a1b926e326725581e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sat, 29 Aug 2026 15:32:55 +0200 Subject: [PATCH 22/55] :construction: Added ConstantPropagationAnalysis, assisted-by Sonnet 5 via Claude Code --- .../lib/Dialect/QCO/Transforms/CMakeLists.txt | 5 + .../ConstantPropagationAnalysis.cpp | 353 ++++++++++++++++++ .../ConstantPropagationAnalysis.hpp | 133 +++++++ .../ConstantPropagation/QuantumState.cpp | 4 + .../Transforms/Optimizations/CMakeLists.txt | 7 + .../test_constantPropagationAnalysis.cpp | 195 ++++++++++ 6 files changed, 697 insertions(+) create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp create mode 100644 mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp diff --git a/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt b/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt index 621df3444e..0718d2dc74 100644 --- a/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt @@ -15,10 +15,15 @@ add_mlir_library( PUBLIC MQTCompilerTarget PRIVATE + MLIRAnalysis + MLIRControlFlowInterfaces + MLIRFunctionInterfaces MLIRQCODialect MLIRQCOUtils + MLIRQTensorDialect MLIRQTensorUtils MLIRArithDialect + MLIRFuncDialect MLIRMathDialect MLIRMQTDialect MLIRMQTTransforms diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp new file mode 100644 index 0000000000..9c3f10defe --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "ConstantPropagationAnalysis.hpp" + +#include "UnionTable.hpp" +#include "mlir/Dialect/MQT/IR/MQTDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Utils/Matrix.h" +#include "mlir/Dialect/QTensor/IR/QTensorOps.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace mlir::qco { + +namespace { + +/// @brief Materializes a value range into an owned vector. +template SmallVector toVec(Range&& range) { + return {range.begin(), range.end()}; +} + +/// @brief Whether v is a qubit argument of the entry-point function's entry +/// block (so its initial state is |0>, per the pass contract). Arguments of any +/// other function have unknown provenance. +bool isEntryPointQubitArgument(const Value v) { + const auto arg = dyn_cast(v); + if (!arg || !isa(arg.getType())) { + return false; + } + Block* const block = arg.getOwner(); + return block->isEntryBlock() && block->getParentOp() != nullptr && + mqt::isEntryPoint(block->getParentOp()); +} + +/// @brief Ensures every qubit operand of op is tracked before use: an +/// entry-point argument starts in |0>, anything else of unknown provenance +/// collapses to top. +void ensureSeeded(UnionTable& table, Operation* const op) { + for (const Value operand : op->getOperands()) { + if (!isa(operand.getType()) || table.isTracked(operand)) { + continue; + } + table.seedQubit(operand); + if (!isEntryPointQubitArgument(operand)) { + table.markQubitsTop(operand); + } + } +} + +} // namespace + +//===----------------------------------------------------------------------===// +// UnionTableLattice +//===----------------------------------------------------------------------===// + +ChangeResult UnionTableLattice::join(const AbstractDenseLattice& other) { + const auto& rhs = static_cast(other); + if (!rhs.initialized) { + return ChangeResult::NoChange; + } + return joinUnionTable(rhs.table); +} + +ChangeResult UnionTableLattice::setUnionTable(UnionTable next) { + if (initialized && next == table) { + return ChangeResult::NoChange; + } + table = std::move(next); + initialized = true; + return ChangeResult::Change; +} + +ChangeResult UnionTableLattice::joinUnionTable(const UnionTable& rhs) { + if (!initialized) { + return setUnionTable(rhs); + } + UnionTable joined = table; + joined.join(rhs); + return setUnionTable(std::move(joined)); +} + +void UnionTableLattice::print(raw_ostream& os) const { + if (!initialized) { + os << ""; + return; + } + table.print(os); +} + +//===----------------------------------------------------------------------===// +// ConstantPropagationAnalysis +//===----------------------------------------------------------------------===// + +ConstantPropagationAnalysis::ConstantPropagationAnalysis( + DataFlowSolver& solver, const size_t maxNonzeroAmplitudes, + const size_t maxHybridStates) + : DenseForwardDataFlowAnalysis(solver), + maxNonzeroAmplitudes(maxNonzeroAmplitudes), + maxHybridStates(maxHybridStates) {} + +LogicalResult ConstantPropagationAnalysis::initialize(Operation* top) { + // Does not reason across a call boundary: any call anywhere in the module + // makes every program point top. Uncalled helper functions are fine - the + // analysis just treats their (non-entry-point) qubit arguments as unknown. + bool hasCall = false; + top->walk([&](Operation* op) { hasCall |= isa(op); }); + bailToTop = hasCall; + return DenseForwardDataFlowAnalysis::initialize(top); +} + +UnionTable ConstantPropagationAnalysis::freshTable() const { + UnionTable table(maxNonzeroAmplitudes, maxHybridStates); + if (bailToTop) { + table.markAllTop(); + } + return table; +} + +void ConstantPropagationAnalysis::setToEntryState(UnionTableLattice* lattice) { + // Entry qubits are seeded lazily on first use (see ensureSeeded); the entry + // state is just an empty, budgeted table. + propagateIfChanged(lattice, lattice->setUnionTable(freshTable())); +} + +LogicalResult ConstantPropagationAnalysis::visitOperation( + Operation* op, const UnionTableLattice& before, UnionTableLattice* after) { + if (bailToTop) { + propagateIfChanged(after, after->setUnionTable(freshTable())); + return success(); + } + + // Region-branch ops (qco.if, qco.index_switch) are handled through + // visitRegionBranchControlFlowTransfer; nothing to do for the op itself. + if (isa(op)) { + return success(); + } + + // Bodies of qco.ctrl / qco.inv / qco.pow are interpreted by the enclosing + // modifier's handler, so their nested ops just pass the state through. + if (Operation* const parent = op->getParentOp(); + parent != nullptr && isa(parent)) { + propagateIfChanged(after, after->setUnionTable(before.getUnionTable())); + return success(); + } + + UnionTable table = before.getUnionTable(); + if (failed(applyOperation(table, op, /*quantumControls=*/{}))) { + return op->emitError() + << "constant propagation cannot interpret '" << op->getName() + << "' (unsupported operation, or a propagation bug left the state " + "inconsistent)"; + } + propagateIfChanged(after, after->setUnionTable(std::move(table))); + return success(); +} + +LogicalResult ConstantPropagationAnalysis::applyOperation( + UnionTable& table, Operation* op, const ArrayRef quantumControls) { + ensureSeeded(table, op); + + return TypeSwitch(op) + .Case([&](AllocOp alloc) { + table.seedQubit(alloc.getResult()); + return success(); + }) + .Case([&](StaticOp stat) { + table.seedQubit(stat.getQubit()); + return success(); + }) + .Case([&](qtensor::ExtractOp extract) { + table.seedQubit(extract.getResult()); + return success(); + }) + .Case( + [](Operation*) { return success(); }) + .Case( + [&](arith::ConstantOp constant) -> LogicalResult { + const Attribute value = constant.getValue(); + if (!isa(value)) { + return failure(); + } + table.seedClassical(constant.getResult(), value); + return success(); + }) + .Case([&](MeasureOp measure) { + return table.measureQubit(measure.getQubitIn(), measure.getQubitOut(), + measure.getResult()); + }) + .Case([&](ResetOp reset) { + return table.resetQubit(reset.getQubitIn(), reset.getQubitOut()); + }) + .Case([&](GPhaseOp gphase) { + return table.addGlobalPhase(gphase.getTheta(), quantumControls, + quantumControls); + }) + .Case([&](const CtrlOp ctrl) { + return applyCtrl(table, ctrl, quantumControls); + }) + .Case([](Operation*) { + // Region-branch ops are routed by the framework + // (visitRegionBranchControlFlowTransfer); reaching one here means it is + // nested in a modifier body, which the QCO verifier forbids. + return failure(); + }) + .Case([&](UnitaryOpInterface gate) { + // Every remaining unitary: base gates, and qco.inv / qco.pow bodies + // (which apply here when they expose a compile-time matrix, top out + // otherwise). + return applyUnitary(table, gate, quantumControls); + }) + .Default([&](Operation* other) -> LogicalResult { + // Not a QCO operation. Anything clear of qubits is a classical op to + // fold; an unrecognized qubit-touching op is unsupported. + const auto isQubit = [](const Type t) { return isa(t); }; + if (llvm::any_of(other->getOperandTypes(), isQubit) || + llvm::any_of(other->getResultTypes(), isQubit)) { + return failure(); + } + table.propagateClassical(other); + return success(); + }); +} + +LogicalResult ConstantPropagationAnalysis::applyUnitary( + UnionTable& table, UnitaryOpInterface gate, + const ArrayRef quantumControls) { + const auto targetsIn = toVec(gate.getInputTargets()); + const auto targetsOut = toVec(gate.getOutputTargets()); + + Matrix2x2 matrix2; + if (gate.getNumTargets() == 1 && gate.getUnitaryMatrix2x2(matrix2)) { + return table.applyMatrix1Q(targetsIn[0], targetsOut[0], matrix2, + quantumControls, quantumControls); + } + Matrix4x4 matrix4; + if (gate.getNumTargets() == 2 && gate.getUnitaryMatrix4x4(matrix4)) { + return table.applyMatrix2Q(targetsIn[0], targetsIn[1], targetsOut[0], + targetsOut[1], matrix4, quantumControls, + quantumControls); + } + // Parametric-without-constant, >2-qubit, dynamic-matrix, or an unmodelled + // qco.inv / qco.pow body: the targets become top. + table.markQubitsTop(targetsIn); + table.forwardValues(targetsIn, targetsOut); + return success(); +} + +LogicalResult +ConstantPropagationAnalysis::applyCtrl(UnionTable& table, CtrlOp ctrl, + const ArrayRef quantumControls) { + Block& body = ctrl.getRegion().front(); + + table.forwardValues(toVec(ctrl.getInputTargets()), + toVec(body.getArguments())); + + SmallVector innerControls(quantumControls); + llvm::append_range(innerControls, ctrl.getInputControls()); + + for (Operation& nested : body.without_terminator()) { + if (failed(applyOperation(table, &nested, innerControls))) { + return failure(); + } + } + + auto yield = cast(body.getTerminator()); + table.forwardValues(toVec(yield.getOperands()), + toVec(ctrl.getOutputTargets())); + table.forwardValues(toVec(ctrl.getInputControls()), + toVec(ctrl.getOutputControls())); + return success(); +} + +void ConstantPropagationAnalysis::visitRegionBranchControlFlowTransfer( + RegionBranchOpInterface branch, const std::optional regionFrom, + const std::optional regionTo, const UnionTableLattice& before, + UnionTableLattice* after) { + // nullopt = the parent op; a value = the index of one of `branch`'s regions. + auto ifOp = dyn_cast(branch.getOperation()); + if (bailToTop || !ifOp || !before.isInitialized()) { + // bailToTop, qco.index_switch, or any not-yet-modelled region-branch op: + // a plain join of `before` into `after`, without the operand->block-arg + // (enter) / yield->result (leave) renaming that Case A / Case C do below. + // The renamed value is thus untracked downstream, so the next + // applyOperation that consumes it runs ensureSeeded, which - the value not + // being an entry-point argument - marks its qubits top. (Under bailToTop + // `before` is already all-top; an uninitialized `before` makes the join a + // no-op.) + DenseForwardDataFlowAnalysis::visitRegionBranchControlFlowTransfer( + branch, regionFrom, regionTo, before, after); + return; + } + + UnionTable table = before.getUnionTable(); + + if (!regionFrom.has_value() && regionTo.has_value()) { + // Entering a branch: the op's linear operands become the region's block + // arguments. Both then and else regions get the same incoming state; a + // constant condition is exploited at rewrite time. + Block& body = ifOp->getRegion(*regionTo).front(); + table.forwardValues(toVec(ifOp.getQubits()), toVec(body.getArguments())); + propagateIfChanged(after, after->setUnionTable(std::move(table))); + return; + } + + if (!regionFrom.has_value() || regionTo.has_value()) { + // Happens if loops produce region -> region calls (not supported) or a + // region is empty (e.g. an else branch does not exist) + return; + } + + // Leaving a branch: the region's yield becomes the op's results. Classical + // results precede the linear ones. Each region contributes one exit edge; the + // lattice accumulates them via join. + auto yield = + cast(ifOp->getRegion(*regionFrom).front().getTerminator()); + const size_t numClassical = ifOp.getClassicalResults().size(); + table.forwardValues(toVec(yield.getOperands().take_front(numClassical)), + toVec(ifOp.getClassicalResults())); + table.forwardValues(toVec(yield.getOperands().drop_front(numClassical)), + toVec(ifOp.getLinearResults())); + propagateIfChanged(after, after->joinUnionTable(table)); +} + +} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp new file mode 100644 index 0000000000..762b417929 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "UnionTable.hpp" +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace mlir::qco { + +/** + * @brief The dense-lattice payload for the constant-propagation analysis: one + * UnionTable per program point. + * + * The lattice has an explicit *uninitialized* (bottom) state so that the + * framework's join-accumulation over control-flow edges works: joining bottom + * with a value adopts the value; joining two values delegates to + * UnionTable::join. + */ +class UnionTableLattice : public dataflow::AbstractDenseLattice { + UnionTable table; + bool initialized = false; + +public: + MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(UnionTableLattice) + + using AbstractDenseLattice::AbstractDenseLattice; + + ChangeResult join(const AbstractDenseLattice& other) override; + + void print(raw_ostream& os) const override; + + [[nodiscard("UnionTableLattice::isInitialized is called but ignored")]] bool + isInitialized() const { + return initialized; + } + + /// @brief The payload. Only meaningful once @isInitialized. + [[nodiscard("UnionTableLattice::getUnionTable is called but " + "ignored")]] const UnionTable& + getUnionTable() const { + return table; + } + + /// @brief Replaces the payload; returns whether it changed. + ChangeResult setUnionTable(UnionTable next); + + /// @brief Joins rhs into the payload (adopting it if still uninitialized). + ChangeResult joinUnionTable(const UnionTable& rhs); +}; + +/** + * Forward dense data-flow analysis that threads a UnionTable through a QCO + * program, interpreting gates, measurements, resets, global phases, classical + * folds, control modifiers, and constant/branching `qco.if`. + * + * Unsupported constructs (`scf.for`, `qco.index_switch`, and any operation + * touching qubits that the analysis cannot model) make the pass fail via + * `emitError`. Precision losses (parametric gates, `qco.inv` / `qco.pow` + * bodies, non-constant `qco.if`) collapse the affected qubits to top instead. + * + * v2.0 does not reason across a call boundary: if the module contains any call, + * the analysis conservatively reports top everywhere (@ref bailToTop). Uncalled + * helper functions are tolerated - only the entry-point function's qubit + * arguments are assumed to be |0>; every other function's are treated as + * unknown. + */ +class ConstantPropagationAnalysis + : public dataflow::DenseForwardDataFlowAnalysis { + size_t maxNonzeroAmplitudes; + size_t maxHybridStates; + + /// @brief Set in @ref initialize when the module contains any call; every + /// program point is then top. + bool bailToTop = false; + + /// @brief A budgeted empty table, or an all-top one when @ref bailToTop. + [[nodiscard]] UnionTable freshTable() const; + + /// @brief Dispatches a single operation onto table, given the quantum + /// controls accumulated by any enclosing qco.ctrl. + LogicalResult applyOperation(UnionTable& table, Operation* op, + ArrayRef quantumControls); + + /// @brief Applies a unitary operation: its 1-/2-qubit matrix if available, + /// otherwise the targets become top (parametric, >2-qubit, dynamic-matrix, or + /// an unmodelled qco.inv / qco.pow body). + static LogicalResult applyUnitary(UnionTable& table, UnitaryOpInterface gate, + ArrayRef quantumControls); + + /// @brief Interprets a qco.ctrl body, extending the control context. + LogicalResult applyCtrl(UnionTable& table, CtrlOp ctrl, + ArrayRef quantumControls); + +public: + ConstantPropagationAnalysis(DataFlowSolver& solver, + size_t maxNonzeroAmplitudes, + size_t maxHybridStates); + + LogicalResult initialize(Operation* top) override; + + LogicalResult visitOperation(Operation* op, const UnionTableLattice& before, + UnionTableLattice* after) override; + + void visitRegionBranchControlFlowTransfer( + RegionBranchOpInterface branch, std::optional regionFrom, + std::optional regionTo, const UnionTableLattice& before, + UnionTableLattice* after) override; + + void setToEntryState(UnionTableLattice* lattice) override; +}; + +} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 4a8f1f13ba..f1b0639973 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -398,6 +398,10 @@ bool QuantumState::operator==(const QuantumState& that) const { } void QuantumState::print(raw_ostream& os) const { + if (top) { + os << ""; + return; + } if (qubits.empty()) { return; } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index 0deaf3c402..49ecdf16f9 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -9,6 +9,7 @@ set(target_name mqt-core-mlir-unittest-optimizations) add_executable( ${target_name} + ConstantPropagation/test_constantPropagationAnalysis.cpp ConstantPropagation/test_hybridState.cpp ConstantPropagation/test_quantumState.cpp ConstantPropagation/test_unionTable.cpp @@ -23,12 +24,18 @@ add_executable( target_link_libraries( ${target_name} PRIVATE GTest::gtest_main + MLIRAnalysis MLIRControlFlowDialect + MLIRControlFlowInterfaces + MLIRFunctionInterfaces MLIRQCODDFunctionality MLIRQCOProgramBuilder MLIRQCOPrograms MLIRQCOTransforms MLIRQCOUtils + MLIRQTensorDialect + MLIRArithDialect + MLIRFuncDialect MLIRParser MLIRIR MLIRPass diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp new file mode 100644 index 0000000000..d31f1c37a6 --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "ConstantPropagation/ConstantPropagationAnalysis.hpp" +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +using namespace mlir; +using namespace mlir::qco; + +/// Runs the analysis over module and returns a " -> " line +/// for every operation, in walk order. +std::string analyze(ModuleOp module, const size_t maxAmplitudes = 16, + const size_t maxHybridStates = 8) { + DataFlowSolver solver; + solver.load(); + solver.load(); + solver.load(maxAmplitudes, maxHybridStates); + if (failed(solver.initializeAndRun(module))) { + return ""; + } + + std::string out; + llvm::raw_string_ostream os(out); + module.walk([&](Operation* op) { + if (isa(op)) { + return; + } + os << op->getName().getStringRef() << " -> "; + if (const auto* lattice = solver.lookupState( + solver.getProgramPointAfter(op))) { + lattice->print(os); + } else { + os << ""; + } + os << "\n"; + }); + return out; +} + +class ConstantPropagationAnalysisTest : public testing::Test { +protected: + MLIRContext context; + QCOProgramBuilder builder; + + ConstantPropagationAnalysisTest() : builder(&context) {} + + void SetUp() override { + DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + builder.initialize(); + } +}; + +TEST_F(ConstantPropagationAnalysisTest, allocSeedsZeroAndGateInterprets) { + auto reg = builder.allocQubitRegister(1); + builder.x(reg[0]); + const auto module = builder.finalize(); + + const std::string dump = analyze(*module); + EXPECT_EQ(dump.find(""), std::string::npos); + EXPECT_NE(dump.find("qco.x -> "), std::string::npos); + EXPECT_NE(dump.find("|1> -> 1.00"), std::string::npos); +} + +TEST_F(ConstantPropagationAnalysisTest, uncalledHelperDoesNotDisturbEntry) { + auto reg = builder.allocQubitRegister(1); + builder.x(reg[0]); + auto module = builder.finalize(); + + // An uncalled helper function is tolerated: the entry stays precise. + OpBuilder ob(module->getContext()); + ob.setInsertionPointToEnd(module->getBody()); + func::FuncOp::create(ob, module->getLoc(), "helper", + ob.getFunctionType({}, {})) + .setPrivate(); + + const std::string dump = analyze(*module); + EXPECT_EQ(dump.find(""), std::string::npos); + EXPECT_NE(dump.find("|1> -> 1.00"), std::string::npos); +} + +TEST_F(ConstantPropagationAnalysisTest, anyCallBailsToTop) { + auto reg = builder.allocQubitRegister(1); + builder.x(reg[0]); + auto module = builder.finalize(); + + OpBuilder ob(module->getContext()); + ob.setInsertionPointToEnd(module->getBody()); + auto callee = func::FuncOp::create(ob, module->getLoc(), "callee", + ob.getFunctionType({}, {})); + callee.setPrivate(); + auto entry = *module->getBody()->getOps().begin(); + ob.setInsertionPointToStart(&entry.getBody().front()); + func::CallOp::create(ob, module->getLoc(), callee, ValueRange{}); + + const std::string dump = analyze(*module); + EXPECT_EQ(dump.find(""), std::string::npos); + EXPECT_NE(dump.find("qco.x -> "), std::string::npos); +} + +TEST_F(ConstantPropagationAnalysisTest, independentGatesStayFactored) { + auto reg = builder.allocQubitRegister(2); + builder.x(reg[0]); + builder.x(reg[1]); + const auto module = builder.finalize(); + + const std::string dump = analyze(*module); + EXPECT_EQ(dump.find(""), std::string::npos); + EXPECT_EQ(dump.find("|11> -> 1.00"), std::string::npos); + EXPECT_NE(dump.find("|1> -> 1.00"), std::string::npos); +} + +TEST_F(ConstantPropagationAnalysisTest, entanglingGateMergedFactors) { + auto reg = builder.allocQubitRegister(2); + const Value q0 = builder.x(reg[0]); + builder.dcx(q0, reg[1]); + const auto module = builder.finalize(); + + const std::string dump = analyze(*module); + EXPECT_EQ(dump.find(""), std::string::npos); + EXPECT_NE(dump.find("qco.dcx -> "), std::string::npos); + EXPECT_NE(dump.find("|10> -> 1.00"), std::string::npos); +} + +TEST_F(ConstantPropagationAnalysisTest, controlledGateFires) { + auto reg = builder.allocQubitRegister(2); + const Value q0 = builder.h(reg[0]); + builder.cx(q0, reg[1]); + const auto module = builder.finalize(); + + const std::string dump = analyze(*module); + EXPECT_EQ(dump.find(""), std::string::npos); + EXPECT_NE(dump.find("|00> -> 0.71"), std::string::npos); + EXPECT_NE(dump.find("|11> -> 0.71"), std::string::npos); +} + +TEST_F(ConstantPropagationAnalysisTest, measuringSuperpositionTops) { + auto reg = builder.allocQubitRegister(1); + const Value q0 = builder.h(reg[0]); + builder.measure(q0); + const auto module = builder.finalize(); + + const std::string dump = analyze(*module); + EXPECT_EQ(dump.find(""), std::string::npos); + // H builds a real superposition... + EXPECT_NE(dump.find("[|0> -> 0.71, |1> -> 0.71]"), std::string::npos); + // ...and measuring it tops that qubit's state (no v2.0 hybrid-state split); + // the measured qubit prints as from qco.measure onward. + EXPECT_NE(dump.find("qco.measure ->"), std::string::npos); + EXPECT_NE(dump.find("[]"), std::string::npos); +} + +TEST_F(ConstantPropagationAnalysisTest, deterministicMeasurementRecordsBit) { + auto reg = builder.allocQubitRegister(1); + const Value q0 = builder.x(reg[0]); + builder.measure(q0); + const auto module = builder.finalize(); + + const std::string dump = analyze(*module); + EXPECT_EQ(dump.find(""), std::string::npos); + EXPECT_NE(dump.find("qco.measure -> "), std::string::npos); + EXPECT_NE(dump.find("classical:"), std::string::npos); +} + +} // namespace From 8919e4ea9958f5c3dab4718a4dfb91ccbad62908 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sat, 29 Aug 2026 16:52:37 +0200 Subject: [PATCH 23/55] :construction: Added ConstantPropagationPass, assisted-by Sonnet 5 via Claude Code --- .../Optimizations/ConstantPropagation.cpp | 70 +++++++-- .../ConstantPropagation/Decisions.hpp | 49 +++++++ .../ConstantPropagation/Rewriter.cpp | 138 ++++++++++++++++++ .../ConstantPropagation/Rewriter.hpp | 43 ++++++ .../Transforms/Optimizations/CMakeLists.txt | 1 + .../test_qco_constant_propagation.cpp | 123 ++++++++++++++++ 6 files changed, 413 insertions(+), 11 deletions(-) create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Decisions.hpp create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.hpp create mode 100644 mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 80dde7a537..3a22fea96a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -8,10 +8,20 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "ConstantPropagation/ConstantPropagationAnalysis.hpp" +#include "ConstantPropagation/Decisions.hpp" +#include "ConstantPropagation/Rewriter.hpp" +#include "mlir/Dialect/MQT/IR/MQTDialect.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" +#include +#include +#include +#include #include +#include +#include +#include namespace mlir::qco { @@ -23,23 +33,61 @@ namespace { /** * @brief Quantum constant propagation. * - * Assumes all input qubits start in |0>, propagates the quantum/classical state - * through the circuit up to a complexity threshold, and removes operations that - * are superfluous given that state. + * Assumes every input qubit of the entry-point function starts in |0>, + * propagates the quantum/classical state through the circuit up to a complexity + * threshold (an MLIR `DenseForwardDataFlowAnalysis` over a `UnionTable` + * lattice), then removes operations that are superfluous given that state. * - * The analysis is done as an MLIR `DenseForwardDataFlowAnalysis` over a - * `UnionTable` lattice, with a separate rewrite phase driven by the computed - * facts. + * Rewrites: delete a `qco.ctrl` whose controls can never all hold, and strip + * the always-satisfied controls from a `qco.ctrl` that keeps at least one live + * control. Analyze and rewrite alternate until a fixpoint because a removed + * gate can change a later gate's control facts. */ struct ConstantPropagation final : impl::ConstantPropagationBase { using ConstantPropagationBase::ConstantPropagationBase; void runOnOperation() override { - // TODO(mlir/constant-propagation-v2): implement in stages -- - // 1. QuantumState, 2. UnionTable/HybridState, 3. - // ConstantPropagationAnalysis, - // 4. Decisions + Rewriter + driver, 5. pass-level tests. + ModuleOp module = getOperation(); + + func::FuncOp entry; + for (auto func : module.getOps()) { + if (!mqt::isEntryPoint(func)) { + continue; + } + if (entry) { + module.emitError( + "constant propagation supports a single entry-point function"); + return signalPassFailure(); + } + entry = func; + } + if (!entry) { + return; + } + + IRRewriter rewriter(&getContext()); + constexpr unsigned maxRounds = 64; + for (unsigned round = 0; round < maxRounds; ++round) { + DataFlowSolver solver; + solver.load(); + solver.load(); + solver.load(maximumNonzeroAmplitudes, + maximumHybridStates); + if (failed(solver.initializeAndRun(module))) { + return signalPassFailure(); // the analysis emitted the diagnostic + } + + const SmallVector decisions = collectDecisions(entry, solver); + if (decisions.empty()) { + return; // fixpoint reached + } + applyDecisions(decisions, rewriter); + } + + entry.emitError("constant propagation did not converge within ") + << maxRounds << " rounds"; + signalPassFailure(); } }; diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Decisions.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Decisions.hpp new file mode 100644 index 0000000000..715dcf2089 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Decisions.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/QCO/IR/QCOOps.h" + +#include +#include + +#include + +namespace mlir::qco { + +/** + * A controlled gate whose control configuration can never be satisfied in the + * current state: the body never runs, so the whole CtrlOp is deleted and every + * qubit passes straight through. + */ +struct DropOp { + CtrlOp op; +}; +// TODO: To remove all controls +/** + * @brief A controlled gate and *strict subset* of control qubits that provably + * always hold: the op is rebuilt with only the remaining controls. + * + * dropControlIndices indexes into op.getInputControls(). Indices, not values, + * so an earlier rewrite in the same batch (which may RAUW this op's operands) + * cannot invalidate the decision. The all-controls-redundant case (which would + * turn the op into an uncontrolled gate) is out of v2.0 scope and never + * produced here. + */ +struct StripControls { + CtrlOp op; + SmallVector dropControlIndices; +}; + +/// @brief One rewrite the pass has decided to perform. +using Decision = std::variant; + +} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp new file mode 100644 index 0000000000..34a5d2f183 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "Rewriter.hpp" + +#include "ConstantPropagationAnalysis.hpp" +#include "Decisions.hpp" +#include "UnionTable.hpp" +#include "mlir/Dialect/QCO/IR/QCOOps.h" + +#include +#include +#include +#include + +namespace mlir::qco { + +SmallVector collectDecisions(func::FuncOp entry, + DataFlowSolver& solver) { + SmallVector decisions; + + entry.walk([&](CtrlOp op) { + // A controlled gate nested in another modifier's body is interpreted by + // that modifier's handler and not rewritten. + if (isa(op->getParentOp())) { + return; + } + + const auto* lattice = + solver.lookupState(solver.getProgramPointBefore(op)); + if (lattice == nullptr || !lattice->isInitialized()) { + return; + } + const UnionTable& table = lattice->getUnionTable(); + if (table.isAllTop()) { + return; + } + + const SmallVector controls(op.getInputControls().begin(), + op.getInputControls().end()); + + if (!table.areControlsSatisfiable(controls)) { + decisions.push_back(DropOp{op}); + return; + } + + const SuperfluousResult superfluous = + table.getSuperfluousControls(controls); + if (superfluous.completelySuperfluous) { + decisions.push_back(DropOp{op}); + return; + } + + SmallVector dropIndices; + for (const auto& [index, control] : llvm::enumerate(controls)) { + if (superfluous.superfluousQubits.contains(control)) { + dropIndices.push_back(static_cast(index)); + } + } + // Stripping *every* control would turn this into an uncontrolled gate - + // deferred past v2.0. Only rebuild when a real control remains. + if (!dropIndices.empty() && dropIndices.size() < controls.size()) { + decisions.push_back(StripControls{op, std::move(dropIndices)}); + } + }); + + return decisions; +} + +namespace { + +/// @brief Erases a never-firing controlled gate: every output qubit is replaced +/// by the matching input. +void applyDrop(const DropOp& drop, IRRewriter& rewriter) { + CtrlOp op = drop.op; + for (auto [in, out] : + llvm::zip_equal(op.getInputQubits(), op.getOutputQubits())) { + rewriter.replaceAllUsesWith(out, in); + } + rewriter.eraseOp(op); +} + +/// @brief Rebuilds a controlled gate with a subset of its controls. The body +/// region's block arguments alias the *targets* only, so it moves across +/// untouched. +void applyStrip(const StripControls& strip, IRRewriter& rewriter) { + CtrlOp op = strip.op; + const auto controlsIn = op.getInputControls(); + const auto isDropped = [&](const size_t index) { + return llvm::is_contained(strip.dropControlIndices, + static_cast(index)); + }; + + SmallVector keptControls; + for (const auto& [index, control] : llvm::enumerate(controlsIn)) { + if (!isDropped(index)) { + keptControls.push_back(control); + } + } + + rewriter.setInsertionPoint(op); + auto newOp = + CtrlOp::create(rewriter, op.getLoc(), keptControls, op.getInputTargets()); + rewriter.inlineRegionBefore(op.getRegion(), newOp.getRegion(), + newOp.getRegion().end()); + + for (const auto& [index, control] : llvm::enumerate(controlsIn)) { + rewriter.replaceAllUsesWith( + op.getOutputControl(index), + isDropped(index) ? control : newOp.getOutputForInput(control)); + } + for (const auto& [index, target] : llvm::enumerate(op.getInputTargets())) { + rewriter.replaceAllUsesWith(op.getOutputTarget(index), + newOp.getOutputForInput(target)); + } + rewriter.eraseOp(op); +} + +} // namespace + +void applyDecisions(const ArrayRef decisions, IRRewriter& rewriter) { + for (const Decision& decision : decisions) { + if (const auto* drop = std::get_if(&decision)) { + applyDrop(*drop, rewriter); + } else { + applyStrip(std::get(decision), rewriter); + } + } +} + +} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.hpp new file mode 100644 index 0000000000..f830cf83fc --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "Decisions.hpp" + +#include +#include +#include +#include + +namespace mlir::qco { + +/** + * @brief Walks entry in program order and, from the constant-propagation facts + * already computed in solver, collects the rewrites to perform. + * + * Pure: touches no IR. Only top-level controlled gates are considered - a + * CtrlOp nested in another modifier body is left alone. A program point with no + * lattice, an uninitialised lattice, or an all-top table yields no decision for + * that op. + */ +[[nodiscard]] SmallVector collectDecisions(func::FuncOp entry, + DataFlowSolver& solver); + +/** + * @brief Applies decisions to the IR via rewriter. + * + * Decisions are independent (distinct ops, no nested-body overlap) and use + * operand indices rather than values, so batch application is + * order-insensitive. + */ +void applyDecisions(ArrayRef decisions, IRRewriter& rewriter); + +} // namespace mlir::qco diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index 49ecdf16f9..ffc319a08a 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable( ConstantPropagation/test_hybridState.cpp ConstantPropagation/test_quantumState.cpp ConstantPropagation/test_unionTable.cpp + test_qco_constant_propagation.cpp test_qco_hadamard_lifting.cpp test_qco_measurement_lifting.cpp test_qco_merge_single_qubit_rotation.cpp diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp new file mode 100644 index 0000000000..447f7838d9 --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" +#include "mlir/Dialect/QTensor/IR/QTensorDialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +using namespace mlir; +using namespace mlir::qco; + +/// Every qco.ctrl in the module, in walk order. +SmallVector ctrlOps(ModuleOp module) { + SmallVector ops; + module.walk([&](const CtrlOp op) { ops.push_back(op); }); + return ops; +} + +class ConstantPropagationTest : public testing::Test { +protected: + MLIRContext context; + QCOProgramBuilder builder; + + ConstantPropagationTest() : builder(&context) {} + + void SetUp() override { + DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + builder.initialize(); + } + + static LogicalResult run(ModuleOp module, const std::size_t maxAmplitudes = 4, + const std::size_t maxHybridStates = 4) { + PassManager pm(module.getContext()); + pm.addPass(createConstantPropagation( + ConstantPropagationOptions{.maximumNonzeroAmplitudes = maxAmplitudes, + .maximumHybridStates = maxHybridStates})); + return pm.run(module); + } +}; + +TEST_F(ConstantPropagationTest, dropsGateWithUnsatisfiableControl) { + auto reg = builder.allocQubitRegister(2); + // reg[0] stays |0>, so the controlled X can never fire. + builder.cx(reg[0], reg[1]); + const auto module = builder.finalize(); + + ASSERT_TRUE(succeeded(run(*module))); + EXPECT_TRUE(succeeded(verify(*module))); + EXPECT_TRUE(ctrlOps(*module).empty()); +} + +TEST_F(ConstantPropagationTest, stripsAlwaysSatisfiedControl) { + auto reg = builder.allocQubitRegister(3); + const Value one = builder.x(reg[0]); + const Value sup = builder.h(reg[2]); + const SmallVector controls{one, sup}; + builder.mcx(controls, reg[1]); + const auto module = builder.finalize(); + + ASSERT_TRUE(succeeded(run(*module))); + EXPECT_TRUE(succeeded(verify(*module))); + + auto ctrls = ctrlOps(*module); + ASSERT_EQ(ctrls.size(), 1U); + EXPECT_EQ(ctrls.front().getNumControls(), 1U); +} + +TEST_F(ConstantPropagationTest, keepsGateWhenEveryControlRedundant) { + auto reg = builder.allocQubitRegister(2); + const Value one = builder.x(reg[0]); // always |1> + (void)builder.cx(one, reg[1]); + const auto module = builder.finalize(); + + ASSERT_TRUE(succeeded(run(*module))); + EXPECT_TRUE(succeeded(verify(*module))); + + // Unwrapping a fully-redundant control to an uncontrolled gate is out of + // v2.0 scope: the op is left untouched. + auto ctrls = ctrlOps(*module); + ASSERT_EQ(ctrls.size(), 1U); + EXPECT_EQ(ctrls.front().getNumControls(), 1U); +} + +TEST_F(ConstantPropagationTest, leavesGateAloneWhenStateIsImprecise) { + auto reg = builder.allocQubitRegister(2); + const Value sup = builder.h(reg[0]); + builder.cx(sup, reg[1]); + const auto module = builder.finalize(); + + // Budget of one amplitude forces reg[0] to top before the controlled gate. + ASSERT_TRUE(succeeded(run(*module, 1))); + EXPECT_TRUE(succeeded(verify(*module))); + EXPECT_EQ(ctrlOps(*module).size(), 1U); +} + +} // namespace From a489f17fd830e5a6b36529d464a173cbed77d189 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sat, 29 Aug 2026 17:32:20 +0200 Subject: [PATCH 24/55] :construction: Added removal of all ctrl qubits, assisted-by Sonnet 5 via Claude Code --- .../Optimizations/ConstantPropagation.cpp | 9 +++-- .../ConstantPropagation/Decisions.hpp | 17 +++++---- .../ConstantPropagation/Rewriter.cpp | 37 ++++++++++++++++--- .../test_qco_constant_propagation.cpp | 17 ++++----- 4 files changed, 53 insertions(+), 27 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 3a22fea96a..c8ed9e572a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -38,10 +38,11 @@ namespace { * threshold (an MLIR `DenseForwardDataFlowAnalysis` over a `UnionTable` * lattice), then removes operations that are superfluous given that state. * - * Rewrites: delete a `qco.ctrl` whose controls can never all hold, and strip - * the always-satisfied controls from a `qco.ctrl` that keeps at least one live - * control. Analyze and rewrite alternate until a fixpoint because a removed - * gate can change a later gate's control facts. + * Rewrites: delete a `qco.ctrl` whose controls can never all hold, and remove + * the always-satisfied controls from a `qco.ctrl` - rebuilding it with the rest, + * or inlining its body when every control was redundant. Analyze and rewrite + * alternate until a fixpoint because a removed gate can change a later gate's + * control facts. Classical controls are not reasoned about yet. */ struct ConstantPropagation final : impl::ConstantPropagationBase { diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Decisions.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Decisions.hpp index 715dcf2089..314ee41606 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Decisions.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Decisions.hpp @@ -13,7 +13,6 @@ #include "mlir/Dialect/QCO/IR/QCOOps.h" #include -#include #include @@ -27,16 +26,18 @@ namespace mlir::qco { struct DropOp { CtrlOp op; }; -// TODO: To remove all controls + /** - * @brief A controlled gate and *strict subset* of control qubits that provably - * always hold: the op is rebuilt with only the remaining controls. + * @brief A controlled gate and the control qubits that provably always hold in + * the current state. + * + * If a real control remains, the op is rebuilt with only those. If + * dropControlIndices covers *every* control, the gate runs unconditionally and + * its body is inlined in place of the op. * * dropControlIndices indexes into op.getInputControls(). Indices, not values, - * so an earlier rewrite in the same batch (which may RAUW this op's operands) - * cannot invalidate the decision. The all-controls-redundant case (which would - * turn the op into an uncontrolled gate) is out of v2.0 scope and never - * produced here. + * so an earlier rewrite in the same batch cannot invalidate the decision. + * Classical controls are not considered yet. */ struct StripControls { CtrlOp op; diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp index 34a5d2f183..cef44c8177 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include namespace mlir::qco { @@ -64,9 +66,9 @@ SmallVector collectDecisions(func::FuncOp entry, dropIndices.push_back(static_cast(index)); } } - // Stripping *every* control would turn this into an uncontrolled gate - - // deferred past v2.0. Only rebuild when a real control remains. - if (!dropIndices.empty() && dropIndices.size() < controls.size()) { + // A strict subset is stripped; all of them means the gate fires + // unconditionally and its body is inlined (see applyStrip). + if (!dropIndices.empty()) { decisions.push_back(StripControls{op, std::move(dropIndices)}); } }); @@ -87,9 +89,13 @@ void applyDrop(const DropOp& drop, IRRewriter& rewriter) { rewriter.eraseOp(op); } -/// @brief Rebuilds a controlled gate with a subset of its controls. The body -/// region's block arguments alias the *targets* only, so it moves across -/// untouched. +/// @brief Removes always-satisfied controls from a controlled gate. +/// +/// A CtrlOp's body block arguments alias its *targets* only - controls merely +/// pass through - so dropping a subset just rebuilds the op around the same +/// body. Dropping every control means the body runs unconditionally: it is +/// inlined in place of the op, with the target block arguments bound to the +/// target operands and the yielded values taking over the op's target results. void applyStrip(const StripControls& strip, IRRewriter& rewriter) { CtrlOp op = strip.op; const auto controlsIn = op.getInputControls(); @@ -106,6 +112,25 @@ void applyStrip(const StripControls& strip, IRRewriter& rewriter) { } rewriter.setInsertionPoint(op); + + if (keptControls.empty()) { + Block& body = op.getRegion().front(); + auto yield = cast(body.getTerminator()); + const auto yielded = yield.getOperands(); + rewriter.inlineBlockBefore(&body, op, op.getInputTargets()); + for (auto [result, value] : + llvm::zip_equal(op.getOutputTargets(), yielded)) { + rewriter.replaceAllUsesWith(result, value); + } + for (auto [result, control] : + llvm::zip_equal(op.getOutputControls(), controlsIn)) { + rewriter.replaceAllUsesWith(result, control); + } + rewriter.eraseOp(yield); + rewriter.eraseOp(op); + return; + } + auto newOp = CtrlOp::create(rewriter, op.getLoc(), keptControls, op.getInputTargets()); rewriter.inlineRegionBefore(op.getRegion(), newOp.getRegion(), diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index 447f7838d9..5e43ee807d 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -92,20 +92,19 @@ TEST_F(ConstantPropagationTest, stripsAlwaysSatisfiedControl) { EXPECT_EQ(ctrls.front().getNumControls(), 1U); } -TEST_F(ConstantPropagationTest, keepsGateWhenEveryControlRedundant) { +TEST_F(ConstantPropagationTest, unwrapsGateWhenEveryControlRedundant) { auto reg = builder.allocQubitRegister(2); - const Value one = builder.x(reg[0]); // always |1> - (void)builder.cx(one, reg[1]); - const auto module = builder.finalize(); + const Value one = builder.x(reg[0]); + builder.cx(one, reg[1]); + auto module = builder.finalize(); ASSERT_TRUE(succeeded(run(*module))); EXPECT_TRUE(succeeded(verify(*module))); - // Unwrapping a fully-redundant control to an uncontrolled gate is out of - // v2.0 scope: the op is left untouched. - auto ctrls = ctrlOps(*module); - ASSERT_EQ(ctrls.size(), 1U); - EXPECT_EQ(ctrls.front().getNumControls(), 1U); + EXPECT_TRUE(ctrlOps(*module).empty()); + unsigned xGates = 0; + module->walk([&](XOp) { ++xGates; }); + EXPECT_EQ(xGates, 2U); } TEST_F(ConstantPropagationTest, leavesGateAloneWhenStateIsImprecise) { From beaf11b7ec4e25a6c587ef4e160bf35593d3cf23 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 09:15:46 +0200 Subject: [PATCH 25/55] :white_check_mark: Added tests for constant propagation, assisted-by Sonnet 5 via Claude Code --- .../test_qco_constant_propagation.cpp | 131 +++++++++++++++--- 1 file changed, 114 insertions(+), 17 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index 5e43ee807d..b6262e52d1 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -32,11 +33,22 @@ namespace { using namespace mlir; using namespace mlir::qco; -/// Every qco.ctrl in the module, in walk order. -SmallVector ctrlOps(ModuleOp module) { - SmallVector ops; - module.walk([&](const CtrlOp op) { ops.push_back(op); }); - return ops; +/// Number of ops of a given kind anywhere in the module (bodies included). +template unsigned countOps(ModuleOp module) { + unsigned n = 0; + module.walk([&](OpT) { ++n; }); + return n; +} + +/// The first op of a given kind in walk order, or a null handle if there is +/// none. +template OpT firstOp(ModuleOp module) { + OpT found; + module.walk([&](OpT op) { + found = op; + return WalkResult::interrupt(); + }); + return found; } class ConstantPropagationTest : public testing::Test { @@ -67,13 +79,12 @@ class ConstantPropagationTest : public testing::Test { TEST_F(ConstantPropagationTest, dropsGateWithUnsatisfiableControl) { auto reg = builder.allocQubitRegister(2); - // reg[0] stays |0>, so the controlled X can never fire. builder.cx(reg[0], reg[1]); const auto module = builder.finalize(); ASSERT_TRUE(succeeded(run(*module))); EXPECT_TRUE(succeeded(verify(*module))); - EXPECT_TRUE(ctrlOps(*module).empty()); + EXPECT_EQ(countOps(*module), 0U); } TEST_F(ConstantPropagationTest, stripsAlwaysSatisfiedControl) { @@ -87,24 +98,21 @@ TEST_F(ConstantPropagationTest, stripsAlwaysSatisfiedControl) { ASSERT_TRUE(succeeded(run(*module))); EXPECT_TRUE(succeeded(verify(*module))); - auto ctrls = ctrlOps(*module); - ASSERT_EQ(ctrls.size(), 1U); - EXPECT_EQ(ctrls.front().getNumControls(), 1U); + ASSERT_EQ(countOps(*module), 1U); + EXPECT_EQ(firstOp(*module).getNumControls(), 1U); } TEST_F(ConstantPropagationTest, unwrapsGateWhenEveryControlRedundant) { auto reg = builder.allocQubitRegister(2); const Value one = builder.x(reg[0]); builder.cx(one, reg[1]); - auto module = builder.finalize(); + const auto module = builder.finalize(); ASSERT_TRUE(succeeded(run(*module))); EXPECT_TRUE(succeeded(verify(*module))); - EXPECT_TRUE(ctrlOps(*module).empty()); - unsigned xGates = 0; - module->walk([&](XOp) { ++xGates; }); - EXPECT_EQ(xGates, 2U); + EXPECT_EQ(countOps(*module), 0U); + EXPECT_EQ(countOps(*module), 2U); } TEST_F(ConstantPropagationTest, leavesGateAloneWhenStateIsImprecise) { @@ -113,10 +121,99 @@ TEST_F(ConstantPropagationTest, leavesGateAloneWhenStateIsImprecise) { builder.cx(sup, reg[1]); const auto module = builder.finalize(); - // Budget of one amplitude forces reg[0] to top before the controlled gate. ASSERT_TRUE(succeeded(run(*module, 1))); EXPECT_TRUE(succeeded(verify(*module))); - EXPECT_EQ(ctrlOps(*module).size(), 1U); + EXPECT_EQ(countOps(*module), 1U); +} + +TEST_F(ConstantPropagationTest, dropsGateWhenOneOfSeveralControlsIsAlwaysZero) { + auto reg = builder.allocQubitRegister(3); + const Value one = builder.x(reg[0]); + const SmallVector controls{one, reg[1]}; + builder.mcx(controls, reg[2]); + const auto module = builder.finalize(); + + ASSERT_TRUE(succeeded(run(*module))); + EXPECT_TRUE(succeeded(verify(*module))); + EXPECT_EQ(countOps(*module), 0U); + EXPECT_EQ(countOps(*module), 1U); +} + +TEST_F(ConstantPropagationTest, stripsTrailingAlwaysOneControl) { + auto reg = builder.allocQubitRegister(3); + const Value sup = builder.h(reg[0]); + const Value one = builder.x(reg[1]); + const SmallVector controls{sup, one}; + builder.mcx(controls, reg[2]); + const auto module = builder.finalize(); + + ASSERT_TRUE(succeeded(run(*module))); + EXPECT_TRUE(succeeded(verify(*module))); + + ASSERT_EQ(countOps(*module), 1U); + auto ctrl = firstOp(*module); + EXPECT_EQ(ctrl.getNumControls(), 1U); + EXPECT_TRUE(ctrl.getInputControl(0) == sup); + EXPECT_EQ(countOps(*module), 2U); +} + +TEST_F(ConstantPropagationTest, stripsAllButOneControl) { + auto reg = builder.allocQubitRegister(4); + const Value a = builder.x(reg[0]); + const Value b = builder.x(reg[1]); + const Value sup = builder.h(reg[3]); + const SmallVector controls{a, b, sup}; + builder.mcx(controls, reg[2]); + const auto module = builder.finalize(); + + ASSERT_TRUE(succeeded(run(*module))); + EXPECT_TRUE(succeeded(verify(*module))); + + ASSERT_EQ(countOps(*module), 1U); + auto ctrl = firstOp(*module); + EXPECT_EQ(ctrl.getNumControls(), 1U); + EXPECT_TRUE(ctrl.getInputControl(0) == sup); +} + +TEST_F(ConstantPropagationTest, unwrapsMultiControlGateWhenAllControlsRedundant) { + auto reg = builder.allocQubitRegister(3); + const Value a = builder.x(reg[0]); + const Value b = builder.x(reg[1]); + const SmallVector controls{a, b}; + builder.mcx(controls, reg[2]); + const auto module = builder.finalize(); + + ASSERT_TRUE(succeeded(run(*module))); + EXPECT_TRUE(succeeded(verify(*module))); + + EXPECT_EQ(countOps(*module), 0U); + EXPECT_EQ(countOps(*module), 3U); +} + +TEST_F(ConstantPropagationTest, simplifiesChainOfControlledGates) { + auto reg = builder.allocQubitRegister(3); + const Value q0 = builder.x(reg[0]); + const Value q1 = builder.cx(q0, reg[1]).second; + builder.cx(q1, reg[2]); + const auto module = builder.finalize(); + + ASSERT_TRUE(succeeded(run(*module))); + EXPECT_TRUE(succeeded(verify(*module))); + + EXPECT_EQ(countOps(*module), 0U); + EXPECT_EQ(countOps(*module), 3U); +} + +TEST_F(ConstantPropagationTest, noControlledGatesIsNoOp) { + auto reg = builder.allocQubitRegister(2); + (void)builder.x(reg[0]); + (void)builder.h(reg[1]); + const auto module = builder.finalize(); + + ASSERT_TRUE(succeeded(run(*module))); + EXPECT_TRUE(succeeded(verify(*module))); + EXPECT_EQ(countOps(*module), 1U); + EXPECT_EQ(countOps(*module), 1U); } } // namespace From 958af966c9764935111166049403084b102f57be Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 09:27:53 +0200 Subject: [PATCH 26/55] :construction: Added registering of constant propagation --- mlir/lib/Support/Passes.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/lib/Support/Passes.cpp b/mlir/lib/Support/Passes.cpp index 8c1302a361..9775e38cf1 100644 --- a/mlir/lib/Support/Passes.cpp +++ b/mlir/lib/Support/Passes.cpp @@ -51,6 +51,7 @@ runWithPassManager(ModuleOp mod, void registerMQTCompilerPasses() { static const auto REGISTERED = [] { registerConvertCBitToMemRef(); + qco::registerConstantPropagation(); qco::registerDecomposeMultiControlled(); qco::registerFuseSingleQubitUnitaryRuns(); qco::registerHadamardLifting(); From fa9bd22cb22886acdfe8772a6617187ec831ae73 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 09:37:16 +0200 Subject: [PATCH 27/55] :memo: Corrected docstring --- .../ConstantPropagationAnalysis.hpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp index 762b417929..1ee115e6c1 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp @@ -79,11 +79,10 @@ class UnionTableLattice : public dataflow::AbstractDenseLattice { * `emitError`. Precision losses (parametric gates, `qco.inv` / `qco.pow` * bodies, non-constant `qco.if`) collapse the affected qubits to top instead. * - * v2.0 does not reason across a call boundary: if the module contains any call, - * the analysis conservatively reports top everywhere (@ref bailToTop). Uncalled - * helper functions are tolerated - only the entry-point function's qubit - * arguments are assumed to be |0>; every other function's are treated as - * unknown. + * Does not call across a boundary: if the module contains any call, the + * analysis reports top everywhere. Uncalled helper functions are tolerated - + * only the entry-point function's qubit arguments are assumed to be |0>; every + * other function's qubits are treated as unknown. */ class ConstantPropagationAnalysis : public dataflow::DenseForwardDataFlowAnalysis { @@ -106,7 +105,7 @@ class ConstantPropagationAnalysis /// otherwise the targets become top (parametric, >2-qubit, dynamic-matrix, or /// an unmodelled qco.inv / qco.pow body). static LogicalResult applyUnitary(UnionTable& table, UnitaryOpInterface gate, - ArrayRef quantumControls); + ArrayRef quantumControls); /// @brief Interprets a qco.ctrl body, extending the control context. LogicalResult applyCtrl(UnionTable& table, CtrlOp ctrl, @@ -122,10 +121,11 @@ class ConstantPropagationAnalysis LogicalResult visitOperation(Operation* op, const UnionTableLattice& before, UnionTableLattice* after) override; - void visitRegionBranchControlFlowTransfer( - RegionBranchOpInterface branch, std::optional regionFrom, - std::optional regionTo, const UnionTableLattice& before, - UnionTableLattice* after) override; + void visitRegionBranchControlFlowTransfer(RegionBranchOpInterface branch, + std::optional regionFrom, + std::optional regionTo, + const UnionTableLattice& before, + UnionTableLattice* after) override; void setToEntryState(UnionTableLattice* lattice) override; }; From 7ecb300d8ddfbb836c67c811523dc3252f80a44c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 07:42:56 +0000 Subject: [PATCH 28/55] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QCO/Transforms/Passes.td | 11 +++++------ .../Transforms/Optimizations/ConstantPropagation.cpp | 8 ++++---- .../ConstantPropagation/test_quantumState.cpp | 6 ++++-- .../ConstantPropagation/test_unionTable.cpp | 3 +-- .../Optimizations/test_qco_constant_propagation.cpp | 3 ++- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 8e093c6eb4..0b50aa7ad0 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -185,12 +185,11 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { def ConstantPropagation : Pass<"constant-propagation", "mlir::ModuleOp"> { let dependentDialects = ["mlir::qco::QCODialect", "::mlir::arith::ArithDialect"]; - let summary = - "This pass applies constant propagation to a circuit. It " - "assumes that all input qubits are |0>. It propagates the " - "state of the qubits up to a given complexity threshold and " - "removes gates which are superfluous considering the current " - "state."; + let summary = "This pass applies constant propagation to a circuit. It " + "assumes that all input qubits are |0>. It propagates the " + "state of the qubits up to a given complexity threshold and " + "removes gates which are superfluous considering the current " + "state."; let description = [{ This pass applies quantum constant propagation. This optimization routine assumes that the input qubits of the circuits are |0>. It propagates the qubit states and the state of additional classical values through the circuit. diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index c8ed9e572a..68f2342721 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -39,10 +39,10 @@ namespace { * lattice), then removes operations that are superfluous given that state. * * Rewrites: delete a `qco.ctrl` whose controls can never all hold, and remove - * the always-satisfied controls from a `qco.ctrl` - rebuilding it with the rest, - * or inlining its body when every control was redundant. Analyze and rewrite - * alternate until a fixpoint because a removed gate can change a later gate's - * control facts. Classical controls are not reasoned about yet. + * the always-satisfied controls from a `qco.ctrl` - rebuilding it with the + * rest, or inlining its body when every control was redundant. Analyze and + * rewrite alternate until a fixpoint because a removed gate can change a later + * gate's control facts. Classical controls are not reasoned about yet. */ struct ConstantPropagation final : impl::ConstantPropagationBase { diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 744979ab84..4b5ec63710 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -385,7 +385,8 @@ TEST_F(QuantumStateTest, resetSuperpositionForcesTargetToZero) { auto qs = QuantumState({q[0], q[1]}, 4); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); ASSERT_TRUE( - qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}).succeeded()); + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}) + .succeeded()); const auto result = qs.reset(q[0], q[0]); ASSERT_TRUE(succeeded(result)); const auto& outcomes = *result; @@ -453,7 +454,8 @@ TEST_F(QuantumStateTest, hasAlwaysZeroAmplitude) { auto qs = QuantumState({q[0], q[1]}, 4); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); ASSERT_TRUE( - qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}).succeeded()); + qs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {q[0]}) + .succeeded()); EXPECT_TRUE(qs.hasAlwaysZeroAmplitude({{q[0], false}, {q[1], true}})); EXPECT_FALSE(qs.hasAlwaysZeroAmplitude({{q[0], true}, {q[1], true}})); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 9f108026f7..48d0c5f45c 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -181,8 +181,7 @@ TEST_F(UnionTableTest, classicalControlSkipsGate) { TEST_F(UnionTableTest, unresolvedClassicalControlFails) { auto ut = make(); ut.seedQubit(q[0]); - const Value c = - builder.boolConstant(false); + const Value c = builder.boolConstant(false); EXPECT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {}, {}, {c}) .failed()); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index b6262e52d1..3d66adcf39 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -175,7 +175,8 @@ TEST_F(ConstantPropagationTest, stripsAllButOneControl) { EXPECT_TRUE(ctrl.getInputControl(0) == sup); } -TEST_F(ConstantPropagationTest, unwrapsMultiControlGateWhenAllControlsRedundant) { +TEST_F(ConstantPropagationTest, + unwrapsMultiControlGateWhenAllControlsRedundant) { auto reg = builder.allocQubitRegister(3); const Value a = builder.x(reg[0]); const Value b = builder.x(reg[1]); From 7f60545230b401a6899564e9b3ca9259981da823 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 09:45:46 +0200 Subject: [PATCH 29/55] :memo: Updated CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aebbffbea1..a9976b2288 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ releases may include breaking changes. #### Passes and transformations +- Add pass tp remove controlling qubits via constant propagation ([#2280]) ([**@lirem101**]) - ✨ Add passes for quantum-specific interprocedural optimizations ([#2193]) ([**@DRovara**], [**@burgholzer**]) - ✨ Add Pauli twirling, quantum loop unrolling, and qubit reuse passes @@ -859,6 +860,7 @@ for previous changelogs._ +[#2280]: https://github.com/munich-quantum-toolkit/core/pull/2280 [#2278]: https://github.com/munich-quantum-toolkit/core/pull/2278 [#2270]: https://github.com/munich-quantum-toolkit/core/pull/2270 [#2262]: https://github.com/munich-quantum-toolkit/core/pull/2262 From 368ade5b59377b865c73973dbb42778e62f1d5ae Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 07:50:45 +0000 Subject: [PATCH 30/55] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9976b2288..2c61339a34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,7 +56,8 @@ releases may include breaking changes. #### Passes and transformations -- Add pass tp remove controlling qubits via constant propagation ([#2280]) ([**@lirem101**]) +- Add pass tp remove controlling qubits via constant propagation ([#2280]) + ([**@lirem101**]) - ✨ Add passes for quantum-specific interprocedural optimizations ([#2193]) ([**@DRovara**], [**@burgholzer**]) - ✨ Add Pauli twirling, quantum loop unrolling, and qubit reuse passes From 851c9fb81096b4cd94065ff0c9c1e6a2723c91f0 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 10:14:13 +0200 Subject: [PATCH 31/55] :rotating_light: Made mlir values non-const, assisted-by Sonnet 5 via Claude Code --- .../ConstantPropagationAnalysis.cpp | 4 +- .../ConstantPropagation/HybridState.cpp | 59 ++++++++------- .../ConstantPropagation/HybridState.hpp | 2 +- .../ConstantPropagation/QuantumState.cpp | 33 +++++---- .../ConstantPropagation/QuantumState.hpp | 2 +- .../ConstantPropagation/UnionTable.cpp | 71 +++++++++---------- .../test_constantPropagationAnalysis.cpp | 8 +-- .../ConstantPropagation/test_hybridState.cpp | 14 ++-- .../ConstantPropagation/test_quantumState.cpp | 2 +- .../ConstantPropagation/test_unionTable.cpp | 20 +++--- .../test_qco_constant_propagation.cpp | 28 ++++---- 11 files changed, 118 insertions(+), 125 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp index 9c3f10defe..275c309e3c 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp @@ -49,7 +49,7 @@ template SmallVector toVec(Range&& range) { /// @brief Whether v is a qubit argument of the entry-point function's entry /// block (so its initial state is |0>, per the pass contract). Arguments of any /// other function have unknown provenance. -bool isEntryPointQubitArgument(const Value v) { +bool isEntryPointQubitArgument(Value v) { const auto arg = dyn_cast(v); if (!arg || !isa(arg.getType())) { return false; @@ -63,7 +63,7 @@ bool isEntryPointQubitArgument(const Value v) { /// entry-point argument starts in |0>, anything else of unknown provenance /// collapses to top. void ensureSeeded(UnionTable& table, Operation* const op) { - for (const Value operand : op->getOperands()) { + for (Value operand : op->getOperands()) { if (!isa(operand.getType()) || table.isTracked(operand)) { continue; } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 78ce99299f..f936c89d3e 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -71,7 +71,7 @@ std::optional classicalDouble(const Attribute attr) { // Observers //===----------------------------------------------------------------------===// -std::optional HybridState::getClassical(const Value v) const { +std::optional HybridState::getClassical(Value v) const { const auto it = classical.find(v); if (it == classical.end()) { return std::nullopt; @@ -83,11 +83,11 @@ std::optional HybridState::getClassical(const Value v) const { // Mutation //===----------------------------------------------------------------------===// -void HybridState::setClassical(const Value v, const Attribute attr) { +void HybridState::setClassical(Value v, const Attribute attr) { classical[v] = attr; } -void HybridState::forwardValue(const Value from, const Value to) { +void HybridState::forwardValue(Value from, Value to) { state.forwardQubit(from, to); const auto it = classical.find(from); if (it != classical.end()) { @@ -107,7 +107,7 @@ void HybridState::intersectClassical(const HybridState& other) { disagreeing.push_back(v); } } - for (const Value v : disagreeing) { + for (Value v : disagreeing) { classical.erase(v); } } @@ -130,7 +130,7 @@ HybridState HybridState::tensor(const HybridState& other) const { FailureOr HybridState::classicalControlsHold(const ArrayRef pos, const ArrayRef neg) const { - for (const Value p : pos) { + for (Value p : pos) { const auto attr = getClassical(p); if (!attr) { return failure(); @@ -143,7 +143,7 @@ HybridState::classicalControlsHold(const ArrayRef pos, return false; } } - for (const Value n : neg) { + for (Value n : neg) { const auto attr = getClassical(n); if (!attr) { return failure(); @@ -163,11 +163,12 @@ HybridState::classicalControlsHold(const ArrayRef pos, // Gate application //===----------------------------------------------------------------------===// -LogicalResult HybridState::applyMatrix1Q( - const Value in, const Value out, const Matrix2x2& matrix, - const ArrayRef quantumCtrlsIn, const ArrayRef quantumCtrlsOut, - const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) { +LogicalResult +HybridState::applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, + const ArrayRef quantumCtrlsIn, + const ArrayRef quantumCtrlsOut, + const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) { if (!ctrlRenameOk(quantumCtrlsIn, quantumCtrlsOut) || !state.contains(in)) { return failure(); } @@ -185,13 +186,11 @@ LogicalResult HybridState::applyMatrix1Q( return success(); } -LogicalResult -HybridState::applyMatrix2Q(const Value in0, const Value in1, const Value out0, - const Value out1, const Matrix4x4& matrix, - const ArrayRef quantumCtrlsIn, - const ArrayRef quantumCtrlsOut, - const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) { +LogicalResult HybridState::applyMatrix2Q( + Value in0, Value in1, Value out0, Value out1, const Matrix4x4& matrix, + const ArrayRef quantumCtrlsIn, const ArrayRef quantumCtrlsOut, + const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) { if (!ctrlRenameOk(quantumCtrlsIn, quantumCtrlsOut) || !state.contains(in0) || !state.contains(in1)) { return failure(); @@ -211,8 +210,7 @@ HybridState::applyMatrix2Q(const Value in0, const Value in1, const Value out0, } LogicalResult -HybridState::addGlobalPhase(const Value theta, - const ArrayRef quantumCtrlsIn, +HybridState::addGlobalPhase(Value theta, const ArrayRef quantumCtrlsIn, const ArrayRef quantumCtrlsOut, const ArrayRef posClassicalCtrls, const ArrayRef negClassicalCtrls) { @@ -242,7 +240,7 @@ HybridState::addGlobalPhase(const Value theta, void HybridState::propagateClassical(Operation* const op) { SmallVector operands; operands.reserve(op->getNumOperands()); - for (const Value operand : op->getOperands()) { + for (Value operand : op->getOperands()) { operands.push_back(classical.lookup(operand)); } SmallVector folded; @@ -262,8 +260,7 @@ void HybridState::propagateClassical(Operation* const op) { //===----------------------------------------------------------------------===// LogicalResult -HybridState::measureQubit(const Value in, const Value out, - const Value classicalResult, +HybridState::measureQubit(Value in, Value out, Value classicalResult, const ArrayRef posClassicalCtrls, const ArrayRef negClassicalCtrls) { if (!state.contains(in)) { @@ -297,7 +294,7 @@ HybridState::measureQubit(const Value in, const Value out, return success(); } -LogicalResult HybridState::resetQubit(const Value in, const Value out, +LogicalResult HybridState::resetQubit(Value in, Value out, const ArrayRef posClassicalCtrls, const ArrayRef negClassicalCtrls) { if (!state.contains(in)) { @@ -332,20 +329,20 @@ LogicalResult HybridState::resetQubit(const Value in, const Value out, // Queries //===----------------------------------------------------------------------===// -bool HybridState::isQubitAlwaysZero(const Value q) const { +bool HybridState::isQubitAlwaysZero(Value q) const { return state.isAlwaysZero(q); } -bool HybridState::isQubitAlwaysOne(const Value q) const { +bool HybridState::isQubitAlwaysOne(Value q) const { return state.isAlwaysOne(q); } -bool HybridState::isClassicalTrue(const Value v) const { +bool HybridState::isClassicalTrue(Value v) const { const auto attr = getClassical(v); return attr && classicalTruth(*attr).value_or(false); } -bool HybridState::isClassicalFalse(const Value v) const { +bool HybridState::isClassicalFalse(Value v) const { const auto attr = getClassical(v); if (!attr) { return false; @@ -357,12 +354,12 @@ bool HybridState::isClassicalFalse(const Value v) const { bool HybridState::areControlsSatisfiable( const ArrayRef quantumCtrls, const ArrayRef posClassicalCtrls, const ArrayRef negClassicalCtrls) const { - for (const Value pc : posClassicalCtrls) { + for (Value pc : posClassicalCtrls) { if (isClassicalFalse(pc)) { return false; } } - for (const Value nc : negClassicalCtrls) { + for (Value nc : negClassicalCtrls) { if (isClassicalTrue(nc)) { return false; } @@ -371,7 +368,7 @@ bool HybridState::areControlsSatisfiable( return true; } SmallVector> assignment; - for (const Value qc : quantumCtrls) { + for (Value qc : quantumCtrls) { if (!state.contains(qc)) { return false; } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index ef2d0d22f6..eaf3767c56 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -91,7 +91,7 @@ class HybridState { return state.getQubits(); } [[nodiscard("HybridState::hasQubit called but ignored")]] bool - hasQubit(const Value q) const { + hasQubit(Value q) const { return state.contains(q); } [[nodiscard("HybridState::getClassical called but ignored")]] diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index f1b0639973..81d3c932cd 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -48,12 +48,12 @@ QuantumState::QuantumState(const ArrayRef qubits, amplitudes[0] = Complex{1.0, 0.0}; } -QuantumState QuantumState::singletonZero(const Value qubit, +QuantumState QuantumState::singletonZero(Value qubit, const size_t maxNonzeroAmplitudes) { return {ArrayRef(qubit), maxNonzeroAmplitudes}; } -std::optional QuantumState::indexOf(const Value q) const { +std::optional QuantumState::indexOf(Value q) const { for (const auto [idx, qubit] : llvm::enumerate(qubits)) { if (qubit == q) { return static_cast(idx); @@ -64,7 +64,7 @@ std::optional QuantumState::indexOf(const Value q) const { uint64_t QuantumState::maskOf(const ArrayRef values) const { uint64_t mask = 0; - for (const Value v : values) { + for (Value v : values) { if (const auto idx = indexOf(v)) { mask |= uint64_t{1} << *idx; } @@ -77,7 +77,7 @@ void QuantumState::markTop() { amplitudes.clear(); } -void QuantumState::forwardQubit(const Value from, const Value to) { +void QuantumState::forwardQubit(Value from, Value to) { if (const auto idx = indexOf(from)) { qubits[*idx] = to; } @@ -108,7 +108,7 @@ void QuantumState::canonicalize() { } } -LogicalResult QuantumState::applyMatrix1Q(const Value in, const Value out, +LogicalResult QuantumState::applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, const ArrayRef ctrlsIn, const ArrayRef ctrlsOut) { @@ -116,7 +116,7 @@ LogicalResult QuantumState::applyMatrix1Q(const Value in, const Value out, if (!idx || ctrlsOut.size() != ctrlsIn.size()) { return failure(); } - for (const Value c : ctrlsIn) { + for (Value c : ctrlsIn) { if (!contains(c)) { return failure(); } @@ -151,9 +151,8 @@ LogicalResult QuantumState::applyMatrix1Q(const Value in, const Value out, return success(); } -LogicalResult QuantumState::applyMatrix2Q(const Value in0, const Value in1, - const Value out0, const Value out1, - const Matrix4x4& matrix, +LogicalResult QuantumState::applyMatrix2Q(Value in0, Value in1, Value out0, + Value out1, const Matrix4x4& matrix, const ArrayRef ctrlsIn, const ArrayRef ctrlsOut) { const auto idx0 = indexOf(in0); @@ -161,7 +160,7 @@ LogicalResult QuantumState::applyMatrix2Q(const Value in0, const Value in1, if (!idx0 || !idx1 || *idx0 == *idx1 || ctrlsOut.size() != ctrlsIn.size()) { return failure(); } - for (const Value c : ctrlsIn) { + for (Value c : ctrlsIn) { if (!contains(c)) { return failure(); } @@ -218,7 +217,7 @@ QuantumState::applyControlledPhase(const double phase, (!ctrlsOut.empty() && ctrlsOut.size() != ctrlsIn.size())) { return failure(); } - for (const Value c : ctrlsIn) { + for (Value c : ctrlsIn) { if (!contains(c)) { return failure(); } @@ -239,8 +238,8 @@ QuantumState::applyControlledPhase(const double phase, return success(); } -FailureOr> -QuantumState::measure(const Value in, const Value out) { +FailureOr> QuantumState::measure(Value in, + Value out) { const auto idx = indexOf(in); if (!idx) { return failure(); @@ -290,8 +289,8 @@ QuantumState::measure(const Value in, const Value out) { return outcomes; } -FailureOr> -QuantumState::reset(const Value in, const Value out) { +FailureOr> QuantumState::reset(Value in, + Value out) { auto outcomes = measure(in, out); if (failed(outcomes)) { return failure(); @@ -336,7 +335,7 @@ QuantumState QuantumState::unify(const QuantumState& that) const { return result; } -bool QuantumState::isAlwaysZero(const Value q) const { +bool QuantumState::isAlwaysZero(Value q) const { const auto idx = indexOf(q); if (top || !idx || amplitudes.empty()) { return false; @@ -346,7 +345,7 @@ bool QuantumState::isAlwaysZero(const Value q) const { }); } -bool QuantumState::isAlwaysOne(const Value q) const { +bool QuantumState::isAlwaysOne(Value q) const { const auto idx = indexOf(q); if (top || !idx || amplitudes.empty()) { return false; diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index ea0bdc8de1..e173e3d3d8 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -88,7 +88,7 @@ class QuantumState { /// @brief Whether QuantumState contains the qubit. [[nodiscard("QuantumState::contains called but ignored")]] bool - contains(const Value q) const { + contains(Value q) const { return indexOf(q).has_value(); } /// @brief The bit position of a qubit, if present. diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index e4fa05d726..51be394be5 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -36,7 +36,7 @@ namespace { std::vector qubitKey(const UnionTable::Slot& slot) { std::vector key; key.reserve(slot.front().getQubits().size()); - for (const Value q : slot.front().getQubits()) { + for (Value q : slot.front().getQubits()) { key.push_back(q.getAsOpaquePointer()); } llvm::sort(key); @@ -48,7 +48,7 @@ std::vector qubitKey(const UnionTable::Slot& slot) { // Partition helpers //===----------------------------------------------------------------------===// -std::optional UnionTable::slotIndexContaining(const Value v) const { +std::optional UnionTable::slotIndexContaining(Value v) const { for (const auto& [i, slot] : llvm::enumerate(slots)) { if (slot.front().hasQubit(v)) { return static_cast(i); @@ -65,7 +65,7 @@ std::optional UnionTable::slotIndexContaining(const Value v) const { SmallVector UnionTable::slotsTouchedBy(const ArrayRef values) const { SmallVector result; - for (const Value v : values) { + for (Value v : values) { if (const auto i = slotIndexContaining(v)) { if (!llvm::is_contained(result, *i)) { result.push_back(*i); @@ -196,7 +196,7 @@ bool UnionTable::sameSlot(const Slot& a, const Slot& b) { // Seeding //===----------------------------------------------------------------------===// -void UnionTable::seedQubit(const Value qubit) { +void UnionTable::seedQubit(Value qubit) { if (allTop || isTracked(qubit)) { return; } @@ -206,7 +206,7 @@ void UnionTable::seedQubit(const Value qubit) { slots.push_back(std::move(slot)); } -void UnionTable::seedClassical(const Value value, const Attribute attr) { +void UnionTable::seedClassical(Value value, const Attribute attr) { if (allTop) { return; } @@ -223,7 +223,7 @@ void UnionTable::seedClassical(const Value value, const Attribute attr) { slots.push_back(std::move(slot)); } -bool UnionTable::isTracked(const Value v) const { +bool UnionTable::isTracked(Value v) const { return slotIndexContaining(v).has_value(); } @@ -231,7 +231,7 @@ bool UnionTable::isTracked(const Value v) const { // SSA forwarding //===----------------------------------------------------------------------===// -void UnionTable::forwardValue(const Value from, const Value to) { +void UnionTable::forwardValue(Value from, Value to) { for (auto& slot : slots) { for (auto& hs : slot) { hs.forwardValue(from, to); @@ -250,11 +250,12 @@ void UnionTable::forwardValues(const ArrayRef from, // Operation propagation //===----------------------------------------------------------------------===// -LogicalResult UnionTable::applyMatrix1Q( - const Value in, const Value out, const Matrix2x2& matrix, - const ArrayRef quantumCtrlsIn, const ArrayRef quantumCtrlsOut, - const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) { +LogicalResult +UnionTable::applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, + const ArrayRef quantumCtrlsIn, + const ArrayRef quantumCtrlsOut, + const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) { if (allTop) { return success(); } @@ -281,13 +282,11 @@ LogicalResult UnionTable::applyMatrix1Q( return success(); } -LogicalResult -UnionTable::applyMatrix2Q(const Value in0, const Value in1, const Value out0, - const Value out1, const Matrix4x4& matrix, - const ArrayRef quantumCtrlsIn, - const ArrayRef quantumCtrlsOut, - const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) { +LogicalResult UnionTable::applyMatrix2Q( + Value in0, Value in1, Value out0, Value out1, const Matrix4x4& matrix, + const ArrayRef quantumCtrlsIn, const ArrayRef quantumCtrlsOut, + const ArrayRef posClassicalCtrls, + const ArrayRef negClassicalCtrls) { if (allTop) { return success(); } @@ -315,8 +314,7 @@ UnionTable::applyMatrix2Q(const Value in0, const Value in1, const Value out0, } LogicalResult -UnionTable::addGlobalPhase(const Value theta, - const ArrayRef quantumCtrlsIn, +UnionTable::addGlobalPhase(Value theta, const ArrayRef quantumCtrlsIn, const ArrayRef quantumCtrlsOut, const ArrayRef posClassicalCtrls, const ArrayRef negClassicalCtrls) { @@ -369,7 +367,7 @@ void UnionTable::propagateClassical(Operation* const op) { if (allTop) { return; } - for (const Value operand : operands) { + for (Value operand : operands) { if (const auto slot = slotIndexContaining(operand)) { for (auto& hs : slots[*slot]) { hs.propagateClassical(op); @@ -380,8 +378,7 @@ void UnionTable::propagateClassical(Operation* const op) { } LogicalResult -UnionTable::measureQubit(const Value in, const Value out, - const Value classicalResult, +UnionTable::measureQubit(Value in, Value out, Value classicalResult, const ArrayRef posClassicalCtrls, const ArrayRef negClassicalCtrls) { if (allTop) { @@ -409,7 +406,7 @@ UnionTable::measureQubit(const Value in, const Value out, return success(); } -LogicalResult UnionTable::resetQubit(const Value in, const Value out, +LogicalResult UnionTable::resetQubit(Value in, Value out, const ArrayRef posClassicalCtrls, const ArrayRef negClassicalCtrls) { if (allTop) { @@ -440,7 +437,7 @@ void UnionTable::markQubitsTop(const ArrayRef qubits) { return; } llvm::DenseSet done; - for (const Value q : qubits) { + for (Value q : qubits) { const auto i = slotIndexContaining(q); if (i && done.insert(*i).second) { for (auto& hs : slots[*i]) { @@ -454,7 +451,7 @@ void UnionTable::markQubitsTop(const ArrayRef qubits) { // Queries //===----------------------------------------------------------------------===// -bool UnionTable::isQubitAlwaysOne(const Value q) const { +bool UnionTable::isQubitAlwaysOne(Value q) const { if (allTop) { return false; } @@ -464,7 +461,7 @@ bool UnionTable::isQubitAlwaysOne(const Value q) const { }); } -bool UnionTable::isQubitAlwaysZero(const Value q) const { +bool UnionTable::isQubitAlwaysZero(Value q) const { if (allTop) { return false; } @@ -474,7 +471,7 @@ bool UnionTable::isQubitAlwaysZero(const Value q) const { }); } -bool UnionTable::isClassicalAlwaysTrue(const Value v) const { +bool UnionTable::isClassicalAlwaysTrue(Value v) const { if (allTop) { return false; } @@ -484,7 +481,7 @@ bool UnionTable::isClassicalAlwaysTrue(const Value v) const { }); } -bool UnionTable::isClassicalAlwaysFalse(const Value v) const { +bool UnionTable::isClassicalAlwaysFalse(Value v) const { if (allTop) { return false; } @@ -507,26 +504,26 @@ bool UnionTable::areControlsSatisfiable( for (const unsigned si : slotsTouchedBy(all)) { const Slot& slot = slots[si]; - const auto hasClassical = [&](const Value c) { + const auto hasClassical = [&](Value c) { return llvm::any_of(slot, [&](const HybridState& hs) { return hs.getClassical(c).has_value(); }); }; SmallVector quantum; - for (const Value c : quantumCtrls) { + for (Value c : quantumCtrls) { if (slot.front().hasQubit(c)) { quantum.push_back(c); } } SmallVector pos; - for (const Value c : posClassicalCtrls) { + for (Value c : posClassicalCtrls) { if (hasClassical(c)) { pos.push_back(c); } } SmallVector neg; - for (const Value c : negClassicalCtrls) { + for (Value c : negClassicalCtrls) { if (hasClassical(c)) { neg.push_back(c); } @@ -553,17 +550,17 @@ SuperfluousResult UnionTable::getSuperfluousControls( result.completelySuperfluous = true; return result; } - for (const Value q : quantumCtrls) { + for (Value q : quantumCtrls) { if (isQubitAlwaysOne(q)) { result.superfluousQubits.insert(q); } } - for (const Value p : posClassicalCtrls) { + for (Value p : posClassicalCtrls) { if (isClassicalAlwaysTrue(p)) { result.superfluousClassicalValues.insert(p); } } - for (const Value n : negClassicalCtrls) { + for (Value n : negClassicalCtrls) { if (isClassicalAlwaysFalse(n)) { result.superfluousClassicalValues.insert(n); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp index d31f1c37a6..1af124a445 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp @@ -142,7 +142,7 @@ TEST_F(ConstantPropagationAnalysisTest, independentGatesStayFactored) { TEST_F(ConstantPropagationAnalysisTest, entanglingGateMergedFactors) { auto reg = builder.allocQubitRegister(2); - const Value q0 = builder.x(reg[0]); + Value q0 = builder.x(reg[0]); builder.dcx(q0, reg[1]); const auto module = builder.finalize(); @@ -154,7 +154,7 @@ TEST_F(ConstantPropagationAnalysisTest, entanglingGateMergedFactors) { TEST_F(ConstantPropagationAnalysisTest, controlledGateFires) { auto reg = builder.allocQubitRegister(2); - const Value q0 = builder.h(reg[0]); + Value q0 = builder.h(reg[0]); builder.cx(q0, reg[1]); const auto module = builder.finalize(); @@ -166,7 +166,7 @@ TEST_F(ConstantPropagationAnalysisTest, controlledGateFires) { TEST_F(ConstantPropagationAnalysisTest, measuringSuperpositionTops) { auto reg = builder.allocQubitRegister(1); - const Value q0 = builder.h(reg[0]); + Value q0 = builder.h(reg[0]); builder.measure(q0); const auto module = builder.finalize(); @@ -182,7 +182,7 @@ TEST_F(ConstantPropagationAnalysisTest, measuringSuperpositionTops) { TEST_F(ConstantPropagationAnalysisTest, deterministicMeasurementRecordsBit) { auto reg = builder.allocQubitRegister(1); - const Value q0 = builder.x(reg[0]); + Value q0 = builder.x(reg[0]); builder.measure(q0); const auto module = builder.finalize(); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp index d39d4c2c31..39627662b9 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -208,7 +208,7 @@ TEST_F(HybridStateTest, unresolvedClassicalControlFails) { TEST_F(HybridStateTest, floatClassicalControlIsSupported) { auto hs = make({q[0]}); - const Value fc = builder.floatConstant(2.5); + Value fc = builder.floatConstant(2.5); hs.setClassical(fc, builder.getF64FloatAttr(2.5)); EXPECT_TRUE(hs.isClassicalTrue(fc)); @@ -226,7 +226,7 @@ TEST_F(HybridStateTest, floatClassicalControlIsSupported) { TEST_F(HybridStateTest, uncontrolledGlobalPhaseAccumulates) { auto hs = make({q[0]}); - const Value theta = builder.floatConstant(std::acos(-1.0)); + Value theta = builder.floatConstant(std::acos(-1.0)); hs.setClassical(theta, builder.getF64FloatAttr(std::acos(-1.0))); ASSERT_TRUE(hs.addGlobalPhase(theta).succeeded()); EXPECT_LT(std::abs(hs.getGlobalPhase() - Complex{-1.0, 0.0}), 1e-9); @@ -234,7 +234,7 @@ TEST_F(HybridStateTest, uncontrolledGlobalPhaseAccumulates) { TEST_F(HybridStateTest, quantumControlledPhaseIsNotGlobal) { auto hs = make({q[0], q[1]}); - const Value theta = builder.floatConstant(std::acos(-1.0)); + Value theta = builder.floatConstant(std::acos(-1.0)); hs.setClassical(theta, builder.getF64FloatAttr(std::acos(-1.0))); ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); ASSERT_TRUE(hs.addGlobalPhase(theta, {q[0]}).succeeded()); @@ -243,7 +243,7 @@ TEST_F(HybridStateTest, quantumControlledPhaseIsNotGlobal) { TEST_F(HybridStateTest, globalPhaseSkippedByClassicalControl) { auto hs = make({q[0]}); - const Value theta = builder.floatConstant(std::acos(-1.0)); + Value theta = builder.floatConstant(std::acos(-1.0)); hs.setClassical(theta, builder.getF64FloatAttr(std::acos(-1.0))); hs.setClassical(cA, builder.getBoolAttr(false)); ASSERT_TRUE(hs.addGlobalPhase(theta, {}, {}, {cA}).succeeded()); @@ -252,14 +252,14 @@ TEST_F(HybridStateTest, globalPhaseSkippedByClassicalControl) { TEST_F(HybridStateTest, globalPhaseFailsWhenThetaUnresolved) { auto hs = make({q[0]}); - const Value theta = builder.floatConstant(std::acos(-1.0)); // never seeded + Value theta = builder.floatConstant(std::acos(-1.0)); // never seeded EXPECT_TRUE(hs.addGlobalPhase(theta).failed()); } TEST_F(HybridStateTest, propagateClassicalFoldsConstants) { auto hs = make({}); - const Value lhs = builder.intConstant(3); - const Value rhs = builder.intConstant(4); + Value lhs = builder.intConstant(3); + Value rhs = builder.intConstant(4); hs.setClassical(lhs, builder.getIntegerAttr(lhs.getType(), 3)); hs.setClassical(rhs, builder.getIntegerAttr(rhs.getType(), 4)); auto add = arith::AddIOp::create(builder, builder.getLoc(), lhs, rhs); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 4b5ec63710..0183949f03 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -188,7 +188,7 @@ TEST_F(QuantumStateTest, applyToQubitNotInGroupFailsEvenWhenTop) { ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); ASSERT_TRUE(qs.isTop()); EXPECT_TRUE(qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); - const Value stranger = builder.allocQubit(); + Value stranger = builder.allocQubit(); EXPECT_TRUE( qs.applyMatrix1Q(stranger, stranger, xOp.getUnitaryMatrix()).failed()); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 48d0c5f45c..4f9943cb5f 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -97,7 +97,7 @@ TEST_F(UnionTableTest, seedQubitCantBeCalledTwice) { TEST_F(UnionTableTest, seedClassicalRecordsConstant) { auto ut = make(); - const Value c = builder.boolConstant(true); + Value c = builder.boolConstant(true); ut.seedClassical(c, builder.getBoolAttr(true)); EXPECT_TRUE(ut.isTracked(c)); EXPECT_TRUE(ut.isClassicalAlwaysTrue(c)); @@ -171,7 +171,7 @@ TEST_F(UnionTableTest, applyToUnseededQubitFails) { TEST_F(UnionTableTest, classicalControlSkipsGate) { auto ut = make(); ut.seedQubit(q[0]); - const Value c = builder.boolConstant(false); + Value c = builder.boolConstant(false); ut.seedClassical(c, builder.getBoolAttr(false)); ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {}, {}, {c}) .succeeded()); @@ -181,7 +181,7 @@ TEST_F(UnionTableTest, classicalControlSkipsGate) { TEST_F(UnionTableTest, unresolvedClassicalControlFails) { auto ut = make(); ut.seedQubit(q[0]); - const Value c = builder.boolConstant(false); + Value c = builder.boolConstant(false); EXPECT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {}, {}, {c}) .failed()); } @@ -193,7 +193,7 @@ TEST_F(UnionTableTest, unresolvedClassicalControlFails) { TEST_F(UnionTableTest, measureDeterministicRecordsBit) { auto ut = make(); ut.seedQubit(q[0]); - const Value result = builder.boolConstant(false); + Value result = builder.boolConstant(false); ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); ASSERT_TRUE(ut.measureQubit(q[0], q[0], result).succeeded()); EXPECT_TRUE(ut.isClassicalAlwaysTrue(result)); @@ -202,7 +202,7 @@ TEST_F(UnionTableTest, measureDeterministicRecordsBit) { TEST_F(UnionTableTest, measureSuperpositionTopsTheState) { auto ut = make(); ut.seedQubit(q[0]); - const Value result = builder.boolConstant(false); + Value result = builder.boolConstant(false); ASSERT_TRUE(ut.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); ASSERT_TRUE(ut.measureQubit(q[0], q[0], result).succeeded()); EXPECT_TRUE(ut.areStatesAllTop()); @@ -224,7 +224,7 @@ TEST_F(UnionTableTest, resetForcesZero) { TEST_F(UnionTableTest, globalPhaseIsRecordedOnce) { auto ut = make(); ut.seedQubit(q[0]); - const Value theta = builder.floatConstant(std::numbers::pi); + Value theta = builder.floatConstant(std::numbers::pi); ut.seedClassical(theta, builder.getF64FloatAttr(std::numbers::pi)); ASSERT_TRUE(ut.addGlobalPhase(theta).succeeded()); EXPECT_NE(printed(ut).find("phase="), std::string::npos); @@ -232,8 +232,8 @@ TEST_F(UnionTableTest, globalPhaseIsRecordedOnce) { TEST_F(UnionTableTest, propagateClassicalFoldsAcrossSlots) { auto ut = make(); - const Value lhs = builder.intConstant(2); - const Value rhs = builder.intConstant(5); + Value lhs = builder.intConstant(2); + Value rhs = builder.intConstant(5); ut.seedClassical(lhs, builder.getIntegerAttr(lhs.getType(), 2)); ut.seedClassical(rhs, builder.getIntegerAttr(rhs.getType(), 5)); auto add = arith::AddIOp::create(builder, builder.getLoc(), lhs, rhs); @@ -265,7 +265,7 @@ TEST_F(UnionTableTest, controlsUnsatisfiableWhenAQubitIsAlwaysZero) { TEST_F(UnionTableTest, negativeClassicalControlSatisfiedByFalseConstant) { auto ut = make(); - const Value c = builder.boolConstant(false); + Value c = builder.boolConstant(false); ut.seedClassical(c, builder.getBoolAttr(false)); EXPECT_FALSE(ut.areControlsSatisfiable({}, {c})); EXPECT_TRUE(ut.areControlsSatisfiable({}, {}, {c})); @@ -354,7 +354,7 @@ TEST_F(UnionTableTest, joinOfDifferentEntanglementStructureTops) { } TEST_F(UnionTableTest, joinKeepsClassicalFactOnlyWhenShared) { - const Value c = builder.boolConstant(true); + Value c = builder.boolConstant(true); auto a = make(); a.seedClassical(c, builder.getBoolAttr(true)); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index 3d66adcf39..6226f4cda5 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -89,8 +89,8 @@ TEST_F(ConstantPropagationTest, dropsGateWithUnsatisfiableControl) { TEST_F(ConstantPropagationTest, stripsAlwaysSatisfiedControl) { auto reg = builder.allocQubitRegister(3); - const Value one = builder.x(reg[0]); - const Value sup = builder.h(reg[2]); + Value one = builder.x(reg[0]); + Value sup = builder.h(reg[2]); const SmallVector controls{one, sup}; builder.mcx(controls, reg[1]); const auto module = builder.finalize(); @@ -104,7 +104,7 @@ TEST_F(ConstantPropagationTest, stripsAlwaysSatisfiedControl) { TEST_F(ConstantPropagationTest, unwrapsGateWhenEveryControlRedundant) { auto reg = builder.allocQubitRegister(2); - const Value one = builder.x(reg[0]); + Value one = builder.x(reg[0]); builder.cx(one, reg[1]); const auto module = builder.finalize(); @@ -117,7 +117,7 @@ TEST_F(ConstantPropagationTest, unwrapsGateWhenEveryControlRedundant) { TEST_F(ConstantPropagationTest, leavesGateAloneWhenStateIsImprecise) { auto reg = builder.allocQubitRegister(2); - const Value sup = builder.h(reg[0]); + Value sup = builder.h(reg[0]); builder.cx(sup, reg[1]); const auto module = builder.finalize(); @@ -128,7 +128,7 @@ TEST_F(ConstantPropagationTest, leavesGateAloneWhenStateIsImprecise) { TEST_F(ConstantPropagationTest, dropsGateWhenOneOfSeveralControlsIsAlwaysZero) { auto reg = builder.allocQubitRegister(3); - const Value one = builder.x(reg[0]); + Value one = builder.x(reg[0]); const SmallVector controls{one, reg[1]}; builder.mcx(controls, reg[2]); const auto module = builder.finalize(); @@ -141,8 +141,8 @@ TEST_F(ConstantPropagationTest, dropsGateWhenOneOfSeveralControlsIsAlwaysZero) { TEST_F(ConstantPropagationTest, stripsTrailingAlwaysOneControl) { auto reg = builder.allocQubitRegister(3); - const Value sup = builder.h(reg[0]); - const Value one = builder.x(reg[1]); + Value sup = builder.h(reg[0]); + Value one = builder.x(reg[1]); const SmallVector controls{sup, one}; builder.mcx(controls, reg[2]); const auto module = builder.finalize(); @@ -159,9 +159,9 @@ TEST_F(ConstantPropagationTest, stripsTrailingAlwaysOneControl) { TEST_F(ConstantPropagationTest, stripsAllButOneControl) { auto reg = builder.allocQubitRegister(4); - const Value a = builder.x(reg[0]); - const Value b = builder.x(reg[1]); - const Value sup = builder.h(reg[3]); + Value a = builder.x(reg[0]); + Value b = builder.x(reg[1]); + Value sup = builder.h(reg[3]); const SmallVector controls{a, b, sup}; builder.mcx(controls, reg[2]); const auto module = builder.finalize(); @@ -178,8 +178,8 @@ TEST_F(ConstantPropagationTest, stripsAllButOneControl) { TEST_F(ConstantPropagationTest, unwrapsMultiControlGateWhenAllControlsRedundant) { auto reg = builder.allocQubitRegister(3); - const Value a = builder.x(reg[0]); - const Value b = builder.x(reg[1]); + Value a = builder.x(reg[0]); + Value b = builder.x(reg[1]); const SmallVector controls{a, b}; builder.mcx(controls, reg[2]); const auto module = builder.finalize(); @@ -193,8 +193,8 @@ TEST_F(ConstantPropagationTest, TEST_F(ConstantPropagationTest, simplifiesChainOfControlledGates) { auto reg = builder.allocQubitRegister(3); - const Value q0 = builder.x(reg[0]); - const Value q1 = builder.cx(q0, reg[1]).second; + Value q0 = builder.x(reg[0]); + Value q1 = builder.cx(q0, reg[1]).second; builder.cx(q1, reg[2]); const auto module = builder.finalize(); From 6df5ef9c77401d4bbf35624f784ed18da0839528 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 18:43:00 +0200 Subject: [PATCH 32/55] :rotating_light: Solved linter issues, assisted-by Sonnet 5 via Claude Code --- .../Optimizations/ConstantPropagation.cpp | 1 + .../ConstantPropagationAnalysis.cpp | 12 ++++------ .../ConstantPropagationAnalysis.hpp | 10 +++++++-- .../ConstantPropagation/HybridState.cpp | 22 +++++++------------ .../ConstantPropagation/QuantumState.cpp | 2 +- .../ConstantPropagation/Rewriter.cpp | 8 ++----- .../ConstantPropagation/UnionTable.cpp | 4 +--- .../test_constantPropagationAnalysis.cpp | 8 +++---- .../ConstantPropagation/test_hybridState.cpp | 10 ++++----- .../ConstantPropagation/test_quantumState.cpp | 6 ++--- .../ConstantPropagation/test_unionTable.cpp | 6 ++--- .../test_qco_constant_propagation.cpp | 8 +++---- 12 files changed, 44 insertions(+), 53 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 68f2342721..543f5c1026 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -48,6 +48,7 @@ struct ConstantPropagation final : impl::ConstantPropagationBase { using ConstantPropagationBase::ConstantPropagationBase; +protected: void runOnOperation() override { ModuleOp module = getOperation(); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp index 275c309e3c..f90e77d13f 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp @@ -39,17 +39,15 @@ namespace mlir::qco { -namespace { - /// @brief Materializes a value range into an owned vector. -template SmallVector toVec(Range&& range) { +template static SmallVector toVec(const Range& range) { return {range.begin(), range.end()}; } /// @brief Whether v is a qubit argument of the entry-point function's entry /// block (so its initial state is |0>, per the pass contract). Arguments of any /// other function have unknown provenance. -bool isEntryPointQubitArgument(Value v) { +static bool isEntryPointQubitArgument(Value v) { const auto arg = dyn_cast(v); if (!arg || !isa(arg.getType())) { return false; @@ -62,7 +60,7 @@ bool isEntryPointQubitArgument(Value v) { /// @brief Ensures every qubit operand of op is tracked before use: an /// entry-point argument starts in |0>, anything else of unknown provenance /// collapses to top. -void ensureSeeded(UnionTable& table, Operation* const op) { +static void ensureSeeded(UnionTable& table, Operation* const op) { for (Value operand : op->getOperands()) { if (!isa(operand.getType()) || table.isTracked(operand)) { continue; @@ -74,14 +72,12 @@ void ensureSeeded(UnionTable& table, Operation* const op) { } } -} // namespace - //===----------------------------------------------------------------------===// // UnionTableLattice //===----------------------------------------------------------------------===// ChangeResult UnionTableLattice::join(const AbstractDenseLattice& other) { - const auto& rhs = static_cast(other); + const auto& rhs = llvm::cast(other); if (!rhs.initialized) { return ChangeResult::NoChange; } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp index 1ee115e6c1..8a9ba50176 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp @@ -46,6 +46,11 @@ class UnionTableLattice : public dataflow::AbstractDenseLattice { using AbstractDenseLattice::AbstractDenseLattice; + /// @brief LLVM-RTTI hook. The dataflow solver instantiates exactly one dense + /// lattice type per analysis, so every AbstractDenseLattice that is handed + /// (e.g. in join) is a UnionTableLattice. + static bool classof(const AbstractDenseLattice*) { return true; } + ChangeResult join(const AbstractDenseLattice& other) override; void print(raw_ostream& os) const override; @@ -111,6 +116,9 @@ class ConstantPropagationAnalysis LogicalResult applyCtrl(UnionTable& table, CtrlOp ctrl, ArrayRef quantumControls); +protected: + void setToEntryState(UnionTableLattice* lattice) override; + public: ConstantPropagationAnalysis(DataFlowSolver& solver, size_t maxNonzeroAmplitudes, @@ -126,8 +134,6 @@ class ConstantPropagationAnalysis std::optional regionTo, const UnionTableLattice& before, UnionTableLattice* after) override; - - void setToEntryState(UnionTableLattice* lattice) override; }; } // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index f936c89d3e..362469bac3 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -33,18 +33,16 @@ namespace mlir::qco { -namespace { - /// @brief Whether ctrlsOut is a valid rename target for ctrlsIn: empty (no /// rename), or the same length. -bool ctrlRenameOk(const ArrayRef ctrlsIn, - const ArrayRef ctrlsOut) { +static bool ctrlRenameOk(const ArrayRef ctrlsIn, + const ArrayRef ctrlsOut) { return ctrlsOut.empty() || ctrlsOut.size() == ctrlsIn.size(); } /// @brief Truthiness of a resolved classical constant (non-zero == true), or /// nullopt if attr is not an integer/index/bool/float constant. -std::optional classicalTruth(const Attribute attr) { +static std::optional classicalTruth(const Attribute attr) { if (const auto ia = dyn_cast_if_present(attr)) { return !ia.getValue().isZero(); } @@ -56,7 +54,7 @@ std::optional classicalTruth(const Attribute attr) { /// @brief Numeric value of a resolved classical constant, or nullopt if attr is /// not an integer/index/bool/float constant. -std::optional classicalDouble(const Attribute attr) { +static std::optional classicalDouble(const Attribute attr) { if (const auto ia = dyn_cast_if_present(attr)) { return static_cast(ia.getValue().getSExtValue()); } @@ -65,7 +63,6 @@ std::optional classicalDouble(const Attribute attr) { } return std::nullopt; } -} // namespace //===----------------------------------------------------------------------===// // Observers @@ -386,13 +383,10 @@ bool HybridState::sameConfiguration(const HybridState& other) const { classical.size() != other.classical.size() || state != other.state) { return false; } - for (const auto& [v, attr] : classical) { - const auto it = other.classical.find(v); - if (it == other.classical.end() || it->second != attr) { - return false; - } - } - return true; + return llvm::all_of(classical, [&](const auto& entry) { + const auto it = other.classical.find(entry.first); + return it != other.classical.end() && it->second == entry.second; + }); } bool HybridState::operator==(const HybridState& other) const { diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 81d3c932cd..cf09d63091 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -196,7 +196,7 @@ LogicalResult QuantumState::applyMatrix2Q(Value in0, Value in1, Value out0, const uint64_t base = key & ~bothBits; const unsigned col = localCol(key); for (unsigned row = 0; row < 4; ++row) { - result[localKey(base, row)] += matrix.data[(4 * row) + col] * amp; + result[localKey(base, row)] += matrix(row, col) * amp; } } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp index cef44c8177..85041a36be 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp @@ -76,11 +76,9 @@ SmallVector collectDecisions(func::FuncOp entry, return decisions; } -namespace { - /// @brief Erases a never-firing controlled gate: every output qubit is replaced /// by the matching input. -void applyDrop(const DropOp& drop, IRRewriter& rewriter) { +static void applyDrop(const DropOp& drop, IRRewriter& rewriter) { CtrlOp op = drop.op; for (auto [in, out] : llvm::zip_equal(op.getInputQubits(), op.getOutputQubits())) { @@ -96,7 +94,7 @@ void applyDrop(const DropOp& drop, IRRewriter& rewriter) { /// body. Dropping every control means the body runs unconditionally: it is /// inlined in place of the op, with the target block arguments bound to the /// target operands and the yielded values taking over the op's target results. -void applyStrip(const StripControls& strip, IRRewriter& rewriter) { +static void applyStrip(const StripControls& strip, IRRewriter& rewriter) { CtrlOp op = strip.op; const auto controlsIn = op.getInputControls(); const auto isDropped = [&](const size_t index) { @@ -148,8 +146,6 @@ void applyStrip(const StripControls& strip, IRRewriter& rewriter) { rewriter.eraseOp(op); } -} // namespace - void applyDecisions(const ArrayRef decisions, IRRewriter& rewriter) { for (const Decision& decision : decisions) { if (const auto* drop = std::get_if(&decision)) { diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 51be394be5..942af8f3fb 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -30,10 +30,9 @@ namespace mlir::qco { -namespace { /// The qubit set of a slot, order-normalized, so two slots (from sibling /// control-flow paths) can be matched. -std::vector qubitKey(const UnionTable::Slot& slot) { +static std::vector qubitKey(const UnionTable::Slot& slot) { std::vector key; key.reserve(slot.front().getQubits().size()); for (Value q : slot.front().getQubits()) { @@ -42,7 +41,6 @@ std::vector qubitKey(const UnionTable::Slot& slot) { llvm::sort(key); return key; } -} // namespace //===----------------------------------------------------------------------===// // Partition helpers diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp index 1af124a445..9a3eee5f5d 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp @@ -30,15 +30,13 @@ #include #include -namespace { - using namespace mlir; using namespace mlir::qco; /// Runs the analysis over module and returns a " -> " line /// for every operation, in walk order. -std::string analyze(ModuleOp module, const size_t maxAmplitudes = 16, - const size_t maxHybridStates = 8) { +static std::string analyze(ModuleOp module, const size_t maxAmplitudes = 16, + const size_t maxHybridStates = 8) { DataFlowSolver solver; solver.load(); solver.load(); @@ -65,6 +63,8 @@ std::string analyze(ModuleOp module, const size_t maxAmplitudes = 16, return out; } +namespace { + class ConstantPropagationAnalysisTest : public testing::Test { protected: MLIRContext context; diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp index 39627662b9..615b2e5296 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -29,20 +29,20 @@ #include #include -namespace { - using namespace mlir; using namespace mlir::qco; -constexpr size_t BUDGET = 16; - -std::string printed(const HybridState& hs) { +static std::string printed(const HybridState& hs) { std::string s; llvm::raw_string_ostream os(s); hs.print(os); return s; } +namespace { + +constexpr size_t BUDGET = 16; + class HybridStateTest : public testing::Test { protected: MLIRContext context; diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 0183949f03..7d7a0b8d06 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -27,19 +27,19 @@ #include #include -namespace { - using namespace mlir; using namespace mlir::qco; /// Renders a QuantumState through its print() method for readable assertions. -std::string printed(const QuantumState& qs) { +static std::string printed(const QuantumState& qs) { std::string s; llvm::raw_string_ostream os(s); qs.print(os); return s; } +namespace { + class QuantumStateTest : public testing::Test { protected: MLIRContext context; diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 4f9943cb5f..7caf7d8cb1 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -27,18 +27,18 @@ #include #include -namespace { - using namespace mlir; using namespace mlir::qco; -std::string printed(const UnionTable& ut) { +static std::string printed(const UnionTable& ut) { std::string s; llvm::raw_string_ostream os(s); ut.print(os); return s; } +namespace { + class UnionTableTest : public testing::Test { protected: MLIRContext context; diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index 6226f4cda5..7f5e4e9e5c 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -28,13 +28,11 @@ #include -namespace { - using namespace mlir; using namespace mlir::qco; /// Number of ops of a given kind anywhere in the module (bodies included). -template unsigned countOps(ModuleOp module) { +template static unsigned countOps(ModuleOp module) { unsigned n = 0; module.walk([&](OpT) { ++n; }); return n; @@ -42,7 +40,7 @@ template unsigned countOps(ModuleOp module) { /// The first op of a given kind in walk order, or a null handle if there is /// none. -template OpT firstOp(ModuleOp module) { +template static OpT firstOp(ModuleOp module) { OpT found; module.walk([&](OpT op) { found = op; @@ -51,6 +49,8 @@ template OpT firstOp(ModuleOp module) { return found; } +namespace { + class ConstantPropagationTest : public testing::Test { protected: MLIRContext context; From ec1b97e80625659ee2d3163f85d1aebeb85f468b Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 18:53:16 +0200 Subject: [PATCH 33/55] :rotating_light: Made implicit cast explicit --- .../Optimizations/ConstantPropagation/QuantumState.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index cf09d63091..dc0d8adf1f 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -320,7 +320,8 @@ QuantumState QuantumState::unify(const QuantumState& that) const { result.qubits.append(that.qubits.begin(), that.qubits.end()); if (top || that.top || result.qubits.size() > MAX_GROUP_QUBITS || - amplitudes.size() * that.amplitudes.size() > maxNonzeroAmplitudes) { + static_cast(amplitudes.size() * that.amplitudes.size()) > + maxNonzeroAmplitudes) { result.markTop(); return result; } From ed8a25b70e345e0a2ab2590fabcff072c1e878f3 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 19:14:28 +0200 Subject: [PATCH 34/55] :rotating_light: Made implicit cast explicit --- .../Optimizations/ConstantPropagation/QuantumState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index dc0d8adf1f..15b25f59b2 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -320,7 +320,7 @@ QuantumState QuantumState::unify(const QuantumState& that) const { result.qubits.append(that.qubits.begin(), that.qubits.end()); if (top || that.top || result.qubits.size() > MAX_GROUP_QUBITS || - static_cast(amplitudes.size() * that.amplitudes.size()) > + static_cast(amplitudes.size()) * that.amplitudes.size() > maxNonzeroAmplitudes) { result.markTop(); return result; From 4604bc2d1802edd5a7df9df3bb2e0a4f7026c80d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 19:36:23 +0200 Subject: [PATCH 35/55] :white_check_mark: Added tests, assisted by Sonnet 5 via Claude Code --- .../Transforms/Optimizations/CMakeLists.txt | 1 + .../test_constantPropagationAnalysis.cpp | 58 +++++++++++++++++++ .../ConstantPropagation/test_hybridState.cpp | 31 ++++++++++ .../ConstantPropagation/test_quantumState.cpp | 26 +++++++++ .../ConstantPropagation/test_unionTable.cpp | 20 +++++++ .../test_qco_constant_propagation.cpp | 44 +++++++++++++- 6 files changed, 178 insertions(+), 2 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index 1eac12e4f1..efe30de551 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -30,6 +30,7 @@ target_link_libraries( MLIRControlFlowDialect MLIRControlFlowInterfaces MLIRFunctionInterfaces + MLIRMQTDialect MLIRQCODDFunctionality MLIRQCOProgramBuilder MLIRQCOPrograms diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp index 9a3eee5f5d..b9a40febde 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp @@ -13,6 +13,8 @@ #include "mlir/Dialect/QCO/IR/QCODialect.h" #include +#include +#include #include #include #include @@ -20,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +31,7 @@ #include #include +#include #include using namespace mlir; @@ -192,4 +196,58 @@ TEST_F(ConstantPropagationAnalysisTest, deterministicMeasurementRecordsBit) { EXPECT_NE(dump.find("classical:"), std::string::npos); } +TEST_F(ConstantPropagationAnalysisTest, resetIsInterpreted) { + auto reg = builder.allocQubitRegister(1); + Value q0 = builder.x(reg[0]); + builder.reset(q0); + const auto module = builder.finalize(); + + const std::string dump = analyze(*module); + EXPECT_EQ(dump.find(""), std::string::npos); + EXPECT_NE(dump.find("qco.reset -> "), std::string::npos); +} + +TEST_F(ConstantPropagationAnalysisTest, globalPhaseIsInterpreted) { + auto reg = builder.allocQubitRegister(1); + builder.x(reg[0]); + builder.gphase(0.5); + const auto module = builder.finalize(); + + const std::string dump = analyze(*module); + EXPECT_EQ(dump.find(""), std::string::npos); + EXPECT_NE(dump.find("qco.gphase -> "), std::string::npos); +} + +TEST_F(ConstantPropagationAnalysisTest, + constantIfIsThreadedThroughBothBranches) { + auto reg = builder.allocQubitRegister(1); + builder.qcoIf( + true, reg[0], [&](const Value arg) { return builder.x(arg); }, + [&](const Value arg) { return builder.h(arg); }); + const auto module = builder.finalize(); + + const std::string dump = analyze(*module); + EXPECT_EQ(dump.find(""), std::string::npos); + EXPECT_NE(dump.find("qco.if -> "), std::string::npos); +} + +TEST_F(ConstantPropagationAnalysisTest, classicalArithmeticIsFolded) { + auto reg = builder.allocQubitRegister(1); + builder.x(reg[0]); + auto module = builder.finalize(); + + OpBuilder ob(module->getContext()); + auto entry = *module->getBody()->getOps().begin(); + ob.setInsertionPointToStart(&entry.getBody().front()); + Value a = + arith::ConstantOp::create(ob, module->getLoc(), ob.getI64IntegerAttr(2)); + Value b = + arith::ConstantOp::create(ob, module->getLoc(), ob.getI64IntegerAttr(3)); + arith::AddIOp::create(ob, module->getLoc(), a, b); + + const std::string dump = analyze(*module); + EXPECT_EQ(dump.find(""), std::string::npos); + EXPECT_NE(dump.find("arith.addi -> "), std::string::npos); +} + } // namespace diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp index 615b2e5296..28ce5a17b4 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -469,4 +469,35 @@ TEST_F(HybridStateTest, printIsNonEmpty) { EXPECT_NE(printed(hs).find("p=1.0000"), std::string::npos); } +TEST_F(HybridStateTest, globalPhaseAcceptsIntegerTheta) { + auto hs = make({q[0]}); + Value theta = builder.intConstant(3); + hs.setClassical(theta, builder.getIntegerAttr(theta.getType(), 3)); + ASSERT_TRUE(hs.addGlobalPhase(theta).succeeded()); + EXPECT_LT(std::abs(hs.getGlobalPhase() - std::polar(1.0, 3.0)), 1e-9); +} + +TEST_F(HybridStateTest, + twoQubitGateSkippedByFalseClassicalControlStillForwards) { + auto hs = make({q[0], q[1]}); + ASSERT_TRUE(hs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix()).succeeded()); + + hs.setClassical(cA, builder.getBoolAttr(false)); + ASSERT_TRUE(hs.applyMatrix2Q(q[0], q[1], q[2], q[3], dcxOp.getUnitaryMatrix(), + {}, {}, {cA}) + .succeeded()); + EXPECT_FALSE(hs.hasQubit(q[0])); + EXPECT_TRUE(hs.hasQubit(q[2])); + EXPECT_TRUE(hs.hasQubit(q[3])); + EXPECT_TRUE(hs.isQubitAlwaysZero(q[2])); + EXPECT_TRUE(hs.isQubitAlwaysOne(q[3])); +} + +TEST_F(HybridStateTest, unresolvedNegativeClassicalControlFails) { + auto hs = make({q[0]}); + EXPECT_TRUE( + hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {}, {}, {}, {cA}) + .failed()); +} + } // namespace diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 7d7a0b8d06..18c52c1eed 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -14,6 +14,7 @@ #include "mlir/Dialect/QCO/IR/QCOOps.h" #include +#include #include #include #include @@ -481,4 +482,29 @@ TEST_F(QuantumStateTest, topStatesAreEqual) { EXPECT_FALSE(a == QuantumState({q[0], q[1], q[2], q[3]}, 4)); } + +TEST_F(QuantumStateTest, groupWiderThanTheIndexTypeIsTop) { + auto reg = builder.allocQubitRegister(64); + SmallVector many; + for (size_t i = 0; i < 64; ++i) { + many.push_back(reg[i]); + } + const auto qs = QuantumState(many, 4); + EXPECT_TRUE(qs.isTop()); +} + +TEST_F(QuantumStateTest, printRendersImaginaryAmplitudes) { + auto qs = QuantumState::singletonZero(q[0], 4); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); + ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], SOp::getUnitaryMatrix()).succeeded()); + EXPECT_NE(printed(qs).find(" i"), std::string::npos); +} + +TEST_F(QuantumStateTest, twoQubitGateWithControlNotInGroupFails) { + auto qs = QuantumState({q[0], q[1]}, 4); + EXPECT_TRUE(qs.applyMatrix2Q(q[0], q[1], q[0], q[1], + swapOp.getUnitaryMatrix(), {q[2]}) + .failed()); +} + } // namespace diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 7caf7d8cb1..286b3f9fe9 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -428,4 +428,24 @@ TEST_F(UnionTableTest, markAllTopIsAbsorbing) { EXPECT_FALSE(ut.isTracked(q[1])); } +TEST_F(UnionTableTest, superfluousControlsListsAlwaysTrueClassicalControl) { + auto ut = make(); + Value c = builder.boolConstant(true); + ut.seedClassical(c, builder.getBoolAttr(true)); + + const auto result = ut.getSuperfluousControls({}, {c}); + EXPECT_FALSE(result.completelySuperfluous); + EXPECT_TRUE(result.superfluousClassicalValues.contains(c)); +} + +TEST_F(UnionTableTest, superfluousControlsListsAlwaysFalseNegativeClassicalControl) { + auto ut = make(); + Value c = builder.boolConstant(false); + ut.seedClassical(c, builder.getBoolAttr(false)); + + const auto result = ut.getSuperfluousControls({}, {}, {c}); + EXPECT_FALSE(result.completelySuperfluous); + EXPECT_TRUE(result.superfluousClassicalValues.contains(c)); +} + } // namespace diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index 7f5e4e9e5c..d4a8a1d9d4 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/MQT/IR/MQTDialect.h" #include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" @@ -18,9 +19,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -207,8 +210,8 @@ TEST_F(ConstantPropagationTest, simplifiesChainOfControlledGates) { TEST_F(ConstantPropagationTest, noControlledGatesIsNoOp) { auto reg = builder.allocQubitRegister(2); - (void)builder.x(reg[0]); - (void)builder.h(reg[1]); + builder.x(reg[0]); + builder.h(reg[1]); const auto module = builder.finalize(); ASSERT_TRUE(succeeded(run(*module))); @@ -217,4 +220,41 @@ TEST_F(ConstantPropagationTest, noControlledGatesIsNoOp) { EXPECT_EQ(countOps(*module), 1U); } +TEST_F(ConstantPropagationTest, missingEntryPointIsANoOp) { + auto reg = builder.allocQubitRegister(2); + Value q0 = builder.x(reg[0]); + builder.cx(q0, reg[1]); + auto module = builder.finalize(); + + mqt::removeEntryPoint(mqt::getEntryPoint(*module).getOperation()); + + ASSERT_TRUE(succeeded(run(*module))); + EXPECT_EQ(countOps(*module), 1U); +} + +TEST_F(ConstantPropagationTest, multipleEntryPointsFail) { + auto reg = builder.allocQubitRegister(1); + builder.x(reg[0]); + auto module = builder.finalize(); + + OpBuilder ob(module->getContext()); + ob.setInsertionPointToEnd(module->getBody()); + auto second = func::FuncOp::create(ob, module->getLoc(), "second", + ob.getFunctionType({}, {})); + mqt::setEntryPoint(second.getOperation()); + + EXPECT_TRUE(failed(run(*module))); +} + +TEST_F(ConstantPropagationTest, runsOnProgramWithConstantIf) { + auto reg = builder.allocQubitRegister(1); + builder.qcoIf( + true, reg[0], [&](const Value arg) { return builder.x(arg); }, + [&](const Value arg) { return builder.h(arg); }); + const auto module = builder.finalize(); + + ASSERT_TRUE(succeeded(run(*module))); + EXPECT_TRUE(succeeded(verify(*module))); +} + } // namespace From e74d76099de1eaa5ae0af672c7da47cd5984faa2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 17:37:48 +0000 Subject: [PATCH 36/55] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Optimizations/ConstantPropagation/test_quantumState.cpp | 6 +++--- .../Optimizations/ConstantPropagation/test_unionTable.cpp | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 18c52c1eed..8048e789b6 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -482,7 +482,6 @@ TEST_F(QuantumStateTest, topStatesAreEqual) { EXPECT_FALSE(a == QuantumState({q[0], q[1], q[2], q[3]}, 4)); } - TEST_F(QuantumStateTest, groupWiderThanTheIndexTypeIsTop) { auto reg = builder.allocQubitRegister(64); SmallVector many; @@ -496,14 +495,15 @@ TEST_F(QuantumStateTest, groupWiderThanTheIndexTypeIsTop) { TEST_F(QuantumStateTest, printRendersImaginaryAmplitudes) { auto qs = QuantumState::singletonZero(q[0], 4); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); - ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], SOp::getUnitaryMatrix()).succeeded()); + ASSERT_TRUE( + qs.applyMatrix1Q(q[0], q[0], SOp::getUnitaryMatrix()).succeeded()); EXPECT_NE(printed(qs).find(" i"), std::string::npos); } TEST_F(QuantumStateTest, twoQubitGateWithControlNotInGroupFails) { auto qs = QuantumState({q[0], q[1]}, 4); EXPECT_TRUE(qs.applyMatrix2Q(q[0], q[1], q[0], q[1], - swapOp.getUnitaryMatrix(), {q[2]}) + swapOp.getUnitaryMatrix(), {q[2]}) .failed()); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 286b3f9fe9..ba8129a48e 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -438,7 +438,8 @@ TEST_F(UnionTableTest, superfluousControlsListsAlwaysTrueClassicalControl) { EXPECT_TRUE(result.superfluousClassicalValues.contains(c)); } -TEST_F(UnionTableTest, superfluousControlsListsAlwaysFalseNegativeClassicalControl) { +TEST_F(UnionTableTest, + superfluousControlsListsAlwaysFalseNegativeClassicalControl) { auto ut = make(); Value c = builder.boolConstant(false); ut.seedClassical(c, builder.getBoolAttr(false)); From 1a64251d4bd0827c5b559261ec114b2d54e462e7 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 19:47:45 +0200 Subject: [PATCH 37/55] :rotating_light: Removed const modifier of mlir value --- .../ConstantPropagation/test_constantPropagationAnalysis.cpp | 4 ++-- .../Optimizations/test_qco_constant_propagation.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp index b9a40febde..904e2ef9e0 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp @@ -222,8 +222,8 @@ TEST_F(ConstantPropagationAnalysisTest, constantIfIsThreadedThroughBothBranches) { auto reg = builder.allocQubitRegister(1); builder.qcoIf( - true, reg[0], [&](const Value arg) { return builder.x(arg); }, - [&](const Value arg) { return builder.h(arg); }); + true, reg[0], [&](Value arg) { return builder.x(arg); }, + [&](Value arg) { return builder.h(arg); }); const auto module = builder.finalize(); const std::string dump = analyze(*module); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index d4a8a1d9d4..aef515e195 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -249,8 +249,8 @@ TEST_F(ConstantPropagationTest, multipleEntryPointsFail) { TEST_F(ConstantPropagationTest, runsOnProgramWithConstantIf) { auto reg = builder.allocQubitRegister(1); builder.qcoIf( - true, reg[0], [&](const Value arg) { return builder.x(arg); }, - [&](const Value arg) { return builder.h(arg); }); + true, reg[0], [&](Value arg) { return builder.x(arg); }, + [&](Value arg) { return builder.h(arg); }); const auto module = builder.finalize(); ASSERT_TRUE(succeeded(run(*module))); From d838bb94fa0876d531026c96e55dd5e7e0f4fcc5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 20:55:22 +0200 Subject: [PATCH 38/55] :memo: Updated CHANGELOG.md --- CHANGELOG.md | 463 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 458 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c61339a34..77b9ed0b2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,14 +56,12 @@ releases may include breaking changes. #### Passes and transformations -- Add pass tp remove controlling qubits via constant propagation ([#2280]) - ([**@lirem101**]) - ✨ Add passes for quantum-specific interprocedural optimizations ([#2193]) ([**@DRovara**], [**@burgholzer**]) -- ✨ Add Pauli twirling, quantum loop unrolling, and qubit reuse passes +- ✨ Add Pauli twirling, quantum loop unrolling, qubit reuse passes, and constant propagation ([#1705], [#1718], [#1755], [#1756], [#1923], [#1924], [#2039], [#2118], - [#2216], [#2224]) ([**@MatthiasReumann**], [**@DRovara**], [**@burgholzer**], - [**@simon1hofmann**]) + [#2216], [#2224], [#2280]) ([**@MatthiasReumann**], [**@DRovara**], [**@burgholzer**], + [**@simon1hofmann**], [**@lirem101**]) - ✨ Add a compiler-target-aware `place-and-route` pass ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], [#1600], [#1664], [#1709], [#1716], [#1748], [#1805], [#1870], [#1904], [#1911], [#1951], [#1997], [#2016], @@ -836,470 +834,925 @@ for previous changelogs._ [unreleased]: https://github.com/munich-quantum-toolkit/core/compare/v3.9.2...HEAD + [3.9.2]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.9.2 + [3.9.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.9.1 + [3.9.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.9.0 + [3.8.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.8.0 + [3.7.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.7.0 + [3.6.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.6.1 + [3.6.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.6.0 + [3.5.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.5.1 + [3.5.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.5.0 + [3.4.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.4.1 + [3.4.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.4.0 + [3.3.3]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.3.3 + [3.3.2]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.3.2 + [3.3.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.3.1 + [3.3.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.3.0 + [3.2.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.2.1 + [3.2.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.2.0 + [3.1.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.1.0 + [3.0.2]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.0.2 + [3.0.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.0.1 + [3.0.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.0.0 + [2.7.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v2.7.0 [#2280]: https://github.com/munich-quantum-toolkit/core/pull/2280 + [#2278]: https://github.com/munich-quantum-toolkit/core/pull/2278 + [#2270]: https://github.com/munich-quantum-toolkit/core/pull/2270 + [#2262]: https://github.com/munich-quantum-toolkit/core/pull/2262 + [#2259]: https://github.com/munich-quantum-toolkit/core/pull/2259 + [#2258]: https://github.com/munich-quantum-toolkit/core/pull/2258 + [#2257]: https://github.com/munich-quantum-toolkit/core/pull/2257 + [#2249]: https://github.com/munich-quantum-toolkit/core/pull/2249 + [#2246]: https://github.com/munich-quantum-toolkit/core/pull/2246 + [#2220]: https://github.com/munich-quantum-toolkit/core/pull/2220 + [#2232]: https://github.com/munich-quantum-toolkit/core/pull/2232 + [#2228]: https://github.com/munich-quantum-toolkit/core/pull/2228 + [#2224]: https://github.com/munich-quantum-toolkit/core/pull/2224 + [#2209]: https://github.com/munich-quantum-toolkit/core/pull/2209 + [#2211]: https://github.com/munich-quantum-toolkit/core/pull/2211 + [#2217]: https://github.com/munich-quantum-toolkit/core/pull/2217 + [#2210]: https://github.com/munich-quantum-toolkit/core/pull/2210 + [#2216]: https://github.com/munich-quantum-toolkit/core/pull/2216 + [#2203]: https://github.com/munich-quantum-toolkit/core/pull/2203 + [#2214]: https://github.com/munich-quantum-toolkit/core/pull/2214 + [#2193]: https://github.com/munich-quantum-toolkit/core/pull/2193 + [#2178]: https://github.com/munich-quantum-toolkit/core/pull/2178 + [#2176]: https://github.com/munich-quantum-toolkit/core/pull/2176 + [#2175]: https://github.com/munich-quantum-toolkit/core/pull/2175 + [#2169]: https://github.com/munich-quantum-toolkit/core/pull/2169 + [#2168]: https://github.com/munich-quantum-toolkit/core/pull/2168 + [#2158]: https://github.com/munich-quantum-toolkit/core/pull/2158 + [#2157]: https://github.com/munich-quantum-toolkit/core/pull/2157 + [#2156]: https://github.com/munich-quantum-toolkit/core/pull/2156 + [#2154]: https://github.com/munich-quantum-toolkit/core/pull/2154 + [#2150]: https://github.com/munich-quantum-toolkit/core/pull/2150 + [#2149]: https://github.com/munich-quantum-toolkit/core/pull/2149 + [#2148]: https://github.com/munich-quantum-toolkit/core/pull/2148 + [#2147]: https://github.com/munich-quantum-toolkit/core/pull/2147 + [#2141]: https://github.com/munich-quantum-toolkit/core/pull/2141 + [#2140]: https://github.com/munich-quantum-toolkit/core/pull/2140 + [#2138]: https://github.com/munich-quantum-toolkit/core/pull/2138 + [#2137]: https://github.com/munich-quantum-toolkit/core/pull/2137 + [#2136]: https://github.com/munich-quantum-toolkit/core/pull/2136 + [#2133]: https://github.com/munich-quantum-toolkit/core/pull/2133 + [#2125]: https://github.com/munich-quantum-toolkit/core/pull/2125 + [#2124]: https://github.com/munich-quantum-toolkit/core/pull/2124 + [#2118]: https://github.com/munich-quantum-toolkit/core/pull/2118 + [#2116]: https://github.com/munich-quantum-toolkit/core/pull/2116 + [#2115]: https://github.com/munich-quantum-toolkit/core/pull/2115 + [#2114]: https://github.com/munich-quantum-toolkit/core/pull/2114 + [#2112]: https://github.com/munich-quantum-toolkit/core/pull/2112 + [#2111]: https://github.com/munich-quantum-toolkit/core/pull/2111 + [#2108]: https://github.com/munich-quantum-toolkit/core/pull/2108 + [#2106]: https://github.com/munich-quantum-toolkit/core/pull/2106 + [#2105]: https://github.com/munich-quantum-toolkit/core/pull/2105 + [#2084]: https://github.com/munich-quantum-toolkit/core/pull/2084 + [#2082]: https://github.com/munich-quantum-toolkit/core/pull/2082 + [#2074]: https://github.com/munich-quantum-toolkit/core/pull/2074 + [#2066]: https://github.com/munich-quantum-toolkit/core/pull/2066 + [#2060]: https://github.com/munich-quantum-toolkit/core/pull/2060 + [#2058]: https://github.com/munich-quantum-toolkit/core/pull/2058 + [#2054]: https://github.com/munich-quantum-toolkit/core/pull/2054 + [#2049]: https://github.com/munich-quantum-toolkit/core/pull/2049 + [#2046]: https://github.com/munich-quantum-toolkit/core/pull/2046 + [#2043]: https://github.com/munich-quantum-toolkit/core/pull/2043 + [#2042]: https://github.com/munich-quantum-toolkit/core/pull/2042 + [#2039]: https://github.com/munich-quantum-toolkit/core/pull/2039 + [#2038]: https://github.com/munich-quantum-toolkit/core/pull/2038 + [#2036]: https://github.com/munich-quantum-toolkit/core/pull/2036 + [#2035]: https://github.com/munich-quantum-toolkit/core/pull/2035 + [#2031]: https://github.com/munich-quantum-toolkit/core/pull/2031 + [#2030]: https://github.com/munich-quantum-toolkit/core/pull/2030 + [#2028]: https://github.com/munich-quantum-toolkit/core/pull/2028 + [#2026]: https://github.com/munich-quantum-toolkit/core/pull/2026 + [#2025]: https://github.com/munich-quantum-toolkit/core/pull/2025 + [#2018]: https://github.com/munich-quantum-toolkit/core/pull/2018 + [#2017]: https://github.com/munich-quantum-toolkit/core/pull/2017 + [#2016]: https://github.com/munich-quantum-toolkit/core/pull/2016 + [#2015]: https://github.com/munich-quantum-toolkit/core/pull/2015 + [#2014]: https://github.com/munich-quantum-toolkit/core/pull/2014 + [#2011]: https://github.com/munich-quantum-toolkit/core/pull/2011 + [#2010]: https://github.com/munich-quantum-toolkit/core/pull/2010 + [#2008]: https://github.com/munich-quantum-toolkit/core/pull/2008 + [#2007]: https://github.com/munich-quantum-toolkit/core/pull/2007 + [#2006]: https://github.com/munich-quantum-toolkit/core/pull/2006 + [#2005]: https://github.com/munich-quantum-toolkit/core/pull/2005 + [#2003]: https://github.com/munich-quantum-toolkit/core/pull/2003 + [#2002]: https://github.com/munich-quantum-toolkit/core/pull/2002 + [#2001]: https://github.com/munich-quantum-toolkit/core/pull/2001 + [#2000]: https://github.com/munich-quantum-toolkit/core/pull/2000 + [#1999]: https://github.com/munich-quantum-toolkit/core/pull/1999 + [#1998]: https://github.com/munich-quantum-toolkit/core/pull/1998 + [#1997]: https://github.com/munich-quantum-toolkit/core/pull/1997 + [#1996]: https://github.com/munich-quantum-toolkit/core/pull/1996 + [#1995]: https://github.com/munich-quantum-toolkit/core/pull/1995 + [#1994]: https://github.com/munich-quantum-toolkit/core/pull/1994 + [#1993]: https://github.com/munich-quantum-toolkit/core/pull/1993 + [#1992]: https://github.com/munich-quantum-toolkit/core/pull/1992 + [#1989]: https://github.com/munich-quantum-toolkit/core/pull/1989 + [#1987]: https://github.com/munich-quantum-toolkit/core/pull/1987 + [#1986]: https://github.com/munich-quantum-toolkit/core/pull/1986 + [#1984]: https://github.com/munich-quantum-toolkit/core/pull/1984 + [#1983]: https://github.com/munich-quantum-toolkit/core/pull/1983 + [#1980]: https://github.com/munich-quantum-toolkit/core/pull/1980 + [#1979]: https://github.com/munich-quantum-toolkit/core/pull/1979 + [#1978]: https://github.com/munich-quantum-toolkit/core/pull/1978 + [#1976]: https://github.com/munich-quantum-toolkit/core/pull/1976 + [#1975]: https://github.com/munich-quantum-toolkit/core/pull/1975 + [#1974]: https://github.com/munich-quantum-toolkit/core/pull/1974 + [#1973]: https://github.com/munich-quantum-toolkit/core/pull/1973 + [#1972]: https://github.com/munich-quantum-toolkit/core/pull/1972 + [#1967]: https://github.com/munich-quantum-toolkit/core/pull/1967 + [#1965]: https://github.com/munich-quantum-toolkit/core/pull/1965 + [#1961]: https://github.com/munich-quantum-toolkit/core/pull/1961 + [#1957]: https://github.com/munich-quantum-toolkit/core/pull/1957 + [#1953]: https://github.com/munich-quantum-toolkit/core/pull/1953 + [#1952]: https://github.com/munich-quantum-toolkit/core/pull/1952 + [#1951]: https://github.com/munich-quantum-toolkit/core/pull/1951 + [#1950]: https://github.com/munich-quantum-toolkit/core/pull/1950 + [#1938]: https://github.com/munich-quantum-toolkit/core/pull/1938 + [#1936]: https://github.com/munich-quantum-toolkit/core/pull/1936 + [#1935]: https://github.com/munich-quantum-toolkit/core/pull/1935 + [#1934]: https://github.com/munich-quantum-toolkit/core/pull/1934 + [#1933]: https://github.com/munich-quantum-toolkit/core/pull/1933 + [#1927]: https://github.com/munich-quantum-toolkit/core/pull/1927 + [#1925]: https://github.com/munich-quantum-toolkit/core/pull/1925 + [#1924]: https://github.com/munich-quantum-toolkit/core/pull/1924 + [#1923]: https://github.com/munich-quantum-toolkit/core/pull/1923 + [#1915]: https://github.com/munich-quantum-toolkit/core/pull/1915 + [#1914]: https://github.com/munich-quantum-toolkit/core/pull/1914 + [#1912]: https://github.com/munich-quantum-toolkit/core/pull/1912 + [#1911]: https://github.com/munich-quantum-toolkit/core/pull/1911 + [#1910]: https://github.com/munich-quantum-toolkit/core/pull/1910 + [#1904]: https://github.com/munich-quantum-toolkit/core/pull/1904 + [#1897]: https://github.com/munich-quantum-toolkit/core/pull/1897 + [#1895]: https://github.com/munich-quantum-toolkit/core/pull/1895 + [#1887]: https://github.com/munich-quantum-toolkit/core/pull/1887 + [#1886]: https://github.com/munich-quantum-toolkit/core/pull/1886 + [#1877]: https://github.com/munich-quantum-toolkit/core/pull/1877 + [#1873]: https://github.com/munich-quantum-toolkit/core/pull/1873 + [#1872]: https://github.com/munich-quantum-toolkit/core/pull/1872 + [#1870]: https://github.com/munich-quantum-toolkit/core/pull/1870 + [#1869]: https://github.com/munich-quantum-toolkit/core/pull/1869 + [#1865]: https://github.com/munich-quantum-toolkit/core/pull/1865 + [#1850]: https://github.com/munich-quantum-toolkit/core/pull/1850 + [#1849]: https://github.com/munich-quantum-toolkit/core/pull/1849 + [#1848]: https://github.com/munich-quantum-toolkit/core/pull/1848 + [#1844]: https://github.com/munich-quantum-toolkit/core/pull/1844 + [#1842]: https://github.com/munich-quantum-toolkit/core/pull/1842 + [#1836]: https://github.com/munich-quantum-toolkit/core/pull/1836 + [#1832]: https://github.com/munich-quantum-toolkit/core/pull/1832 + [#1830]: https://github.com/munich-quantum-toolkit/core/pull/1830 + [#1828]: https://github.com/munich-quantum-toolkit/core/pull/1828 + [#1826]: https://github.com/munich-quantum-toolkit/core/pull/1826 + [#1824]: https://github.com/munich-quantum-toolkit/core/pull/1824 + [#1823]: https://github.com/munich-quantum-toolkit/core/pull/1823 + [#1817]: https://github.com/munich-quantum-toolkit/core/pull/1817 + [#1815]: https://github.com/munich-quantum-toolkit/core/pull/1815 + [#1814]: https://github.com/munich-quantum-toolkit/core/pull/1814 + [#1810]: https://github.com/munich-quantum-toolkit/core/pull/1810 + [#1809]: https://github.com/munich-quantum-toolkit/core/pull/1809 + [#1808]: https://github.com/munich-quantum-toolkit/core/pull/1808 + [#1807]: https://github.com/munich-quantum-toolkit/core/pull/1807 + [#1806]: https://github.com/munich-quantum-toolkit/core/pull/1806 + [#1805]: https://github.com/munich-quantum-toolkit/core/pull/1805 + [#1803]: https://github.com/munich-quantum-toolkit/core/pull/1803 + [#1802]: https://github.com/munich-quantum-toolkit/core/pull/1802 + [#1799]: https://github.com/munich-quantum-toolkit/core/pull/1799 + [#1787]: https://github.com/munich-quantum-toolkit/core/pull/1787 + [#1786]: https://github.com/munich-quantum-toolkit/core/pull/1786 + [#1782]: https://github.com/munich-quantum-toolkit/core/pull/1782 + [#1781]: https://github.com/munich-quantum-toolkit/core/pull/1781 + [#1780]: https://github.com/munich-quantum-toolkit/core/pull/1780 + [#1776]: https://github.com/munich-quantum-toolkit/core/pull/1776 + [#1774]: https://github.com/munich-quantum-toolkit/core/pull/1774 + [#1766]: https://github.com/munich-quantum-toolkit/core/pull/1766 + [#1765]: https://github.com/munich-quantum-toolkit/core/pull/1765 + [#1762]: https://github.com/munich-quantum-toolkit/core/pull/1762 + [#1756]: https://github.com/munich-quantum-toolkit/core/pull/1756 + [#1755]: https://github.com/munich-quantum-toolkit/core/pull/1755 + [#1751]: https://github.com/munich-quantum-toolkit/core/pull/1751 + [#1749]: https://github.com/munich-quantum-toolkit/core/pull/1749 + [#1748]: https://github.com/munich-quantum-toolkit/core/pull/1748 + [#1737]: https://github.com/munich-quantum-toolkit/core/pull/1737 + [#1730]: https://github.com/munich-quantum-toolkit/core/pull/1730 + [#1728]: https://github.com/munich-quantum-toolkit/core/pull/1728 + [#1720]: https://github.com/munich-quantum-toolkit/core/pull/1720 + [#1719]: https://github.com/munich-quantum-toolkit/core/pull/1719 + [#1718]: https://github.com/munich-quantum-toolkit/core/pull/1718 + [#1717]: https://github.com/munich-quantum-toolkit/core/pull/1717 + [#1716]: https://github.com/munich-quantum-toolkit/core/pull/1716 + [#1710]: https://github.com/munich-quantum-toolkit/core/pull/1710 + [#1709]: https://github.com/munich-quantum-toolkit/core/pull/1709 + [#1706]: https://github.com/munich-quantum-toolkit/core/pull/1706 + [#1705]: https://github.com/munich-quantum-toolkit/core/pull/1705 + [#1702]: https://github.com/munich-quantum-toolkit/core/pull/1702 + [#1700]: https://github.com/munich-quantum-toolkit/core/pull/1700 + [#1694]: https://github.com/munich-quantum-toolkit/core/pull/1694 + [#1687]: https://github.com/munich-quantum-toolkit/core/pull/1687 + [#1676]: https://github.com/munich-quantum-toolkit/core/pull/1676 + [#1675]: https://github.com/munich-quantum-toolkit/core/pull/1675 + [#1674]: https://github.com/munich-quantum-toolkit/core/pull/1674 + [#1673]: https://github.com/munich-quantum-toolkit/core/pull/1673 + [#1672]: https://github.com/munich-quantum-toolkit/core/pull/1672 + [#1664]: https://github.com/munich-quantum-toolkit/core/pull/1664 + [#1662]: https://github.com/munich-quantum-toolkit/core/pull/1662 + [#1660]: https://github.com/munich-quantum-toolkit/core/pull/1660 + [#1652]: https://github.com/munich-quantum-toolkit/core/pull/1652 + [#1648]: https://github.com/munich-quantum-toolkit/core/pull/1648 + [#1638]: https://github.com/munich-quantum-toolkit/core/pull/1638 + [#1637]: https://github.com/munich-quantum-toolkit/core/pull/1637 + [#1635]: https://github.com/munich-quantum-toolkit/core/pull/1635 + [#1627]: https://github.com/munich-quantum-toolkit/core/pull/1627 + [#1626]: https://github.com/munich-quantum-toolkit/core/pull/1626 + [#1624]: https://github.com/munich-quantum-toolkit/core/pull/1624 + [#1623]: https://github.com/munich-quantum-toolkit/core/pull/1623 + [#1620]: https://github.com/munich-quantum-toolkit/core/pull/1620 + [#1605]: https://github.com/munich-quantum-toolkit/core/pull/1605 + [#1603]: https://github.com/munich-quantum-toolkit/core/pull/1603 + [#1602]: https://github.com/munich-quantum-toolkit/core/pull/1602 + [#1600]: https://github.com/munich-quantum-toolkit/core/pull/1600 + [#1596]: https://github.com/munich-quantum-toolkit/core/pull/1596 + [#1593]: https://github.com/munich-quantum-toolkit/core/pull/1593 + [#1588]: https://github.com/munich-quantum-toolkit/core/pull/1588 + [#1583]: https://github.com/munich-quantum-toolkit/core/pull/1583 + [#1581]: https://github.com/munich-quantum-toolkit/core/pull/1581 + [#1580]: https://github.com/munich-quantum-toolkit/core/pull/1580 + [#1573]: https://github.com/munich-quantum-toolkit/core/pull/1573 + [#1572]: https://github.com/munich-quantum-toolkit/core/pull/1572 + [#1571]: https://github.com/munich-quantum-toolkit/core/pull/1571 + [#1570]: https://github.com/munich-quantum-toolkit/core/pull/1570 + [#1569]: https://github.com/munich-quantum-toolkit/core/pull/1569 + [#1568]: https://github.com/munich-quantum-toolkit/core/pull/1568 + [#1567]: https://github.com/munich-quantum-toolkit/core/pull/1567 + [#1565]: https://github.com/munich-quantum-toolkit/core/pull/1565 + [#1564]: https://github.com/munich-quantum-toolkit/core/pull/1564 + [#1554]: https://github.com/munich-quantum-toolkit/core/pull/1554 + [#1550]: https://github.com/munich-quantum-toolkit/core/pull/1550 + [#1549]: https://github.com/munich-quantum-toolkit/core/pull/1549 + [#1548]: https://github.com/munich-quantum-toolkit/core/pull/1548 + [#1547]: https://github.com/munich-quantum-toolkit/core/pull/1547 + [#1542]: https://github.com/munich-quantum-toolkit/core/pull/1542 + [#1537]: https://github.com/munich-quantum-toolkit/core/pull/1537 + [#1528]: https://github.com/munich-quantum-toolkit/core/pull/1528 + [#1521]: https://github.com/munich-quantum-toolkit/core/pull/1521 + [#1513]: https://github.com/munich-quantum-toolkit/core/pull/1513 + [#1510]: https://github.com/munich-quantum-toolkit/core/pull/1510 + [#1507]: https://github.com/munich-quantum-toolkit/core/pull/1507 + [#1506]: https://github.com/munich-quantum-toolkit/core/pull/1506 + [#1481]: https://github.com/munich-quantum-toolkit/core/pull/1481 + [#1479]: https://github.com/munich-quantum-toolkit/core/pull/1479 + [#1475]: https://github.com/munich-quantum-toolkit/core/pull/1475 + [#1474]: https://github.com/munich-quantum-toolkit/core/pull/1474 + [#1472]: https://github.com/munich-quantum-toolkit/core/pull/1472 + [#1471]: https://github.com/munich-quantum-toolkit/core/pull/1471 + [#1470]: https://github.com/munich-quantum-toolkit/core/pull/1470 + [#1466]: https://github.com/munich-quantum-toolkit/core/pull/1466 + [#1465]: https://github.com/munich-quantum-toolkit/core/pull/1465 + [#1464]: https://github.com/munich-quantum-toolkit/core/pull/1464 + [#1458]: https://github.com/munich-quantum-toolkit/core/pull/1458 + [#1453]: https://github.com/munich-quantum-toolkit/core/pull/1453 + [#1447]: https://github.com/munich-quantum-toolkit/core/pull/1447 + [#1446]: https://github.com/munich-quantum-toolkit/core/pull/1446 + [#1444]: https://github.com/munich-quantum-toolkit/core/pull/1444 + [#1443]: https://github.com/munich-quantum-toolkit/core/pull/1443 + [#1437]: https://github.com/munich-quantum-toolkit/core/pull/1437 + [#1436]: https://github.com/munich-quantum-toolkit/core/pull/1436 + [#1430]: https://github.com/munich-quantum-toolkit/core/pull/1430 + [#1428]: https://github.com/munich-quantum-toolkit/core/pull/1428 + [#1415]: https://github.com/munich-quantum-toolkit/core/pull/1415 + [#1414]: https://github.com/munich-quantum-toolkit/core/pull/1414 + [#1413]: https://github.com/munich-quantum-toolkit/core/pull/1413 + [#1412]: https://github.com/munich-quantum-toolkit/core/pull/1412 + [#1411]: https://github.com/munich-quantum-toolkit/core/pull/1411 + [#1407]: https://github.com/munich-quantum-toolkit/core/pull/1407 + [#1406]: https://github.com/munich-quantum-toolkit/core/pull/1406 + [#1403]: https://github.com/munich-quantum-toolkit/core/pull/1403 + [#1402]: https://github.com/munich-quantum-toolkit/core/pull/1402 + [#1385]: https://github.com/munich-quantum-toolkit/core/pull/1385 + [#1384]: https://github.com/munich-quantum-toolkit/core/pull/1384 + [#1383]: https://github.com/munich-quantum-toolkit/core/pull/1383 + [#1382]: https://github.com/munich-quantum-toolkit/core/pull/1382 + [#1381]: https://github.com/munich-quantum-toolkit/core/pull/1381 + [#1380]: https://github.com/munich-quantum-toolkit/core/pull/1380 + [#1378]: https://github.com/munich-quantum-toolkit/core/pull/1378 + [#1375]: https://github.com/munich-quantum-toolkit/core/pull/1375 + [#1371]: https://github.com/munich-quantum-toolkit/core/pull/1371 + [#1359]: https://github.com/munich-quantum-toolkit/core/pull/1359 + [#1356]: https://github.com/munich-quantum-toolkit/core/pull/1356 + [#1355]: https://github.com/munich-quantum-toolkit/core/pull/1355 + [#1338]: https://github.com/munich-quantum-toolkit/core/pull/1338 + [#1336]: https://github.com/munich-quantum-toolkit/core/pull/1336 + [#1330]: https://github.com/munich-quantum-toolkit/core/pull/1330 + [#1328]: https://github.com/munich-quantum-toolkit/core/pull/1328 + [#1327]: https://github.com/munich-quantum-toolkit/core/pull/1327 + [#1310]: https://github.com/munich-quantum-toolkit/core/pull/1310 + [#1301]: https://github.com/munich-quantum-toolkit/core/pull/1301 + [#1300]: https://github.com/munich-quantum-toolkit/core/pull/1300 + [#1299]: https://github.com/munich-quantum-toolkit/core/pull/1299 + [#1294]: https://github.com/munich-quantum-toolkit/core/pull/1294 + [#1293]: https://github.com/munich-quantum-toolkit/core/pull/1293 + [#1287]: https://github.com/munich-quantum-toolkit/core/pull/1287 + [#1283]: https://github.com/munich-quantum-toolkit/core/pull/1283 + [#1279]: https://github.com/munich-quantum-toolkit/core/pull/1279 + [#1276]: https://github.com/munich-quantum-toolkit/core/pull/1276 + [#1271]: https://github.com/munich-quantum-toolkit/core/pull/1271 + [#1269]: https://github.com/munich-quantum-toolkit/core/pull/1269 + [#1264]: https://github.com/munich-quantum-toolkit/core/pull/1264 + [#1263]: https://github.com/munich-quantum-toolkit/core/pull/1263 + [#1247]: https://github.com/munich-quantum-toolkit/core/pull/1247 + [#1246]: https://github.com/munich-quantum-toolkit/core/pull/1246 + [#1243]: https://github.com/munich-quantum-toolkit/core/pull/1243 + [#1237]: https://github.com/munich-quantum-toolkit/core/pull/1237 + [#1236]: https://github.com/munich-quantum-toolkit/core/pull/1236 + [#1235]: https://github.com/munich-quantum-toolkit/core/pull/1235 + [#1232]: https://github.com/munich-quantum-toolkit/core/pull/1232 + [#1224]: https://github.com/munich-quantum-toolkit/core/pull/1224 + [#1223]: https://github.com/munich-quantum-toolkit/core/pull/1223 + [#1211]: https://github.com/munich-quantum-toolkit/core/pull/1211 + [#1210]: https://github.com/munich-quantum-toolkit/core/pull/1210 + [#1209]: https://github.com/munich-quantum-toolkit/core/pull/1209 + [#1207]: https://github.com/munich-quantum-toolkit/core/pull/1207 + [#1186]: https://github.com/munich-quantum-toolkit/core/pull/1186 + [#1181]: https://github.com/munich-quantum-toolkit/core/pull/1181 + [#1180]: https://github.com/munich-quantum-toolkit/core/pull/1180 + [#1164]: https://github.com/munich-quantum-toolkit/core/pull/1164 + [#1157]: https://github.com/munich-quantum-toolkit/core/pull/1157 + [#1151]: https://github.com/munich-quantum-toolkit/core/pull/1151 + [#1150]: https://github.com/munich-quantum-toolkit/core/pull/1150 + [#1148]: https://github.com/munich-quantum-toolkit/core/pull/1148 + [#1147]: https://github.com/munich-quantum-toolkit/core/pull/1147 + [#1140]: https://github.com/munich-quantum-toolkit/core/pull/1140 + [#1139]: https://github.com/munich-quantum-toolkit/core/pull/1139 + [#1117]: https://github.com/munich-quantum-toolkit/core/pull/1117 + [#1116]: https://github.com/munich-quantum-toolkit/core/pull/1116 + [#1108]: https://github.com/munich-quantum-toolkit/core/pull/1108 + [#1106]: https://github.com/munich-quantum-toolkit/core/pull/1106 + [#1100]: https://github.com/munich-quantum-toolkit/core/pull/1100 + [#1099]: https://github.com/munich-quantum-toolkit/core/pull/1099 + [#1098]: https://github.com/munich-quantum-toolkit/core/pull/1098 + [#1091]: https://github.com/munich-quantum-toolkit/core/pull/1091 + [#1089]: https://github.com/munich-quantum-toolkit/core/pull/1089 + [#1088]: https://github.com/munich-quantum-toolkit/core/pull/1088 + [#1076]: https://github.com/munich-quantum-toolkit/core/pull/1076 + [#1075]: https://github.com/munich-quantum-toolkit/core/pull/1075 + [#1071]: https://github.com/munich-quantum-toolkit/core/pull/1071 + [#1047]: https://github.com/munich-quantum-toolkit/core/pull/1047 + [#1042]: https://github.com/munich-quantum-toolkit/core/pull/1042 + [#1020]: https://github.com/munich-quantum-toolkit/core/pull/1020 + [#1019]: https://github.com/munich-quantum-toolkit/core/pull/1019 + [#1010]: https://github.com/munich-quantum-toolkit/core/pull/1010 + [#1001]: https://github.com/munich-quantum-toolkit/core/pull/1001 + [#996]: https://github.com/munich-quantum-toolkit/core/pull/996 + [#984]: https://github.com/munich-quantum-toolkit/core/pull/984 + [#982]: https://github.com/munich-quantum-toolkit/core/pull/982 + [#975]: https://github.com/munich-quantum-toolkit/core/pull/975 + [#973]: https://github.com/munich-quantum-toolkit/core/pull/973 + [#964]: https://github.com/munich-quantum-toolkit/core/pull/964 + [#959]: https://github.com/munich-quantum-toolkit/core/pull/959 + [#934]: https://github.com/munich-quantum-toolkit/core/pull/934 + [#933]: https://github.com/munich-quantum-toolkit/core/pull/933 + [#932]: https://github.com/munich-quantum-toolkit/core/pull/932 + [#931]: https://github.com/munich-quantum-toolkit/core/pull/931 + [#930]: https://github.com/munich-quantum-toolkit/core/pull/930 + [#926]: https://github.com/munich-quantum-toolkit/core/pull/926 + [#921]: https://github.com/munich-quantum-toolkit/core/pull/921 + [#913]: https://github.com/munich-quantum-toolkit/core/pull/913 + [#912]: https://github.com/munich-quantum-toolkit/core/pull/912 + [#911]: https://github.com/munich-quantum-toolkit/core/pull/911 + [#908]: https://github.com/munich-quantum-toolkit/core/pull/908 + [#900]: https://github.com/munich-quantum-toolkit/core/pull/900 + [#897]: https://github.com/munich-quantum-toolkit/core/pull/897 + [#895]: https://github.com/munich-quantum-toolkit/core/pull/895 + [#893]: https://github.com/munich-quantum-toolkit/core/pull/893 + [#892]: https://github.com/munich-quantum-toolkit/core/pull/892 + [#886]: https://github.com/munich-quantum-toolkit/core/pull/886 + [#885]: https://github.com/munich-quantum-toolkit/core/pull/885 + [#883]: https://github.com/munich-quantum-toolkit/core/pull/883 + [#882]: https://github.com/munich-quantum-toolkit/core/pull/882 + [#879]: https://github.com/munich-quantum-toolkit/core/pull/879 + [#878]: https://github.com/munich-quantum-toolkit/core/pull/878 + [#877]: https://github.com/munich-quantum-toolkit/core/pull/877 + [#866]: https://github.com/munich-quantum-toolkit/core/pull/866 + [#860]: https://github.com/munich-quantum-toolkit/core/pull/860 + [#859]: https://github.com/munich-quantum-toolkit/core/pull/859 + [#858]: https://github.com/munich-quantum-toolkit/core/pull/858 + [#849]: https://github.com/munich-quantum-toolkit/core/pull/849 + [#847]: https://github.com/munich-quantum-toolkit/core/pull/847 + [#846]: https://github.com/munich-quantum-toolkit/core/pull/846 + [#842]: https://github.com/munich-quantum-toolkit/core/pull/842 + [#839]: https://github.com/munich-quantum-toolkit/core/pull/839 + [#838]: https://github.com/munich-quantum-toolkit/core/pull/838 + [#832]: https://github.com/munich-quantum-toolkit/core/pull/832 + [#831]: https://github.com/munich-quantum-toolkit/core/pull/831 + [#822]: https://github.com/munich-quantum-toolkit/core/pull/822 + [#817]: https://github.com/munich-quantum-toolkit/core/pull/817 + [#810]: https://github.com/munich-quantum-toolkit/core/pull/810 + [#807]: https://github.com/munich-quantum-toolkit/core/pull/807 + [#802]: https://github.com/munich-quantum-toolkit/core/pull/802 + [#798]: https://github.com/munich-quantum-toolkit/core/pull/798 + [#789]: https://github.com/munich-quantum-toolkit/core/pull/789 + [#763]: https://github.com/munich-quantum-toolkit/core/pull/763 + [#762]: https://github.com/munich-quantum-toolkit/core/pull/762 + [#758]: https://github.com/munich-quantum-toolkit/core/pull/758 + [#741]: https://github.com/munich-quantum-toolkit/core/pull/741 + [#724]: https://github.com/munich-quantum-toolkit/core/pull/724 + [#662]: https://github.com/munich-quantum-toolkit/core/pull/662 + [#543]: https://github.com/munich-quantum-toolkit/core/pull/543 + [**@a9b7e70**]: https://github.com/munich-quantum-toolkit/core/pull/798/commits/a9b7e70aaeb532fe8e1e31a7decca86d81eb523f [**@burgholzer**]: https://github.com/burgholzer + [**@ystade**]: https://github.com/ystade + [**@DRovara**]: https://github.com/DRovara + [**@flowerthrower**]: https://github.com/flowerthrower + [**@BertiFlorea**]: https://github.com/BertiFlorea + [**@M-J-Hochreiter**]: https://github.com/M-J-Hochreiter + [**@rotmanjanez**]: https://github.com/rotmanjanez + [**@pehamTom**]: https://github.com/pehamTom + [**@MatthiasReumann**]: https://github.com/MatthiasReumann + [**@denialhaag**]: https://github.com/denialhaag + [**q-inho**]: https://github.com/q-inho + [**@li-mingbao**]: https://github.com/li-mingbao + [**@lavanya-m-k**]: https://github.com/lavanya-m-k + [**@taminob**]: https://github.com/taminob + [**@lsschmid**]: https://github.com/lsschmid + [**@marcelwa**]: https://github.com/marcelwa + [**@lirem101**]: https://github.com/lirem101 + [**@Ectras**]: https://github.com/Ectras + [**@simon1hofmann**]: https://github.com/simon1hofmann + [**@keefehuang**]: https://github.com/keefehuang + [**@J4MMlE**]: https://github.com/J4MMlE + [**@rturrado**]: https://github.com/rturrado [Keep a Changelog]: https://keepachangelog.com/en/1.1.0/ + [Common Changelog]: https://common-changelog.org + [QDMI-on-IQM]: https://github.com/iqm-finland/QDMI-on-IQM + [Semantic Versioning]: https://semver.org/spec/v2.0.0.html + [munich-quantum-toolkit]: https://github.com/munich-quantum-toolkit + [PEP 639]: https://peps.python.org/pep-0639/ + [PEP 735]: https://peps.python.org/pep-0735/ + [CMake presets]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html + [munich-quantum-toolkit/workflows]: https://github.com/munich-quantum-toolkit/workflows From 26ac211266f64fe015396a93321c12140bd4e32c Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 21:02:43 +0200 Subject: [PATCH 39/55] :pencil2: Fixed typos --- .../Optimizations/ConstantPropagation/HybridState.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index eaf3767c56..0c70826983 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -151,9 +151,9 @@ class HybridState { * @brief Combines this subsystem with a disjoint one into a single * HybridState. * - * The qubit sets must be disjointed. Probabilities and global phases - * multiply; classical maps merge (other wins on a key collision). Becomes top - * if the tensor product exceeds the amplitude budget. + * The qubit sets must be disjoint. Probabilities and global phases multiply; + * classical maps merge (other wins on a key collision). Becomes top if the + * tensor product exceeds the amplitude budget. * * @param other The Hybrid state to merge this HybridState with. */ @@ -304,9 +304,9 @@ class HybridState { // Queries //===--------------------------------------------------------------------===// - [[nodiscard("HybridState::isAlwaysZero called but ignored")]] bool + [[nodiscard("HybridState::isQubitAlwaysZero called but ignored")]] bool isQubitAlwaysZero(Value q) const; - [[nodiscard("HybridState::isAlwaysOne called but ignored")]] bool + [[nodiscard("HybridState::isQubitAlwaysOne called but ignored")]] bool isQubitAlwaysOne(Value q) const; /// @brief Whether v is a known non-zero classical constant in this branch. From c4654a5452bae9e555b1fd1e2bae68c2d27dae4d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 21:14:55 +0200 Subject: [PATCH 40/55] :construction: Handling unknown op's by putting to top, assisted by Sonnet 5 via Claude Code --- .../ConstantPropagationAnalysis.cpp | 49 +++++++++++++++---- .../ConstantPropagationAnalysis.hpp | 9 ++-- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp index f90e77d13f..8433db39f7 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp @@ -72,6 +72,33 @@ static void ensureSeeded(UnionTable& table, Operation* const op) { } } +/** + * Conservative fallback for an operation whose quantum effect the analysis does + * not model - a valid but unsupported construct (`scf.for`, `qco.index_switch`, + * ...) or any unrecognized op that touches qubits. + */ +static LogicalResult applyUnmodelledOp(UnionTable& table, Operation* const op) { + const auto isQubit = [](Value v) { return isa(v.getType()); }; + + SmallVector qubitOperands; + for (Value operand : op->getOperands()) { + if (isQubit(operand)) { + qubitOperands.push_back(operand); + } + } + SmallVector qubitResults; + for (Value result : op->getResults()) { + if (isQubit(result)) { + qubitResults.push_back(result); + } + } + + // Thread each qubit through operand -> result. zip() pairs the common prefix + table.forwardValues(qubitOperands, qubitResults); + table.markQubitsTop(qubitResults); + return success(); +} + //===----------------------------------------------------------------------===// // UnionTableLattice //===----------------------------------------------------------------------===// @@ -168,10 +195,12 @@ LogicalResult ConstantPropagationAnalysis::visitOperation( UnionTable table = before.getUnionTable(); if (failed(applyOperation(table, op, /*quantumControls=*/{}))) { + // Valid unsupported ops are absorbed conservatively by applyOperation, so a + // failure here means a UnionTable invariant broke - an internal bug, not a + // property of the input. return op->emitError() - << "constant propagation cannot interpret '" << op->getName() - << "' (unsupported operation, or a propagation bug left the state " - "inconsistent)"; + << "constant propagation left the abstract state inconsistent at '" + << op->getName() << "' (internal error)"; } propagateIfChanged(after, after->setUnionTable(std::move(table))); return success(); @@ -221,11 +250,12 @@ LogicalResult ConstantPropagationAnalysis::applyOperation( .Case([&](const CtrlOp ctrl) { return applyCtrl(table, ctrl, quantumControls); }) - .Case([](Operation*) { - // Region-branch ops are routed by the framework + .Case([&](Operation* branch) { + // Normally routed by the framework // (visitRegionBranchControlFlowTransfer); reaching one here means it is - // nested in a modifier body, which the QCO verifier forbids. - return failure(); + // nested in a qco.ctrl / qco.inv / qco.pow body, which the analysis does + // not interpret - fall back to the conservative top. + return applyUnmodelledOp(table, branch); }) .Case([&](UnitaryOpInterface gate) { // Every remaining unitary: base gates, and qco.inv / qco.pow bodies @@ -235,11 +265,12 @@ LogicalResult ConstantPropagationAnalysis::applyOperation( }) .Default([&](Operation* other) -> LogicalResult { // Not a QCO operation. Anything clear of qubits is a classical op to - // fold; an unrecognized qubit-touching op is unsupported. + // fold; an unrecognized qubit-touching op (e.g. scf.for) is not + // modelled, so its qubits collapse to top rather than failing the pass. const auto isQubit = [](const Type t) { return isa(t); }; if (llvm::any_of(other->getOperandTypes(), isQubit) || llvm::any_of(other->getResultTypes(), isQubit)) { - return failure(); + return applyUnmodelledOp(table, other); } table.propagateClassical(other); return success(); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp index 8a9ba50176..4722a68e20 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.hpp @@ -80,9 +80,12 @@ class UnionTableLattice : public dataflow::AbstractDenseLattice { * folds, control modifiers, and constant/branching `qco.if`. * * Unsupported constructs (`scf.for`, `qco.index_switch`, and any operation - * touching qubits that the analysis cannot model) make the pass fail via - * `emitError`. Precision losses (parametric gates, `qco.inv` / `qco.pow` - * bodies, non-constant `qco.if`) collapse the affected qubits to top instead. + * touching qubits that the analysis cannot model) are handled conservatively: + * the qubits they consume or produce collapse to top, everything else keeps its + * state. Precision losses (parametric gates, `qco.inv` / `qco.pow` bodies, + * non-constant `qco.if`) collapse the affected qubits the same way. The pass + * only fails (via `emitError`) if a `UnionTable` invariant breaks - an internal + * bug rather than a property of the input. * * Does not call across a boundary: if the module contains any call, the * analysis reports top everywhere. Uncalled helper functions are tolerated - From f4fda709978a533fc98944c80e655407ba11d986 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 21:20:48 +0200 Subject: [PATCH 41/55] :construction: Fixed coderabbit comments on HybridState, assisted by Sonnet 5 via Claude Code --- .../ConstantPropagation/HybridState.cpp | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 362469bac3..e12c225ab8 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -33,13 +33,6 @@ namespace mlir::qco { -/// @brief Whether ctrlsOut is a valid rename target for ctrlsIn: empty (no -/// rename), or the same length. -static bool ctrlRenameOk(const ArrayRef ctrlsIn, - const ArrayRef ctrlsOut) { - return ctrlsOut.empty() || ctrlsOut.size() == ctrlsIn.size(); -} - /// @brief Truthiness of a resolved classical constant (non-zero == true), or /// nullopt if attr is not an integer/index/bool/float constant. static std::optional classicalTruth(const Attribute attr) { @@ -56,12 +49,15 @@ static std::optional classicalTruth(const Attribute attr) { /// not an integer/index/bool/float constant. static std::optional classicalDouble(const Attribute attr) { if (const auto ia = dyn_cast_if_present(attr)) { - return static_cast(ia.getValue().getSExtValue()); + if (const std::optional v = ia.getValue().trySExtValue()) { + return static_cast(*v); + } + return {}; } if (const auto fa = dyn_cast_if_present(attr)) { return fa.getValueAsDouble(); } - return std::nullopt; + return {}; } //===----------------------------------------------------------------------===// @@ -166,7 +162,7 @@ HybridState::applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, const ArrayRef quantumCtrlsOut, const ArrayRef posClassicalCtrls, const ArrayRef negClassicalCtrls) { - if (!ctrlRenameOk(quantumCtrlsIn, quantumCtrlsOut) || !state.contains(in)) { + if (quantumCtrlsIn.size() != quantumCtrlsOut.size() || !state.contains(in)) { return failure(); } const auto hold = classicalControlsHold(posClassicalCtrls, negClassicalCtrls); @@ -188,7 +184,7 @@ LogicalResult HybridState::applyMatrix2Q( const ArrayRef quantumCtrlsIn, const ArrayRef quantumCtrlsOut, const ArrayRef posClassicalCtrls, const ArrayRef negClassicalCtrls) { - if (!ctrlRenameOk(quantumCtrlsIn, quantumCtrlsOut) || !state.contains(in0) || + if (quantumCtrlsIn.size() != quantumCtrlsOut.size() || !state.contains(in0) || !state.contains(in1)) { return failure(); } @@ -211,18 +207,18 @@ HybridState::addGlobalPhase(Value theta, const ArrayRef quantumCtrlsIn, const ArrayRef quantumCtrlsOut, const ArrayRef posClassicalCtrls, const ArrayRef negClassicalCtrls) { - if (!ctrlRenameOk(quantumCtrlsIn, quantumCtrlsOut)) { + if (quantumCtrlsIn.size() != quantumCtrlsOut.size()) { return failure(); } const auto hold = classicalControlsHold(posClassicalCtrls, negClassicalCtrls); if (failed(hold)) { return failure(); } - const auto angle = classicalDouble(classical.lookup(theta)); - if (!angle) { - return failure(); - } if (*hold) { + const auto angle = classicalDouble(classical.lookup(theta)); + if (!angle) { + return failure(); + } if (!quantumCtrlsIn.empty()) { return state.applyControlledPhase(*angle, quantumCtrlsIn, quantumCtrlsOut); @@ -405,8 +401,17 @@ void HybridState::print(raw_ostream& os) const { os << "]"; if (!classical.empty()) { os << " classical:"; + SmallVector entries; + entries.reserve(classical.size()); for (const auto& [v, attr] : classical) { - os << " " << attr; + std::string entry; + llvm::raw_string_ostream entryOs(entry); + entryOs << v << "=" << attr; + entries.push_back(std::move(entry)); + } + llvm::sort(entries); + for (const auto& entry : entries) { + os << " " << entry; } } } From d44c29b714c5b06aa9e87697e560f67cc759d84a Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 21:22:51 +0200 Subject: [PATCH 42/55] :memo: Fixed comments --- .../Optimizations/ConstantPropagation/QuantumState.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index e173e3d3d8..02f8412009 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -46,8 +46,8 @@ struct MeasurementOutcome { * managed vector. * * If the number of non-zero amplitudes exceeds the threshold - * maxNonzeroAmplitudes, or the group holds more than qubits than unsigned - * int has bits, the state collapses to top. + * maxNonzeroAmplitudes, or the group holds more than 63 qubits (the basis + * index is a uint64_t), the state collapses to top. */ class QuantumState { bool top = false; @@ -234,7 +234,7 @@ class QuantumState { * group are ignored. Returns true when no non-zero amplitude matches all * the (in-group) pairs simultaneously. */ - [[nodiscard("QuantumState::hasZeroAmplitude called but ignored")]] bool + [[nodiscard("QuantumState::hasAlwaysZeroAmplitude called but ignored")]] bool hasAlwaysZeroAmplitude(ArrayRef> basis) const; [[nodiscard("QuantumState::== called but ignored")]] bool From 9153099670a3e780a56c019d509c9d73ab831992 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 21:26:48 +0200 Subject: [PATCH 43/55] :memo: Fixed comments --- .../Optimizations/ConstantPropagation/UnionTable.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index c8a1a2733e..f42edfbe1f 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -55,9 +55,9 @@ struct SuperfluousResult { * gates to matrices and target/output SSA values. Before a multi-qubit or * controlled operation the touched slots are merged into one (alternatives * multiply out via HybridState::tensor); if that exceeds maxHybridStates the - * whole table collapses to allTop. A target or control value absent from the - * table is a caller/propagation bug and yields failure(); the analysis seeds - * every qubit before first use. + * merged slots collapse to a single top state. A target or control value absent + * from the table is a caller/propagation bug and yields failure(); the analysis + * seeds every qubit before first use. */ class UnionTable { public: From f394ecac84aab26831ec5bf7da1ccf73b9b9e6c8 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 21:29:57 +0200 Subject: [PATCH 44/55] :white_check_mark: Assert cast before checking value --- .../Optimizations/ConstantPropagation/test_hybridState.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp index 28ce5a17b4..f15799b9d7 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -266,7 +266,9 @@ TEST_F(HybridStateTest, propagateClassicalFoldsConstants) { hs.propagateClassical(add.getOperation()); const auto folded = hs.getClassical(add.getResult()); ASSERT_TRUE(folded.has_value()); - EXPECT_EQ(dyn_cast(*folded).getInt(), 7); + const auto intAttr = dyn_cast(*folded); + ASSERT_TRUE(intAttr); + EXPECT_EQ(intAttr.getInt(), 7); } //===----------------------------------------------------------------------===// From 5ca396c9ca3ef001049f6db1183775262f634bec Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 21:32:19 +0200 Subject: [PATCH 45/55] :white_check_mark: Add ctrl output qubit --- .../Optimizations/ConstantPropagation/test_quantumState.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 8048e789b6..bc002626b0 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -181,7 +181,8 @@ TEST_F(QuantumStateTest, applyTwoQubitGateToSameBitFails) { TEST_F(QuantumStateTest, applyWithControlNotInGroupFails) { auto qs = QuantumState({q[0], q[1]}, 4); EXPECT_TRUE( - qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {q[2]}).failed()); + qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {q[2]}, {q[2]}) + .failed()); } TEST_F(QuantumStateTest, applyToQubitNotInGroupFailsEvenWhenTop) { @@ -503,7 +504,7 @@ TEST_F(QuantumStateTest, printRendersImaginaryAmplitudes) { TEST_F(QuantumStateTest, twoQubitGateWithControlNotInGroupFails) { auto qs = QuantumState({q[0], q[1]}, 4); EXPECT_TRUE(qs.applyMatrix2Q(q[0], q[1], q[0], q[1], - swapOp.getUnitaryMatrix(), {q[2]}) + swapOp.getUnitaryMatrix(), {q[2]}, {q[2]}) .failed()); } From 0584479fe6b3d40f7e47ef346c81aca6d6dd69b9 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 21:41:49 +0200 Subject: [PATCH 46/55] :white_check_mark: Removed const from value parameters, assisted by Sonnet 5 via Claude Code --- .../ConstantPropagationAnalysis.cpp | 22 ++++---- .../ConstantPropagation/HybridState.cpp | 46 ++++++++-------- .../ConstantPropagation/HybridState.hpp | 8 +-- .../ConstantPropagation/QuantumState.cpp | 34 ++++++------ .../ConstantPropagation/QuantumState.hpp | 2 +- .../ConstantPropagation/Rewriter.cpp | 4 +- .../ConstantPropagation/UnionTable.cpp | 54 +++++++++---------- .../ConstantPropagation/UnionTable.hpp | 2 +- .../test_constantPropagationAnalysis.cpp | 4 +- .../ConstantPropagation/test_hybridState.cpp | 5 +- .../ConstantPropagation/test_unionTable.cpp | 5 +- .../test_qco_constant_propagation.cpp | 4 +- 12 files changed, 95 insertions(+), 95 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp index 8433db39f7..82e5d6deb5 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp @@ -60,7 +60,7 @@ static bool isEntryPointQubitArgument(Value v) { /// @brief Ensures every qubit operand of op is tracked before use: an /// entry-point argument starts in |0>, anything else of unknown provenance /// collapses to top. -static void ensureSeeded(UnionTable& table, Operation* const op) { +static void ensureSeeded(UnionTable& table, Operation* op) { for (Value operand : op->getOperands()) { if (!isa(operand.getType()) || table.isTracked(operand)) { continue; @@ -77,7 +77,7 @@ static void ensureSeeded(UnionTable& table, Operation* const op) { * not model - a valid but unsupported construct (`scf.for`, `qco.index_switch`, * ...) or any unrecognized op that touches qubits. */ -static LogicalResult applyUnmodelledOp(UnionTable& table, Operation* const op) { +static LogicalResult applyUnmodelledOp(UnionTable& table, Operation* op) { const auto isQubit = [](Value v) { return isa(v.getType()); }; SmallVector qubitOperands; @@ -142,8 +142,8 @@ void UnionTableLattice::print(raw_ostream& os) const { //===----------------------------------------------------------------------===// ConstantPropagationAnalysis::ConstantPropagationAnalysis( - DataFlowSolver& solver, const size_t maxNonzeroAmplitudes, - const size_t maxHybridStates) + DataFlowSolver& solver, size_t maxNonzeroAmplitudes, + size_t maxHybridStates) : DenseForwardDataFlowAnalysis(solver), maxNonzeroAmplitudes(maxNonzeroAmplitudes), maxHybridStates(maxHybridStates) {} @@ -207,7 +207,7 @@ LogicalResult ConstantPropagationAnalysis::visitOperation( } LogicalResult ConstantPropagationAnalysis::applyOperation( - UnionTable& table, Operation* op, const ArrayRef quantumControls) { + UnionTable& table, Operation* op, ArrayRef quantumControls) { ensureSeeded(table, op); return TypeSwitch(op) @@ -247,7 +247,7 @@ LogicalResult ConstantPropagationAnalysis::applyOperation( return table.addGlobalPhase(gphase.getTheta(), quantumControls, quantumControls); }) - .Case([&](const CtrlOp ctrl) { + .Case([&](CtrlOp ctrl) { return applyCtrl(table, ctrl, quantumControls); }) .Case([&](Operation* branch) { @@ -267,7 +267,7 @@ LogicalResult ConstantPropagationAnalysis::applyOperation( // Not a QCO operation. Anything clear of qubits is a classical op to // fold; an unrecognized qubit-touching op (e.g. scf.for) is not // modelled, so its qubits collapse to top rather than failing the pass. - const auto isQubit = [](const Type t) { return isa(t); }; + const auto isQubit = [](Type t) { return isa(t); }; if (llvm::any_of(other->getOperandTypes(), isQubit) || llvm::any_of(other->getResultTypes(), isQubit)) { return applyUnmodelledOp(table, other); @@ -279,7 +279,7 @@ LogicalResult ConstantPropagationAnalysis::applyOperation( LogicalResult ConstantPropagationAnalysis::applyUnitary( UnionTable& table, UnitaryOpInterface gate, - const ArrayRef quantumControls) { + ArrayRef quantumControls) { const auto targetsIn = toVec(gate.getInputTargets()); const auto targetsOut = toVec(gate.getOutputTargets()); @@ -303,7 +303,7 @@ LogicalResult ConstantPropagationAnalysis::applyUnitary( LogicalResult ConstantPropagationAnalysis::applyCtrl(UnionTable& table, CtrlOp ctrl, - const ArrayRef quantumControls) { + ArrayRef quantumControls) { Block& body = ctrl.getRegion().front(); table.forwardValues(toVec(ctrl.getInputTargets()), @@ -327,8 +327,8 @@ ConstantPropagationAnalysis::applyCtrl(UnionTable& table, CtrlOp ctrl, } void ConstantPropagationAnalysis::visitRegionBranchControlFlowTransfer( - RegionBranchOpInterface branch, const std::optional regionFrom, - const std::optional regionTo, const UnionTableLattice& before, + RegionBranchOpInterface branch, std::optional regionFrom, + std::optional regionTo, const UnionTableLattice& before, UnionTableLattice* after) { // nullopt = the parent op; a value = the index of one of `branch`'s regions. auto ifOp = dyn_cast(branch.getOperation()); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index e12c225ab8..61f570a548 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -35,7 +35,7 @@ namespace mlir::qco { /// @brief Truthiness of a resolved classical constant (non-zero == true), or /// nullopt if attr is not an integer/index/bool/float constant. -static std::optional classicalTruth(const Attribute attr) { +static std::optional classicalTruth(Attribute attr) { if (const auto ia = dyn_cast_if_present(attr)) { return !ia.getValue().isZero(); } @@ -47,7 +47,7 @@ static std::optional classicalTruth(const Attribute attr) { /// @brief Numeric value of a resolved classical constant, or nullopt if attr is /// not an integer/index/bool/float constant. -static std::optional classicalDouble(const Attribute attr) { +static std::optional classicalDouble(Attribute attr) { if (const auto ia = dyn_cast_if_present(attr)) { if (const std::optional v = ia.getValue().trySExtValue()) { return static_cast(*v); @@ -76,7 +76,7 @@ std::optional HybridState::getClassical(Value v) const { // Mutation //===----------------------------------------------------------------------===// -void HybridState::setClassical(Value v, const Attribute attr) { +void HybridState::setClassical(Value v, Attribute attr) { classical[v] = attr; } @@ -121,8 +121,8 @@ HybridState HybridState::tensor(const HybridState& other) const { //===----------------------------------------------------------------------===// FailureOr -HybridState::classicalControlsHold(const ArrayRef pos, - const ArrayRef neg) const { +HybridState::classicalControlsHold(ArrayRef pos, + ArrayRef neg) const { for (Value p : pos) { const auto attr = getClassical(p); if (!attr) { @@ -158,10 +158,10 @@ HybridState::classicalControlsHold(const ArrayRef pos, LogicalResult HybridState::applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, - const ArrayRef quantumCtrlsIn, - const ArrayRef quantumCtrlsOut, - const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) { + ArrayRef quantumCtrlsIn, + ArrayRef quantumCtrlsOut, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (quantumCtrlsIn.size() != quantumCtrlsOut.size() || !state.contains(in)) { return failure(); } @@ -181,9 +181,9 @@ HybridState::applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, LogicalResult HybridState::applyMatrix2Q( Value in0, Value in1, Value out0, Value out1, const Matrix4x4& matrix, - const ArrayRef quantumCtrlsIn, const ArrayRef quantumCtrlsOut, - const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) { + ArrayRef quantumCtrlsIn, ArrayRef quantumCtrlsOut, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (quantumCtrlsIn.size() != quantumCtrlsOut.size() || !state.contains(in0) || !state.contains(in1)) { return failure(); @@ -203,10 +203,10 @@ LogicalResult HybridState::applyMatrix2Q( } LogicalResult -HybridState::addGlobalPhase(Value theta, const ArrayRef quantumCtrlsIn, - const ArrayRef quantumCtrlsOut, - const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) { +HybridState::addGlobalPhase(Value theta, ArrayRef quantumCtrlsIn, + ArrayRef quantumCtrlsOut, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (quantumCtrlsIn.size() != quantumCtrlsOut.size()) { return failure(); } @@ -230,7 +230,7 @@ HybridState::addGlobalPhase(Value theta, const ArrayRef quantumCtrlsIn, return success(); } -void HybridState::propagateClassical(Operation* const op) { +void HybridState::propagateClassical(Operation* op) { SmallVector operands; operands.reserve(op->getNumOperands()); for (Value operand : op->getOperands()) { @@ -254,8 +254,8 @@ void HybridState::propagateClassical(Operation* const op) { LogicalResult HybridState::measureQubit(Value in, Value out, Value classicalResult, - const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) { + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (!state.contains(in)) { return failure(); } @@ -288,8 +288,8 @@ HybridState::measureQubit(Value in, Value out, Value classicalResult, } LogicalResult HybridState::resetQubit(Value in, Value out, - const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) { + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (!state.contains(in)) { return failure(); } @@ -345,8 +345,8 @@ bool HybridState::isClassicalFalse(Value v) const { } bool HybridState::areControlsSatisfiable( - const ArrayRef quantumCtrls, const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) const { + ArrayRef quantumCtrls, ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) const { for (Value pc : posClassicalCtrls) { if (isClassicalFalse(pc)) { return false; diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index 0c70826983..33fa034ec6 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -63,8 +63,8 @@ class HybridState { * @param maxNonzeroAmplitudes Budget for QuantumStates created here (reset). * @param probability This alternative's weight within its slot (1 if sole). */ - HybridState(QuantumState state, const size_t maxNonzeroAmplitudes, - const double probability) + HybridState(QuantumState state, size_t maxNonzeroAmplitudes, + double probability) : maxNonzeroAmplitudes(maxNonzeroAmplitudes), probability(probability), state(std::move(state)) {} @@ -123,14 +123,14 @@ class HybridState { * * @param factor The factor to multiply the probability with. */ - void scaleProbability(const double factor) { probability *= factor; } + void scaleProbability(double factor) { probability *= factor; } /** * @brief Sets this branch's probability (its weight within its slot). * * @param newProbability The new probability. */ - void setProbability(const double newProbability) { + void setProbability(double newProbability) { probability = newProbability; } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 15b25f59b2..fdf85bf53b 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -37,8 +37,8 @@ namespace { constexpr unsigned MAX_GROUP_QUBITS = 63; } // namespace -QuantumState::QuantumState(const ArrayRef qubits, - const size_t maxNonzeroAmplitudes) +QuantumState::QuantumState(ArrayRef qubits, + size_t maxNonzeroAmplitudes) : maxNonzeroAmplitudes(maxNonzeroAmplitudes), qubits(qubits.begin(), qubits.end()) { if (qubits.size() > MAX_GROUP_QUBITS) { @@ -49,7 +49,7 @@ QuantumState::QuantumState(const ArrayRef qubits, } QuantumState QuantumState::singletonZero(Value qubit, - const size_t maxNonzeroAmplitudes) { + size_t maxNonzeroAmplitudes) { return {ArrayRef(qubit), maxNonzeroAmplitudes}; } @@ -62,7 +62,7 @@ std::optional QuantumState::indexOf(Value q) const { return std::nullopt; } -uint64_t QuantumState::maskOf(const ArrayRef values) const { +uint64_t QuantumState::maskOf(ArrayRef values) const { uint64_t mask = 0; for (Value v : values) { if (const auto idx = indexOf(v)) { @@ -83,8 +83,8 @@ void QuantumState::forwardQubit(Value from, Value to) { } } -void QuantumState::forwardQubits(const ArrayRef from, - const ArrayRef to) { +void QuantumState::forwardQubits(ArrayRef from, + ArrayRef to) { for (const auto [f, t] : llvm::zip(from, to)) { forwardQubit(f, t); } @@ -110,8 +110,8 @@ void QuantumState::canonicalize() { LogicalResult QuantumState::applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, - const ArrayRef ctrlsIn, - const ArrayRef ctrlsOut) { + ArrayRef ctrlsIn, + ArrayRef ctrlsOut) { const auto idx = indexOf(in); if (!idx || ctrlsOut.size() != ctrlsIn.size()) { return failure(); @@ -153,8 +153,8 @@ LogicalResult QuantumState::applyMatrix1Q(Value in, Value out, LogicalResult QuantumState::applyMatrix2Q(Value in0, Value in1, Value out0, Value out1, const Matrix4x4& matrix, - const ArrayRef ctrlsIn, - const ArrayRef ctrlsOut) { + ArrayRef ctrlsIn, + ArrayRef ctrlsOut) { const auto idx0 = indexOf(in0); const auto idx1 = indexOf(in1); if (!idx0 || !idx1 || *idx0 == *idx1 || ctrlsOut.size() != ctrlsIn.size()) { @@ -178,11 +178,11 @@ LogicalResult QuantumState::applyMatrix2Q(Value in0, Value in1, Value out0, const uint64_t bothBits = hiBit | loBit; const uint64_t ctrlMask = maskOf(ctrlsIn); - const auto localKey = [&](const uint64_t base, const unsigned local) { + const auto localKey = [&](uint64_t base, unsigned local) { return base | ((local & 1U) != 0U ? loBit : 0) | ((local & 2U) != 0U ? hiBit : 0); }; - const auto localCol = [&](const uint64_t key) { + const auto localCol = [&](uint64_t key) { return ((key & hiBit) != 0 ? 2U : 0U) | ((key & loBit) != 0 ? 1U : 0U); }; @@ -210,9 +210,9 @@ LogicalResult QuantumState::applyMatrix2Q(Value in0, Value in1, Value out0, } LogicalResult -QuantumState::applyControlledPhase(const double phase, - const ArrayRef ctrlsIn, - const ArrayRef ctrlsOut) { +QuantumState::applyControlledPhase(double phase, + ArrayRef ctrlsIn, + ArrayRef ctrlsOut) { if (ctrlsIn.empty() || (!ctrlsOut.empty() && ctrlsOut.size() != ctrlsIn.size())) { return failure(); @@ -264,7 +264,7 @@ FailureOr> QuantumState::measure(Value in, } } - const auto makeBranch = [&](const unsigned bit, const double probability, + const auto makeBranch = [&](unsigned bit, double probability, const llvm::DenseMap& amps) { auto branch = std::unique_ptr(new QuantumState(maxNonzeroAmplitudes)); @@ -357,7 +357,7 @@ bool QuantumState::isAlwaysOne(Value q) const { } bool QuantumState::hasAlwaysZeroAmplitude( - const ArrayRef> basis) const { + ArrayRef> basis) const { if (top) { return false; } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index 02f8412009..1bb214b78f 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -55,7 +55,7 @@ class QuantumState { SmallVector qubits; llvm::DenseMap amplitudes; - explicit QuantumState(const size_t maxNonzeroAmplitudes) + explicit QuantumState(size_t maxNonzeroAmplitudes) : maxNonzeroAmplitudes(maxNonzeroAmplitudes) {} /// @brief Bitmask of the positions of the given values that are in the group. diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp index 85041a36be..79cfd59743 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp @@ -97,7 +97,7 @@ static void applyDrop(const DropOp& drop, IRRewriter& rewriter) { static void applyStrip(const StripControls& strip, IRRewriter& rewriter) { CtrlOp op = strip.op; const auto controlsIn = op.getInputControls(); - const auto isDropped = [&](const size_t index) { + const auto isDropped = [&](size_t index) { return llvm::is_contained(strip.dropControlIndices, static_cast(index)); }; @@ -146,7 +146,7 @@ static void applyStrip(const StripControls& strip, IRRewriter& rewriter) { rewriter.eraseOp(op); } -void applyDecisions(const ArrayRef decisions, IRRewriter& rewriter) { +void applyDecisions(ArrayRef decisions, IRRewriter& rewriter) { for (const Decision& decision : decisions) { if (const auto* drop = std::get_if(&decision)) { applyDrop(*drop, rewriter); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 942af8f3fb..3177478941 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -61,7 +61,7 @@ std::optional UnionTable::slotIndexContaining(Value v) const { } SmallVector -UnionTable::slotsTouchedBy(const ArrayRef values) const { +UnionTable::slotsTouchedBy(ArrayRef values) const { SmallVector result; for (Value v : values) { if (const auto i = slotIndexContaining(v)) { @@ -82,7 +82,7 @@ HybridState UnionTable::reducedRepresentative(const Slot& slot) { return representative; } -void UnionTable::mergeSlots(const ArrayRef values) { +void UnionTable::mergeSlots(ArrayRef values) { if (allTop) { return; } @@ -204,7 +204,7 @@ void UnionTable::seedQubit(Value qubit) { slots.push_back(std::move(slot)); } -void UnionTable::seedClassical(Value value, const Attribute attr) { +void UnionTable::seedClassical(Value value, Attribute attr) { if (allTop) { return; } @@ -237,8 +237,8 @@ void UnionTable::forwardValue(Value from, Value to) { } } -void UnionTable::forwardValues(const ArrayRef from, - const ArrayRef to) { +void UnionTable::forwardValues(ArrayRef from, + ArrayRef to) { for (const auto [f, t] : llvm::zip(from, to)) { forwardValue(f, t); } @@ -250,10 +250,10 @@ void UnionTable::forwardValues(const ArrayRef from, LogicalResult UnionTable::applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, - const ArrayRef quantumCtrlsIn, - const ArrayRef quantumCtrlsOut, - const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) { + ArrayRef quantumCtrlsIn, + ArrayRef quantumCtrlsOut, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (allTop) { return success(); } @@ -282,9 +282,9 @@ UnionTable::applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, LogicalResult UnionTable::applyMatrix2Q( Value in0, Value in1, Value out0, Value out1, const Matrix4x4& matrix, - const ArrayRef quantumCtrlsIn, const ArrayRef quantumCtrlsOut, - const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) { + ArrayRef quantumCtrlsIn, ArrayRef quantumCtrlsOut, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (allTop) { return success(); } @@ -312,10 +312,10 @@ LogicalResult UnionTable::applyMatrix2Q( } LogicalResult -UnionTable::addGlobalPhase(Value theta, const ArrayRef quantumCtrlsIn, - const ArrayRef quantumCtrlsOut, - const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) { +UnionTable::addGlobalPhase(Value theta, ArrayRef quantumCtrlsIn, + ArrayRef quantumCtrlsOut, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (allTop) { return success(); } @@ -355,7 +355,7 @@ UnionTable::addGlobalPhase(Value theta, const ArrayRef quantumCtrlsIn, return success(); } -void UnionTable::propagateClassical(Operation* const op) { +void UnionTable::propagateClassical(Operation* op) { if (allTop) { return; } @@ -377,8 +377,8 @@ void UnionTable::propagateClassical(Operation* const op) { LogicalResult UnionTable::measureQubit(Value in, Value out, Value classicalResult, - const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) { + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (allTop) { return success(); } @@ -405,8 +405,8 @@ UnionTable::measureQubit(Value in, Value out, Value classicalResult, } LogicalResult UnionTable::resetQubit(Value in, Value out, - const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) { + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (allTop) { return success(); } @@ -430,7 +430,7 @@ LogicalResult UnionTable::resetQubit(Value in, Value out, return success(); } -void UnionTable::markQubitsTop(const ArrayRef qubits) { +void UnionTable::markQubitsTop(ArrayRef qubits) { if (allTop) { return; } @@ -490,8 +490,8 @@ bool UnionTable::isClassicalAlwaysFalse(Value v) const { } bool UnionTable::areControlsSatisfiable( - const ArrayRef quantumCtrls, const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) const { + ArrayRef quantumCtrls, ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) const { if (allTop) { return true; } @@ -540,8 +540,8 @@ bool UnionTable::areControlsSatisfiable( } SuperfluousResult UnionTable::getSuperfluousControls( - const ArrayRef quantumCtrls, const ArrayRef posClassicalCtrls, - const ArrayRef negClassicalCtrls) const { + ArrayRef quantumCtrls, ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) const { SuperfluousResult result; if (!areControlsSatisfiable(quantumCtrls, posClassicalCtrls, negClassicalCtrls)) { @@ -625,7 +625,7 @@ void UnionTable::join(const UnionTable& other) { // A purely classical fact survives only if the other branch asserts the same // one; otherwise it becomes unknown (it is simply dropped). for (const unsigned mi : myClassical) { - const bool inBoth = llvm::any_of(theirClassical, [&](const unsigned ti) { + const bool inBoth = llvm::any_of(theirClassical, [&](unsigned ti) { return llvm::any_of(other.slots[ti], [&](const HybridState& theirHs) { return slots[mi].front().sameConfiguration(theirHs); }); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index f42edfbe1f..e107c2f92a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -115,7 +115,7 @@ class UnionTable { * @param maxHybridStates Per-slot alternative budget before the whole slot * collapses to allTop. */ - UnionTable(const size_t maxNonzeroAmplitudes, const size_t maxHybridStates) + UnionTable(size_t maxNonzeroAmplitudes, size_t maxHybridStates) : maxNonzeroAmplitudes(maxNonzeroAmplitudes), maxHybridStates(maxHybridStates) {} diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp index 904e2ef9e0..c090764334 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp @@ -39,8 +39,8 @@ using namespace mlir::qco; /// Runs the analysis over module and returns a " -> " line /// for every operation, in walk order. -static std::string analyze(ModuleOp module, const size_t maxAmplitudes = 16, - const size_t maxHybridStates = 8) { +static std::string analyze(ModuleOp module, size_t maxAmplitudes = 16, + size_t maxHybridStates = 8) { DataFlowSolver solver; solver.load(); solver.load(); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp index f15799b9d7..d12454cf56 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -76,8 +76,7 @@ class HybridStateTest : public testing::Test { dcxOp = DCXOp::create(builder, builder.getLoc(), qt, qt, q[0], q[1]); } - static HybridState make(const ArrayRef qubits, - const double probability = 1.0) { + static HybridState make(ArrayRef qubits, double probability = 1.0) { return HybridState(QuantumState(qubits, BUDGET), BUDGET, probability); } }; @@ -237,7 +236,7 @@ TEST_F(HybridStateTest, quantumControlledPhaseIsNotGlobal) { Value theta = builder.floatConstant(std::acos(-1.0)); hs.setClassical(theta, builder.getF64FloatAttr(std::acos(-1.0))); ASSERT_TRUE(hs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix()).succeeded()); - ASSERT_TRUE(hs.addGlobalPhase(theta, {q[0]}).succeeded()); + ASSERT_TRUE(hs.addGlobalPhase(theta, {q[0]}, {q[0]}).succeeded()); EXPECT_LT(std::abs(hs.getGlobalPhase() - Complex{1.0, 0.0}), 1e-9); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index ba8129a48e..0222cecbad 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -68,8 +68,8 @@ class UnionTableTest : public testing::Test { dcxOp = DCXOp::create(builder, builder.getLoc(), qt, qt, q[0], q[1]); } - static UnionTable make(const size_t maxAmplitudes = 16, - const size_t maxHybridStates = 8) { + static UnionTable make(size_t maxAmplitudes = 16, + size_t maxHybridStates = 8) { return {maxAmplitudes, maxHybridStates}; } }; @@ -240,6 +240,7 @@ TEST_F(UnionTableTest, propagateClassicalFoldsAcrossSlots) { ut.propagateClassical(add.getOperation()); EXPECT_TRUE(ut.isTracked(add.getResult())); EXPECT_FALSE(ut.isClassicalAlwaysFalse(add.getResult())); + EXPECT_NE(printed(ut).find('7'), std::string::npos); } //===----------------------------------------------------------------------===// diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index aef515e195..32ea41a0e9 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -70,8 +70,8 @@ class ConstantPropagationTest : public testing::Test { builder.initialize(); } - static LogicalResult run(ModuleOp module, const std::size_t maxAmplitudes = 4, - const std::size_t maxHybridStates = 4) { + static LogicalResult run(ModuleOp module, std::size_t maxAmplitudes = 4, + std::size_t maxHybridStates = 4) { PassManager pm(module.getContext()); pm.addPass(createConstantPropagation( ConstantPropagationOptions{.maximumNonzeroAmplitudes = maxAmplitudes, From b404cc5b87eae5d34f592831171730a3ccd88036 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 22:06:22 +0200 Subject: [PATCH 47/55] :white_check_mark: Made tests more specific, assisted by Sonnet 5 via Claude Code --- .../test_constantPropagationAnalysis.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp index c090764334..9dd7507c25 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_constantPropagationAnalysis.cpp @@ -229,6 +229,7 @@ TEST_F(ConstantPropagationAnalysisTest, const std::string dump = analyze(*module); EXPECT_EQ(dump.find(""), std::string::npos); EXPECT_NE(dump.find("qco.if -> "), std::string::npos); + EXPECT_NE(dump.find("p=1.0000 [|1> -> 1.00]"), std::string::npos); } TEST_F(ConstantPropagationAnalysisTest, classicalArithmeticIsFolded) { @@ -247,7 +248,12 @@ TEST_F(ConstantPropagationAnalysisTest, classicalArithmeticIsFolded) { const std::string dump = analyze(*module); EXPECT_EQ(dump.find(""), std::string::npos); + // `addi 2, 3` folds to a classical constant 5, recorded in the lattice as a + // resolved value; the state stays precise - not top, not empty. EXPECT_NE(dump.find("arith.addi -> "), std::string::npos); + EXPECT_NE(dump.find("=5 : i64"), std::string::npos); + EXPECT_EQ(dump.find("arith.addi -> "), std::string::npos); + EXPECT_EQ(dump.find("arith.addi -> "), std::string::npos); } } // namespace From d7cd00399dbef3b3df3383bb33e7f3a0b22d512e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 22:11:41 +0200 Subject: [PATCH 48/55] :white_check_mark: Re-structured test code --- .../ConstantPropagation/test_quantumState.cpp | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index bc002626b0..8c94181462 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -195,6 +195,13 @@ TEST_F(QuantumStateTest, applyToQubitNotInGroupFailsEvenWhenTop) { qs.applyMatrix1Q(stranger, stranger, xOp.getUnitaryMatrix()).failed()); } +TEST_F(QuantumStateTest, twoQubitGateWithControlNotInGroupFails) { + auto qs = QuantumState({q[0], q[1]}, 4); + EXPECT_TRUE(qs.applyMatrix2Q(q[0], q[1], q[0], q[1], + swapOp.getUnitaryMatrix(), {q[2]}, {q[2]}) + .failed()); +} + //===----------------------------------------------------------------------===// // Controls //===----------------------------------------------------------------------===// @@ -267,6 +274,16 @@ TEST_F(QuantumStateTest, topStateStillForwardsQubits) { EXPECT_TRUE(qs.contains(q[1])); } +TEST_F(QuantumStateTest, groupWiderThanTheIndexTypeIsTop) { + auto reg = builder.allocQubitRegister(64); + SmallVector many; + for (size_t i = 0; i < 64; ++i) { + many.push_back(reg[i]); + } + const auto qs = QuantumState(many, 4); + EXPECT_TRUE(qs.isTop()); +} + //===----------------------------------------------------------------------===// // Controlled phase //===----------------------------------------------------------------------===// @@ -483,15 +500,9 @@ TEST_F(QuantumStateTest, topStatesAreEqual) { EXPECT_FALSE(a == QuantumState({q[0], q[1], q[2], q[3]}, 4)); } -TEST_F(QuantumStateTest, groupWiderThanTheIndexTypeIsTop) { - auto reg = builder.allocQubitRegister(64); - SmallVector many; - for (size_t i = 0; i < 64; ++i) { - many.push_back(reg[i]); - } - const auto qs = QuantumState(many, 4); - EXPECT_TRUE(qs.isTop()); -} +//===----------------------------------------------------------------------===// +// Printing +//===----------------------------------------------------------------------===// TEST_F(QuantumStateTest, printRendersImaginaryAmplitudes) { auto qs = QuantumState::singletonZero(q[0], 4); @@ -501,11 +512,4 @@ TEST_F(QuantumStateTest, printRendersImaginaryAmplitudes) { EXPECT_NE(printed(qs).find(" i"), std::string::npos); } -TEST_F(QuantumStateTest, twoQubitGateWithControlNotInGroupFails) { - auto qs = QuantumState({q[0], q[1]}, 4); - EXPECT_TRUE(qs.applyMatrix2Q(q[0], q[1], q[0], q[1], - swapOp.getUnitaryMatrix(), {q[2]}, {q[2]}) - .failed()); -} - } // namespace From f62289f7608b9c57cd07f150bdec5438624a2f06 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 22:12:59 +0200 Subject: [PATCH 49/55] :construction: Fixed formatting issues --- CHANGELOG.md | 8 +-- .../ConstantPropagationAnalysis.cpp | 24 ++++----- .../ConstantPropagation/HybridState.cpp | 50 +++++++++-------- .../ConstantPropagation/HybridState.hpp | 4 +- .../ConstantPropagation/QuantumState.cpp | 13 ++--- .../ConstantPropagation/UnionTable.cpp | 54 +++++++++---------- 6 files changed, 73 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77b9ed0b2e..7bc948d83f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,10 +58,10 @@ releases may include breaking changes. - ✨ Add passes for quantum-specific interprocedural optimizations ([#2193]) ([**@DRovara**], [**@burgholzer**]) -- ✨ Add Pauli twirling, quantum loop unrolling, qubit reuse passes, and constant propagation - ([#1705], [#1718], [#1755], [#1756], [#1923], [#1924], [#2039], [#2118], - [#2216], [#2224], [#2280]) ([**@MatthiasReumann**], [**@DRovara**], [**@burgholzer**], - [**@simon1hofmann**], [**@lirem101**]) +- ✨ Add Pauli twirling, quantum loop unrolling, qubit reuse passes, and + constant propagation ([#1705], [#1718], [#1755], [#1756], [#1923], [#1924], + [#2039], [#2118], [#2216], [#2224], [#2280]) ([**@MatthiasReumann**], + [**@DRovara**], [**@burgholzer**], [**@simon1hofmann**], [**@lirem101**]) - ✨ Add a compiler-target-aware `place-and-route` pass ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], [#1600], [#1664], [#1709], [#1716], [#1748], [#1805], [#1870], [#1904], [#1911], [#1951], [#1997], [#2016], diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp index 82e5d6deb5..8ca5a09b19 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ConstantPropagationAnalysis.cpp @@ -142,8 +142,7 @@ void UnionTableLattice::print(raw_ostream& os) const { //===----------------------------------------------------------------------===// ConstantPropagationAnalysis::ConstantPropagationAnalysis( - DataFlowSolver& solver, size_t maxNonzeroAmplitudes, - size_t maxHybridStates) + DataFlowSolver& solver, size_t maxNonzeroAmplitudes, size_t maxHybridStates) : DenseForwardDataFlowAnalysis(solver), maxNonzeroAmplitudes(maxNonzeroAmplitudes), maxHybridStates(maxHybridStates) {} @@ -206,8 +205,9 @@ LogicalResult ConstantPropagationAnalysis::visitOperation( return success(); } -LogicalResult ConstantPropagationAnalysis::applyOperation( - UnionTable& table, Operation* op, ArrayRef quantumControls) { +LogicalResult +ConstantPropagationAnalysis::applyOperation(UnionTable& table, Operation* op, + ArrayRef quantumControls) { ensureSeeded(table, op); return TypeSwitch(op) @@ -247,14 +247,13 @@ LogicalResult ConstantPropagationAnalysis::applyOperation( return table.addGlobalPhase(gphase.getTheta(), quantumControls, quantumControls); }) - .Case([&](CtrlOp ctrl) { - return applyCtrl(table, ctrl, quantumControls); - }) + .Case( + [&](CtrlOp ctrl) { return applyCtrl(table, ctrl, quantumControls); }) .Case([&](Operation* branch) { // Normally routed by the framework // (visitRegionBranchControlFlowTransfer); reaching one here means it is - // nested in a qco.ctrl / qco.inv / qco.pow body, which the analysis does - // not interpret - fall back to the conservative top. + // nested in a qco.ctrl / qco.inv / qco.pow body, which the analysis + // does not interpret - fall back to the conservative top. return applyUnmodelledOp(table, branch); }) .Case([&](UnitaryOpInterface gate) { @@ -277,9 +276,10 @@ LogicalResult ConstantPropagationAnalysis::applyOperation( }); } -LogicalResult ConstantPropagationAnalysis::applyUnitary( - UnionTable& table, UnitaryOpInterface gate, - ArrayRef quantumControls) { +LogicalResult +ConstantPropagationAnalysis::applyUnitary(UnionTable& table, + UnitaryOpInterface gate, + ArrayRef quantumControls) { const auto targetsIn = toVec(gate.getInputTargets()); const auto targetsOut = toVec(gate.getOutputTargets()); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 61f570a548..4c292cde21 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -76,9 +76,7 @@ std::optional HybridState::getClassical(Value v) const { // Mutation //===----------------------------------------------------------------------===// -void HybridState::setClassical(Value v, Attribute attr) { - classical[v] = attr; -} +void HybridState::setClassical(Value v, Attribute attr) { classical[v] = attr; } void HybridState::forwardValue(Value from, Value to) { state.forwardQubit(from, to); @@ -120,9 +118,8 @@ HybridState HybridState::tensor(const HybridState& other) const { // Classical-control handling //===----------------------------------------------------------------------===// -FailureOr -HybridState::classicalControlsHold(ArrayRef pos, - ArrayRef neg) const { +FailureOr HybridState::classicalControlsHold(ArrayRef pos, + ArrayRef neg) const { for (Value p : pos) { const auto attr = getClassical(p); if (!attr) { @@ -156,12 +153,12 @@ HybridState::classicalControlsHold(ArrayRef pos, // Gate application //===----------------------------------------------------------------------===// -LogicalResult -HybridState::applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, - ArrayRef quantumCtrlsIn, - ArrayRef quantumCtrlsOut, - ArrayRef posClassicalCtrls, - ArrayRef negClassicalCtrls) { +LogicalResult HybridState::applyMatrix1Q(Value in, Value out, + const Matrix2x2& matrix, + ArrayRef quantumCtrlsIn, + ArrayRef quantumCtrlsOut, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (quantumCtrlsIn.size() != quantumCtrlsOut.size() || !state.contains(in)) { return failure(); } @@ -179,11 +176,12 @@ HybridState::applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, return success(); } -LogicalResult HybridState::applyMatrix2Q( - Value in0, Value in1, Value out0, Value out1, const Matrix4x4& matrix, - ArrayRef quantumCtrlsIn, ArrayRef quantumCtrlsOut, - ArrayRef posClassicalCtrls, - ArrayRef negClassicalCtrls) { +LogicalResult HybridState::applyMatrix2Q(Value in0, Value in1, Value out0, + Value out1, const Matrix4x4& matrix, + ArrayRef quantumCtrlsIn, + ArrayRef quantumCtrlsOut, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (quantumCtrlsIn.size() != quantumCtrlsOut.size() || !state.contains(in0) || !state.contains(in1)) { return failure(); @@ -202,11 +200,11 @@ LogicalResult HybridState::applyMatrix2Q( return success(); } -LogicalResult -HybridState::addGlobalPhase(Value theta, ArrayRef quantumCtrlsIn, - ArrayRef quantumCtrlsOut, - ArrayRef posClassicalCtrls, - ArrayRef negClassicalCtrls) { +LogicalResult HybridState::addGlobalPhase(Value theta, + ArrayRef quantumCtrlsIn, + ArrayRef quantumCtrlsOut, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (quantumCtrlsIn.size() != quantumCtrlsOut.size()) { return failure(); } @@ -252,10 +250,10 @@ void HybridState::propagateClassical(Operation* op) { // Measurement / reset //===----------------------------------------------------------------------===// -LogicalResult -HybridState::measureQubit(Value in, Value out, Value classicalResult, - ArrayRef posClassicalCtrls, - ArrayRef negClassicalCtrls) { +LogicalResult HybridState::measureQubit(Value in, Value out, + Value classicalResult, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (!state.contains(in)) { return failure(); } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index 33fa034ec6..a8b64612b8 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -130,9 +130,7 @@ class HybridState { * * @param newProbability The new probability. */ - void setProbability(double newProbability) { - probability = newProbability; - } + void setProbability(double newProbability) { probability = newProbability; } /// @brief Collapses this branch's QuantumState to top; classical facts stay. void markStateTop(); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index fdf85bf53b..7bca6c59d5 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -37,8 +37,7 @@ namespace { constexpr unsigned MAX_GROUP_QUBITS = 63; } // namespace -QuantumState::QuantumState(ArrayRef qubits, - size_t maxNonzeroAmplitudes) +QuantumState::QuantumState(ArrayRef qubits, size_t maxNonzeroAmplitudes) : maxNonzeroAmplitudes(maxNonzeroAmplitudes), qubits(qubits.begin(), qubits.end()) { if (qubits.size() > MAX_GROUP_QUBITS) { @@ -83,8 +82,7 @@ void QuantumState::forwardQubit(Value from, Value to) { } } -void QuantumState::forwardQubits(ArrayRef from, - ArrayRef to) { +void QuantumState::forwardQubits(ArrayRef from, ArrayRef to) { for (const auto [f, t] : llvm::zip(from, to)) { forwardQubit(f, t); } @@ -209,10 +207,9 @@ LogicalResult QuantumState::applyMatrix2Q(Value in0, Value in1, Value out0, return success(); } -LogicalResult -QuantumState::applyControlledPhase(double phase, - ArrayRef ctrlsIn, - ArrayRef ctrlsOut) { +LogicalResult QuantumState::applyControlledPhase(double phase, + ArrayRef ctrlsIn, + ArrayRef ctrlsOut) { if (ctrlsIn.empty() || (!ctrlsOut.empty() && ctrlsOut.size() != ctrlsIn.size())) { return failure(); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 3177478941..68328aaece 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -60,8 +60,7 @@ std::optional UnionTable::slotIndexContaining(Value v) const { return std::nullopt; } -SmallVector -UnionTable::slotsTouchedBy(ArrayRef values) const { +SmallVector UnionTable::slotsTouchedBy(ArrayRef values) const { SmallVector result; for (Value v : values) { if (const auto i = slotIndexContaining(v)) { @@ -237,8 +236,7 @@ void UnionTable::forwardValue(Value from, Value to) { } } -void UnionTable::forwardValues(ArrayRef from, - ArrayRef to) { +void UnionTable::forwardValues(ArrayRef from, ArrayRef to) { for (const auto [f, t] : llvm::zip(from, to)) { forwardValue(f, t); } @@ -248,12 +246,12 @@ void UnionTable::forwardValues(ArrayRef from, // Operation propagation //===----------------------------------------------------------------------===// -LogicalResult -UnionTable::applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, - ArrayRef quantumCtrlsIn, - ArrayRef quantumCtrlsOut, - ArrayRef posClassicalCtrls, - ArrayRef negClassicalCtrls) { +LogicalResult UnionTable::applyMatrix1Q(Value in, Value out, + const Matrix2x2& matrix, + ArrayRef quantumCtrlsIn, + ArrayRef quantumCtrlsOut, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (allTop) { return success(); } @@ -280,11 +278,12 @@ UnionTable::applyMatrix1Q(Value in, Value out, const Matrix2x2& matrix, return success(); } -LogicalResult UnionTable::applyMatrix2Q( - Value in0, Value in1, Value out0, Value out1, const Matrix4x4& matrix, - ArrayRef quantumCtrlsIn, ArrayRef quantumCtrlsOut, - ArrayRef posClassicalCtrls, - ArrayRef negClassicalCtrls) { +LogicalResult UnionTable::applyMatrix2Q(Value in0, Value in1, Value out0, + Value out1, const Matrix4x4& matrix, + ArrayRef quantumCtrlsIn, + ArrayRef quantumCtrlsOut, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (allTop) { return success(); } @@ -311,11 +310,11 @@ LogicalResult UnionTable::applyMatrix2Q( return success(); } -LogicalResult -UnionTable::addGlobalPhase(Value theta, ArrayRef quantumCtrlsIn, - ArrayRef quantumCtrlsOut, - ArrayRef posClassicalCtrls, - ArrayRef negClassicalCtrls) { +LogicalResult UnionTable::addGlobalPhase(Value theta, + ArrayRef quantumCtrlsIn, + ArrayRef quantumCtrlsOut, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (allTop) { return success(); } @@ -375,10 +374,10 @@ void UnionTable::propagateClassical(Operation* op) { } } -LogicalResult -UnionTable::measureQubit(Value in, Value out, Value classicalResult, - ArrayRef posClassicalCtrls, - ArrayRef negClassicalCtrls) { +LogicalResult UnionTable::measureQubit(Value in, Value out, + Value classicalResult, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) { if (allTop) { return success(); } @@ -539,9 +538,10 @@ bool UnionTable::areControlsSatisfiable( return true; } -SuperfluousResult UnionTable::getSuperfluousControls( - ArrayRef quantumCtrls, ArrayRef posClassicalCtrls, - ArrayRef negClassicalCtrls) const { +SuperfluousResult +UnionTable::getSuperfluousControls(ArrayRef quantumCtrls, + ArrayRef posClassicalCtrls, + ArrayRef negClassicalCtrls) const { SuperfluousResult result; if (!areControlsSatisfiable(quantumCtrls, posClassicalCtrls, negClassicalCtrls)) { From 1e7e0c25c4942061e39c2f6784554dc22935d16b Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 22:24:05 +0200 Subject: [PATCH 50/55] :construction: Fixed handling of no out ctrl and added tests, assisted by Sonnet 5 via Claude Code --- .../ConstantPropagation/QuantumState.cpp | 3 +-- .../ConstantPropagation/test_hybridState.cpp | 6 ++++++ .../ConstantPropagation/test_quantumState.cpp | 12 +++++++++--- .../ConstantPropagation/test_unionTable.cpp | 8 ++++++++ 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 7bca6c59d5..ffc49d1ade 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -210,8 +210,7 @@ LogicalResult QuantumState::applyMatrix2Q(Value in0, Value in1, Value out0, LogicalResult QuantumState::applyControlledPhase(double phase, ArrayRef ctrlsIn, ArrayRef ctrlsOut) { - if (ctrlsIn.empty() || - (!ctrlsOut.empty() && ctrlsOut.size() != ctrlsIn.size())) { + if (ctrlsOut.size() != ctrlsIn.size()) { return failure(); } for (Value c : ctrlsIn) { diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp index d12454cf56..edd46a0cda 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -159,6 +159,12 @@ TEST_F(HybridStateTest, controlInOutLengthMismatchFails) { .failed()); } +TEST_F(HybridStateTest, nonEmptyControlInEmptyControlOutFails) { + auto hs = make({q[0], q[1]}); + EXPECT_TRUE( + hs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {}).failed()); +} + //===----------------------------------------------------------------------===// // Classical controls //===----------------------------------------------------------------------===// diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 8c94181462..a03433418a 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -250,6 +250,12 @@ TEST_F(QuantumStateTest, controlInOutLengthMismatchFails) { .failed()); } +TEST_F(QuantumStateTest, nonEmptyControlInEmptyControlOutFails) { + auto qs = QuantumState({q[0], q[1]}, 4); + EXPECT_TRUE( + qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {q[1]}, {}).failed()); +} + //===----------------------------------------------------------------------===// // Amplitude budget //===----------------------------------------------------------------------===// @@ -288,15 +294,15 @@ TEST_F(QuantumStateTest, groupWiderThanTheIndexTypeIsTop) { // Controlled phase //===----------------------------------------------------------------------===// -TEST_F(QuantumStateTest, uncontrolledPhaseIsRejected) { +TEST_F(QuantumStateTest, uncontrolledPhaseSucceeds) { auto qs = QuantumState::singletonZero(q[0], 4); - EXPECT_TRUE(qs.applyControlledPhase(std::acos(-1.0), {}).failed()); + EXPECT_TRUE(qs.applyControlledPhase(std::acos(-1.0), {}).succeeded()); } TEST_F(QuantumStateTest, controlledPhaseAffectsOnlyControlledSubspace) { auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 4); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); - ASSERT_TRUE(qs.applyControlledPhase(std::acos(-1.0), {q[0]}).succeeded()); + ASSERT_TRUE(qs.applyControlledPhase(std::acos(-1.0), {q[0]}, {q[0]}).succeeded()); EXPECT_EQ(printed(qs), "|0000> -> 0.71, |0001> -> -0.71"); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 0222cecbad..b462e50492 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -164,6 +164,14 @@ TEST_F(UnionTableTest, applyToUnseededQubitFails) { EXPECT_TRUE(ut.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix()).failed()); } +TEST_F(UnionTableTest, nonEmptyControlInEmptyControlOutFails) { + auto ut = make(); + ut.seedQubit(q[0]); + ut.seedQubit(q[1]); + EXPECT_TRUE( + ut.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {}).failed()); +} + //===----------------------------------------------------------------------===// // Classical controls //===----------------------------------------------------------------------===// From 93b270241f5bf17f35cbcb7f1acbc460ac8e4485 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 20:25:49 +0000 Subject: [PATCH 51/55] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Optimizations/ConstantPropagation/test_hybridState.cpp | 4 ++-- .../ConstantPropagation/test_quantumState.cpp | 7 ++++--- .../Optimizations/ConstantPropagation/test_unionTable.cpp | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp index edd46a0cda..2dc171b350 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -161,8 +161,8 @@ TEST_F(HybridStateTest, controlInOutLengthMismatchFails) { TEST_F(HybridStateTest, nonEmptyControlInEmptyControlOutFails) { auto hs = make({q[0], q[1]}); - EXPECT_TRUE( - hs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {}).failed()); + EXPECT_TRUE(hs.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {}) + .failed()); } //===----------------------------------------------------------------------===// diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index a03433418a..b0d65e192b 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -252,8 +252,8 @@ TEST_F(QuantumStateTest, controlInOutLengthMismatchFails) { TEST_F(QuantumStateTest, nonEmptyControlInEmptyControlOutFails) { auto qs = QuantumState({q[0], q[1]}, 4); - EXPECT_TRUE( - qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {q[1]}, {}).failed()); + EXPECT_TRUE(qs.applyMatrix1Q(q[0], q[0], xOp.getUnitaryMatrix(), {q[1]}, {}) + .failed()); } //===----------------------------------------------------------------------===// @@ -302,7 +302,8 @@ TEST_F(QuantumStateTest, uncontrolledPhaseSucceeds) { TEST_F(QuantumStateTest, controlledPhaseAffectsOnlyControlledSubspace) { auto qs = QuantumState({q[0], q[1], q[2], q[3]}, 4); ASSERT_TRUE(qs.applyMatrix1Q(q[0], q[0], hOp.getUnitaryMatrix()).succeeded()); - ASSERT_TRUE(qs.applyControlledPhase(std::acos(-1.0), {q[0]}, {q[0]}).succeeded()); + ASSERT_TRUE( + qs.applyControlledPhase(std::acos(-1.0), {q[0]}, {q[0]}).succeeded()); EXPECT_EQ(printed(qs), "|0000> -> 0.71, |0001> -> -0.71"); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index b462e50492..94eac0c5fe 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -168,8 +168,8 @@ TEST_F(UnionTableTest, nonEmptyControlInEmptyControlOutFails) { auto ut = make(); ut.seedQubit(q[0]); ut.seedQubit(q[1]); - EXPECT_TRUE( - ut.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {}).failed()); + EXPECT_TRUE(ut.applyMatrix1Q(q[1], q[1], xOp.getUnitaryMatrix(), {q[0]}, {}) + .failed()); } //===----------------------------------------------------------------------===// From 5853369d6fdd400c27fb64540521531796baf7a2 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 23:06:23 +0200 Subject: [PATCH 52/55] :construction: QuantumState rejects uncontrolled global Phase --- .../Optimizations/ConstantPropagation/QuantumState.cpp | 2 +- .../Optimizations/ConstantPropagation/test_quantumState.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index ffc49d1ade..9763154f8a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -210,7 +210,7 @@ LogicalResult QuantumState::applyMatrix2Q(Value in0, Value in1, Value out0, LogicalResult QuantumState::applyControlledPhase(double phase, ArrayRef ctrlsIn, ArrayRef ctrlsOut) { - if (ctrlsOut.size() != ctrlsIn.size()) { + if (ctrlsIn.empty() || ctrlsOut.size() != ctrlsIn.size()) { return failure(); } for (Value c : ctrlsIn) { diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index a03433418a..2b4063d375 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -296,7 +296,7 @@ TEST_F(QuantumStateTest, groupWiderThanTheIndexTypeIsTop) { TEST_F(QuantumStateTest, uncontrolledPhaseSucceeds) { auto qs = QuantumState::singletonZero(q[0], 4); - EXPECT_TRUE(qs.applyControlledPhase(std::acos(-1.0), {}).succeeded()); + EXPECT_TRUE(qs.applyControlledPhase(std::acos(-1.0), {}).failed()); } TEST_F(QuantumStateTest, controlledPhaseAffectsOnlyControlledSubspace) { From d65e292bd3e9d0885ab510cd8062962166c1116b Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 23:10:55 +0200 Subject: [PATCH 53/55] :construction: Check if all ctrls are present in state, assisted by Sonnet 5 via Claude Code --- .../ConstantPropagation/HybridState.cpp | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 4c292cde21..e8c956b723 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -45,6 +45,15 @@ static std::optional classicalTruth(Attribute attr) { return std::nullopt; } +/// @brief Whether every quantum control qubit is present in the group. A +/// missing quantum control is a caller/propagation bug that must fail the gate +/// even when the classical controls would skip its application. +static bool quantumControlsPresent(const QuantumState& state, + ArrayRef quantumCtrlsIn) { + return llvm::all_of(quantumCtrlsIn, + [&](Value qc) { return state.contains(qc); }); +} + /// @brief Numeric value of a resolved classical constant, or nullopt if attr is /// not an integer/index/bool/float constant. static std::optional classicalDouble(Attribute attr) { @@ -159,7 +168,8 @@ LogicalResult HybridState::applyMatrix1Q(Value in, Value out, ArrayRef quantumCtrlsOut, ArrayRef posClassicalCtrls, ArrayRef negClassicalCtrls) { - if (quantumCtrlsIn.size() != quantumCtrlsOut.size() || !state.contains(in)) { + if (quantumCtrlsIn.size() != quantumCtrlsOut.size() || !state.contains(in) || + !quantumControlsPresent(state, quantumCtrlsIn)) { return failure(); } const auto hold = classicalControlsHold(posClassicalCtrls, negClassicalCtrls); @@ -182,8 +192,9 @@ LogicalResult HybridState::applyMatrix2Q(Value in0, Value in1, Value out0, ArrayRef quantumCtrlsOut, ArrayRef posClassicalCtrls, ArrayRef negClassicalCtrls) { - if (quantumCtrlsIn.size() != quantumCtrlsOut.size() || !state.contains(in0) || - !state.contains(in1)) { + if (quantumCtrlsIn.size() != quantumCtrlsOut.size() || in0 == in1 || + !state.contains(in0) || !state.contains(in1) || + !quantumControlsPresent(state, quantumCtrlsIn)) { return failure(); } const auto hold = classicalControlsHold(posClassicalCtrls, negClassicalCtrls); @@ -205,7 +216,8 @@ LogicalResult HybridState::addGlobalPhase(Value theta, ArrayRef quantumCtrlsOut, ArrayRef posClassicalCtrls, ArrayRef negClassicalCtrls) { - if (quantumCtrlsIn.size() != quantumCtrlsOut.size()) { + if (quantumCtrlsIn.size() != quantumCtrlsOut.size() || + !quantumControlsPresent(state, quantumCtrlsIn)) { return failure(); } const auto hold = classicalControlsHold(posClassicalCtrls, negClassicalCtrls); From add0e90731c0cf7c2c8c6766b86c1ae9dc51b9a5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 23:15:37 +0200 Subject: [PATCH 54/55] :construction: Skip controls nested anywhere below a modifier --- .../Transforms/Optimizations/ConstantPropagation/Rewriter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp index 79cfd59743..1655360835 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/Rewriter.cpp @@ -31,7 +31,8 @@ SmallVector collectDecisions(func::FuncOp entry, entry.walk([&](CtrlOp op) { // A controlled gate nested in another modifier's body is interpreted by // that modifier's handler and not rewritten. - if (isa(op->getParentOp())) { + if (op->getParentOfType() || op->getParentOfType() || + op->getParentOfType()) { return; } From 5faed8a586999f768c0d737ee62324335f205749 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 30 Aug 2026 23:27:28 +0200 Subject: [PATCH 55/55] :white_check_mark: Fixed test title --- .../Optimizations/ConstantPropagation/test_quantumState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 92b1b79431..06d2b447a9 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -294,7 +294,7 @@ TEST_F(QuantumStateTest, groupWiderThanTheIndexTypeIsTop) { // Controlled phase //===----------------------------------------------------------------------===// -TEST_F(QuantumStateTest, uncontrolledPhaseSucceeds) { +TEST_F(QuantumStateTest, uncontrolledPhaseFails) { auto qs = QuantumState::singletonZero(q[0], 4); EXPECT_TRUE(qs.applyControlledPhase(std::acos(-1.0), {}).failed()); }