Skip to content

Commit 25f536f

Browse files
committed
Prepare Layout API for partial injections
`Layout` can now describe partial program-to-hardware mappings. `nqubits()` is split into `nProgramQubits()` and `nHardwareQubits()`. `hasProgramAt(hw)` reports whether a hardware qubit currently carries a program qubit. `swap(hwA, hwB)` accepts either side as unplaced and treats empty as a legitimate value that also gets swapped. `random(nProg, nHw, seed)` places `nProg` program qubits on distinct hardware qubits drawn from `[0, nHw)`, so `nProg < nHw` leaves the remaining hardware slots unplaced. The mapping pass keeps calling `random(n, n, rng())` and `nHardwareQubits()`, so behavior is unchanged. Actually using `nProg < nHw` and the query API from inside the pass is left for a follow-up. Part of #1867 Assisted-by: Claude Opus 4.7 via Claude Code Signed-off-by: rturrado <rturrado@gmail.com>
1 parent 698fa25 commit 25f536f

4 files changed

Lines changed: 239 additions & 50 deletions

File tree

mlir/include/mlir/Dialect/QCO/Utils/Layout.h

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,36 @@
2121

2222
namespace mlir::qco {
2323

24-
/// A qubit layout that maps program and hardware indices without
25-
/// storing Values. Used for efficient memory usage when Value tracking isn't
26-
/// needed.
24+
/// A qubit layout that maps program qubit indices to hardware qubit indices
25+
/// without storing Values.
2726
///
28-
/// Note that we use the terminology "hardware" and "program" qubits
29-
/// here, because "virtual" (opposed to physical) and "static" (opposed to
30-
/// dynamic) are C++ keywords.
27+
/// The mapping is a partial injection: at most `nProgramQubits` program qubits
28+
/// are placed on distinct hardware qubits drawn from `[0, nHardwareQubits)`.
29+
/// The two bounds may differ (usually `nProgramQubits <= nHardwareQubits`), so
30+
/// some hardware qubits may remain unplaced.
31+
///
32+
/// Note that we use the terminology "hardware" and "program" qubits here,
33+
/// because "virtual" (opposed to physical) and "static" (opposed to dynamic)
34+
/// are C++ keywords.
3135
class Layout {
3236
public:
37+
/// Construct an empty layout.
38+
Layout() = default;
39+
3340
/// Construct and return a random layout that places every program qubit
34-
/// index in `[0, nqubits)` on a distinct hardware index in the same range.
35-
static Layout random(size_t nqubits, size_t seed);
41+
/// index in `[0, nProgramQubits)` on a distinct hardware index drawn from
42+
/// `[0, nHardwareQubits)`.
43+
static Layout random(size_t nProgramQubits, size_t nHardwareQubits,
44+
size_t seed);
3645

3746
/// Construct a layout from a program-to-hardware mapping,
3847
/// where mapping[prog] = hw.
48+
/// Sets both `nProgramQubits` and `nHardwareQubits` to `mapping.size()`.
3949
static Layout fromMapping(ArrayRef<size_t> mapping);
4050

4151
/// Insert a program:hardware index mapping.
42-
/// Requires that neither `prog` nor `hw` has been mapped previously.
52+
/// Requires `prog < nProgramQubits`, `hw < nHardwareQubits`, and that
53+
/// neither `prog` nor `hw` has been mapped previously.
4354
void add(size_t prog, size_t hw);
4455

4556
/// Lookup and return program index for a hardware index.
@@ -64,15 +75,24 @@ class Layout {
6475
return std::tuple{getProgramIndex(static_cast<size_t>(hws))...};
6576
}
6677

78+
/// Return true if `hw` currently has a program qubit assigned to it.
79+
[[nodiscard]] bool hasProgramAt(size_t hw) const;
80+
6781
/// Swap the mapping to program indices of two hardware indices.
82+
/// Either side may be currently unplaced; the empty state is treated as a
83+
/// legitimate value that also gets swapped.
6884
void swap(size_t hwA, size_t hwB);
6985

70-
/// Return the number of qubits this layout was declared with.
71-
[[nodiscard]] size_t nqubits() const;
86+
/// Return the number of program qubits this layout was declared with.
87+
[[nodiscard]] size_t nProgramQubits() const;
88+
89+
/// Return the number of hardware qubits this layout was declared with.
90+
[[nodiscard]] size_t nHardwareQubits() const;
7291

7392
/// Return the program to hardware mapping as a materialized vector of
74-
/// length `nqubits()`, where entry `prog` is the hardware index assigned
75-
/// to program qubit `prog`.
93+
/// length `nProgramQubits()`, where entry `prog` is the hardware index
94+
/// assigned to program qubit `prog`. Requires every program qubit to be
95+
/// placed.
7696
[[nodiscard]] SmallVector<size_t> getProgramToHardware() const;
7797

7898
/// Compare two layouts for equality.
@@ -81,14 +101,16 @@ class Layout {
81101
}
82102

83103
private:
84-
/// Construct a layout with `nqubits`.
85-
explicit Layout(const size_t nqubits) : nqubits_(nqubits) {}
104+
Layout(const size_t nProgramQubits, const size_t nHardwareQubits)
105+
: nProgramQubits_(nProgramQubits), nHardwareQubits_(nHardwareQubits) {}
86106

87107
/// Maps a program qubit index to its hardware index.
88108
DenseMap<size_t, size_t> programToHardware_;
89109
/// Maps a hardware qubit index to its program index.
90110
DenseMap<size_t, size_t> hardwareToProgram_;
91-
/// Number of qubits this layout was declared with.
92-
size_t nqubits_ = 0;
111+
/// Number of program qubits this layout was declared with.
112+
size_t nProgramQubits_ = 0;
113+
/// Number of hardware qubits this layout was declared with.
114+
size_t nHardwareQubits_ = 0;
93115
};
94116
} // namespace mlir::qco

mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ struct MappingPass : impl::MappingPassBase<MappingPass> {
636636

637637
// Create and save static qubit operations.
638638
rewriter.setInsertionPointToStart(&body.front());
639-
for (size_t hw = 0; hw < layout.nqubits(); ++hw) {
639+
for (size_t hw = 0; hw < layout.nHardwareQubits(); ++hw) {
640640
const auto site = target->siteForVertex(hw);
641641
auto op = StaticOp::create(rewriter, body.getLoc(), site);
642642
staticQubits.emplace_back(op.getQubit());
@@ -688,7 +688,7 @@ struct MappingPass : impl::MappingPassBase<MappingPass> {
688688
// Create sinks for remaining, unused, static qubits.
689689

690690
rewriter.setInsertionPoint(body.back().getTerminator());
691-
for (size_t prog = wires.size(); prog < layout.nqubits(); ++prog) {
691+
for (size_t prog = wires.size(); prog < layout.nHardwareQubits(); ++prog) {
692692
const auto hw = layout.getHardwareIndex(prog);
693693
const auto site = target->siteForVertex(hw);
694694
const auto qubit = staticQubits[site];
@@ -720,7 +720,7 @@ struct MappingPass : impl::MappingPassBase<MappingPass> {
720720
}
721721
})
722722
.Case<scf::ForOp>([&](scf::ForOp forOp) {
723-
assert(qubits.size() == layout.nqubits());
723+
assert(qubits.size() == layout.nHardwareQubits());
724724

725725
llvm::for_each(getQubitValues(forOp.getInits()),
726726
[&](Value v) { qubits.erase(v); });
@@ -741,7 +741,7 @@ struct MappingPass : impl::MappingPassBase<MappingPass> {
741741
DenseSet<Value>(regionQubits.begin(), regionQubits.end()));
742742
})
743743
.Case<scf::WhileOp>([&](scf::WhileOp whileOp) {
744-
assert(qubits.size() == layout.nqubits());
744+
assert(qubits.size() == layout.nHardwareQubits());
745745

746746
llvm::for_each(getQubitValues(whileOp.getInits()),
747747
[&](Value v) { qubits.erase(v); });
@@ -767,7 +767,7 @@ struct MappingPass : impl::MappingPassBase<MappingPass> {
767767
DenseSet<Value>(afterArgs.begin(), afterArgs.end()));
768768
})
769769
.Case<IfOp>([&](IfOp ifOp) {
770-
assert(qubits.size() == layout.nqubits());
770+
assert(qubits.size() == layout.nHardwareQubits());
771771

772772
llvm::for_each(ifOp.getQubits(),
773773
[&](Value v) { qubits.erase(v); });
@@ -790,7 +790,7 @@ struct MappingPass : impl::MappingPassBase<MappingPass> {
790790
DenseSet<Value>(elseArgs.begin(), elseArgs.end()));
791791
})
792792
.Case<IndexSwitchOp>([&](IndexSwitchOp switchOp) {
793-
assert(qubits.size() == layout.nqubits());
793+
assert(qubits.size() == layout.nHardwareQubits());
794794

795795
llvm::for_each(switchOp.getTargets(),
796796
[&](Value value) { qubits.erase(value); });
@@ -850,7 +850,8 @@ struct MappingPass : impl::MappingPassBase<MappingPass> {
850850
trials.emplace_back(
851851
RoutingBundle{.wires = wires,
852852
.infos = infos,
853-
.layout = Layout::random(target->numQubits(), rng())});
853+
.layout = Layout::random(target->numQubits(),
854+
target->numQubits(), rng())});
854855
}
855856

856857
parallelForEach(&getContext(), trials, [&, this](Trial& t) {
@@ -921,8 +922,8 @@ struct MappingPass : impl::MappingPassBase<MappingPass> {
921922
SmallVector<IndexPairType, 6> expansionSet;
922923

923924
const auto materializeKey = [](const Layout& layout) {
924-
SmallVector<size_t> key(layout.nqubits());
925-
for (size_t prog = 0; prog < layout.nqubits(); ++prog) {
925+
SmallVector<size_t> key(layout.nHardwareQubits());
926+
for (size_t prog = 0; prog < layout.nHardwareQubits(); ++prog) {
926927
key[prog] = layout.getHardwareIndex(prog);
927928
}
928929
return key;
@@ -1088,7 +1089,7 @@ struct MappingPass : impl::MappingPassBase<MappingPass> {
10881089
/// is the order (the permutation) of program-to-hardware indices.
10891090
template <typename Range> static Layout vote(Range layouts) {
10901091
assert(!layouts.empty() && "expected at least one layout");
1091-
const auto ncandidates = (*layouts.begin()).nqubits();
1092+
const auto ncandidates = (*layouts.begin()).nHardwareQubits();
10921093

10931094
SmallVector<size_t> scores(ncandidates, 0);
10941095
for (const Layout& layout : layouts) {

mlir/lib/Dialect/QCO/Utils/Layout.cpp

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,25 @@
1818
#include <llvm/Support/ErrorHandling.h>
1919
#include <mlir/Support/LLVM.h>
2020

21+
#include <algorithm>
2122
#include <cassert>
2223
#include <cstddef>
2324
#include <random>
2425

2526
namespace mlir::qco {
2627

27-
Layout Layout::random(const size_t nqubits, const size_t seed) {
28-
auto mapping = llvm::to_vector(llvm::seq(nqubits));
29-
llvm::shuffle(mapping.begin(), mapping.end(), std::mt19937_64{seed});
30-
return fromMapping(mapping);
28+
Layout Layout::random(const size_t nProgramQubits, const size_t nHardwareQubits,
29+
const size_t seed) {
30+
assert(nProgramQubits <= nHardwareQubits &&
31+
"cannot place more program qubits than hardware qubits");
32+
auto hwIndices = llvm::to_vector(llvm::seq(nHardwareQubits));
33+
llvm::shuffle(hwIndices.begin(), hwIndices.end(), std::mt19937_64{seed});
34+
35+
Layout layout(nProgramQubits, nHardwareQubits);
36+
for (size_t prog = 0; prog < nProgramQubits; ++prog) {
37+
layout.add(prog, hwIndices[prog]);
38+
}
39+
return layout;
3140
}
3241

3342
Layout Layout::fromMapping(ArrayRef<size_t> mapping) {
@@ -39,16 +48,16 @@ Layout Layout::fromMapping(ArrayRef<size_t> mapping) {
3948
seen.set(hw);
4049
}
4150

42-
Layout layout(mapping.size());
51+
Layout layout(mapping.size(), mapping.size());
4352
for (const auto [prog, hw] : enumerate(mapping)) {
4453
layout.add(prog, hw);
4554
}
4655
return layout;
4756
}
4857

4958
void Layout::add(const size_t prog, const size_t hw) {
50-
assert(prog < nqubits_ && "program index out of bounds");
51-
assert(hw < nqubits_ && "hardware index out of bounds");
59+
assert(prog < nProgramQubits_ && "program index out of bounds");
60+
assert(hw < nHardwareQubits_ && "hardware index out of bounds");
5261
assert(!programToHardware_.contains(prog) && "program index already mapped");
5362
assert(!hardwareToProgram_.contains(hw) && "hardware index already mapped");
5463
programToHardware_[prog] = hw;
@@ -67,24 +76,43 @@ size_t Layout::getHardwareIndex(const size_t prog) const {
6776
return it->second;
6877
}
6978

79+
bool Layout::hasProgramAt(const size_t hw) const {
80+
return hardwareToProgram_.contains(hw);
81+
}
82+
7083
void Layout::swap(const size_t hwA, const size_t hwB) {
84+
assert(hwA < nHardwareQubits_ && "hardware index out of bounds");
85+
assert(hwB < nHardwareQubits_ && "hardware index out of bounds");
86+
if (hwA == hwB) {
87+
return;
88+
}
89+
// Read the current value on each side (may be empty), then write each to the
90+
// other side. Empty is treated as a legitimate value.
7191
const auto itA = hardwareToProgram_.find(hwA);
7292
const auto itB = hardwareToProgram_.find(hwB);
73-
assert(itA != hardwareToProgram_.end() && "hardware index not mapped");
74-
assert(itB != hardwareToProgram_.end() && "hardware index not mapped");
75-
const auto progA = itA->second;
76-
const auto progB = itB->second;
77-
itA->second = progB;
78-
itB->second = progA;
79-
programToHardware_[progA] = hwB;
80-
programToHardware_[progB] = hwA;
93+
const bool hasA = itA != hardwareToProgram_.end();
94+
const bool hasB = itB != hardwareToProgram_.end();
95+
const size_t progA = hasA ? itA->second : 0;
96+
const size_t progB = hasB ? itB->second : 0;
97+
hardwareToProgram_.erase(hwA);
98+
hardwareToProgram_.erase(hwB);
99+
if (hasA) {
100+
hardwareToProgram_[hwB] = progA;
101+
programToHardware_[progA] = hwB;
102+
}
103+
if (hasB) {
104+
hardwareToProgram_[hwA] = progB;
105+
programToHardware_[progB] = hwA;
106+
}
81107
}
82108

83-
size_t Layout::nqubits() const { return nqubits_; }
109+
size_t Layout::nProgramQubits() const { return nProgramQubits_; }
110+
111+
size_t Layout::nHardwareQubits() const { return nHardwareQubits_; }
84112

85113
SmallVector<size_t> Layout::getProgramToHardware() const {
86-
SmallVector<size_t> result(nqubits_);
87-
for (size_t prog = 0; prog < nqubits_; ++prog) {
114+
SmallVector<size_t> result(nProgramQubits_);
115+
for (size_t prog = 0; prog < nProgramQubits_; ++prog) {
88116
result[prog] = getHardwareIndex(prog);
89117
}
90118
return result;

0 commit comments

Comments
 (0)