Skip to content

Commit

Permalink
[CircuitCheck] Update test utility to comprehend mapping changes (NVI…
Browse files Browse the repository at this point in the history
…DIA#668)

* [opt] Add WireInterface

* [CircuitCheck] Adapt it to handle wires and check mappigns

* [utils] Fix CircuitCheck by avoiding log2(0)

* [opt] Replace WireInterface with specific functions

* [CircuitCheck] Replace WireInterface with specific functions

* [opt] Replace WireInterface with specific functions (part 2)

* [CircuitCheck] Replace WireInterface with specific functions (part 2)

* [CircuitCheck] Replace WireInterface with specific functions (part 3)

---------

Co-authored-by: Ben Howe <[email protected]>
Co-authored-by: Ben Howe <[email protected]>
  • Loading branch information
3 people authored Oct 25, 2023
1 parent a01c642 commit b9b2964
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 21 deletions.
12 changes: 9 additions & 3 deletions utils/CircuitCheck/CircuitCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ static cl::opt<bool>
cl::desc("Check unitaries are equal up to global phase."),
cl::init(false));

static cl::opt<bool>
upToMapping("up-to-mapping",
cl::desc("Check unitaries are equal up a known permutation."),
cl::init(false));

static cl::opt<bool>
dontCanonicalize("no-canonicalizer",
cl::desc("Disable running the canonicalizer pass."),
Expand All @@ -42,8 +47,9 @@ static cl::opt<bool> printUnitary("print-unitary",
cl::init(false));

static LogicalResult computeUnitary(func::FuncOp func,
cudaq::UnitaryBuilder::UMatrix &unitary) {
cudaq::UnitaryBuilder builder(unitary);
cudaq::UnitaryBuilder::UMatrix &unitary,
bool upToMapping = false) {
cudaq::UnitaryBuilder builder(unitary, upToMapping);
return builder.build(func);
}

Expand Down Expand Up @@ -84,7 +90,7 @@ int main(int argc, char **argv) {

auto inputFunc = dyn_cast<func::FuncOp>(inputOp);
if (failed(computeUnitary(checkFunc, checkUnitary)) ||
failed(computeUnitary(inputFunc, inputUnitary))) {
failed(computeUnitary(inputFunc, inputUnitary, upToMapping))) {
llvm::errs() << "Cannot compute unitary for " << opName.str() << ".\n";
exitStatus = EXIT_FAILURE;
continue;
Expand Down
47 changes: 31 additions & 16 deletions utils/CircuitCheck/UnitaryBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ LogicalResult UnitaryBuilder::build(func::FuncOp func) {
SmallVector<Complex, 16> matrix;
SmallVector<Qubit, 16> qubits;
auto result = func.walk([&](Operation *op) {
if (auto nullWireOp = dyn_cast<quake::NullWireOp>(op))
return allocateQubits(nullWireOp.getResult());
if (auto allocOp = dyn_cast<quake::AllocaOp>(op))
return allocateQubits(allocOp.getResult());
if (auto extractOp = dyn_cast<quake::ExtractRefOp>(op))
Expand All @@ -38,20 +40,34 @@ LogicalResult UnitaryBuilder::build(func::FuncOp func) {
optor.emitOpError("Couldn't produce matrix.");
return WalkResult::interrupt();
}
auto quantumOperands = quake::getQuantumOperands(op);
if (quantumOperands.empty()) {
optor.emitOpError("Couldn't get quantum operands");
return WalkResult::interrupt();
}
// If we can't get the qubits involved in this operation, stop the walk
if (failed(getQubits(optor.getControls(), qubits)) ||
failed(getQubits(optor.getTargets(), qubits))) {
if (failed(getQubits(quantumOperands, qubits))) {
optor.emitOpError("Couldn't get the qubits.");
return WalkResult::interrupt();
}

if (optor.getNegatedControls())
negatedControls(*optor.getNegatedControls(), qubits);
for (auto &&[newQuantumOp, quantumOp] : llvm::zip(
quake::getQuantumResults(op), quake::getQuantumOperands(op)))
qubitMap[newQuantumOp] = qubitMap[quantumOp];

applyOperator(matrix, optor.getTargets().size(), qubits);
// When checking mapped circuits, we do a software swap, i.e., just change
// the qubit mapping instead of applying the swap operation.
if (upToMapping && isa<quake::SwapOp>(op)) {
std::swap(qubitMap[op->getResult(0)], qubitMap[op->getResult(1)]);
} else {
if (optor.getNegatedControls())
negatedControls(*optor.getNegatedControls(), qubits);

if (optor.getNegatedControls())
negatedControls(*optor.getNegatedControls(), qubits);
applyOperator(matrix, optor.getTargets().size(), qubits);

if (optor.getNegatedControls())
negatedControls(*optor.getNegatedControls(), qubits);
}

matrix.clear();
qubits.clear();
Expand Down Expand Up @@ -85,8 +101,10 @@ WalkResult UnitaryBuilder::visitExtractOp(quake::ExtractRefOp op) {

WalkResult UnitaryBuilder::allocateQubits(Value value) {
auto [entry, success] = qubitMap.try_emplace(value);
if (!success)
if (!success) {
value.getDefiningOp()->emitError("Qubit already allocated.");
return WalkResult::interrupt();
}
auto &qubits = entry->second;
if (auto veq = dyn_cast<quake::VeqType>(value.getType())) {
if (!veq.hasSpecifiedSize()) {
Expand All @@ -102,6 +120,10 @@ WalkResult UnitaryBuilder::allocateQubits(Value value) {
return WalkResult::advance();
}

//===----------------------------------------------------------------------===//
// Helpers
//===----------------------------------------------------------------------===//

LogicalResult UnitaryBuilder::getValueAsInt(Value value, std::size_t &result) {
if (auto op =
dyn_cast_if_present<arith::ConstantIntOp>(value.getDefiningOp()))
Expand All @@ -112,16 +134,9 @@ LogicalResult UnitaryBuilder::getValueAsInt(Value value, std::size_t &result) {
return failure();
}

//===----------------------------------------------------------------------===//
// Helpers
//===----------------------------------------------------------------------===//

LogicalResult UnitaryBuilder::getQubits(ValueRange values,
SmallVectorImpl<Qubit> &qubits) {
for (Value value : values) {
if (dyn_cast<quake::WireType>(value.getType()))
return failure();

if (auto veq = dyn_cast<quake::VeqType>(value.getType())) {
if (!veq.hasSpecifiedSize())
return failure();
Expand All @@ -141,7 +156,7 @@ void UnitaryBuilder::negatedControls(ArrayRef<bool> negatedControls,
}

LogicalResult UnitaryBuilder::deallocateAncillas(std::size_t numQubits) {
if (matrix.rows() == (1 << numQubits))
if (numQubits == 0 || matrix.rows() == (1 << numQubits))
return success();
const std::size_t size = (1ULL << numQubits);
UMatrix newMatrix = matrix.block(0, 0, size, size);
Expand Down
9 changes: 7 additions & 2 deletions utils/CircuitCheck/UnitaryBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ class UnitaryBuilder {
using Qubit = unsigned;
using UMatrix = Eigen::Matrix<Complex, Eigen::Dynamic, Eigen::Dynamic>;

UnitaryBuilder(UMatrix &matrix) : matrix(matrix) {}
UnitaryBuilder(UMatrix &matrix, bool upToMapping)
: matrix(matrix), upToMapping(upToMapping) {}

mlir::LogicalResult build(mlir::func::FuncOp func);

Expand All @@ -43,7 +44,9 @@ class UnitaryBuilder {

mlir::LogicalResult getValueAsInt(mlir::Value value, std::size_t &result);

std::size_t getNumQubits() { return std::log2(matrix.rows()); }
std::size_t getNumQubits() {
return matrix.rows() > 0 ? std::log2(matrix.rows()) : 0;
}

mlir::LogicalResult getQubits(mlir::ValueRange values,
mlir::SmallVectorImpl<Qubit> &qubits);
Expand Down Expand Up @@ -78,6 +81,8 @@ class UnitaryBuilder {
/// The unitary we are building
UMatrix &matrix;

bool upToMapping;

/// Map values to qubits identifiers
///
/// NOTE: To simplify the API and avoid the need to keep different maps for
Expand Down

0 comments on commit b9b2964

Please sign in to comment.