-
Notifications
You must be signed in to change notification settings - Fork 90
QIR FullStateSimulator does not reuse qubit mappings #557
Description
The QIR FullStateSimulator wrapper does not include any logic to reuse qubit logical identifiers, instead always incrementing the logical qubit id with each call to allocate, as seen in FullStateSimulator.cpp:
Qubit AllocateQubit() override
{
typedef void (*TAllocateQubit)(unsigned, unsigned);
static TAllocateQubit allocateQubit = reinterpret_cast<TAllocateQubit>(this->GetProc("allocateQubit"));
const unsigned id = this->nextQubitId;
allocateQubit(this->simulatorId, id);
this->nextQubitId++; // <-- UNCONDITIONALLY INCREASES ID
return reinterpret_cast<Qubit>(id);
}Looking at how the native simulator implements the allocateQubit API, the assumption is that the caller will reuse a previously released qubit id if possible. Since the QIR wrapper does not, it means each allocation will grow the size of the qubit map even if there are unused qubits already present in the map. This is effectively a memory leak, though admittedly a very small one.
While we could implement the logic in FullStateSimulator.cpp to track in-use qubit ids and reuse the next available (similar to what the C# mapping implemented in QubitManager.cs does), we should instead consider exposing the qubit management logic already present in the native simulator through the C API. The simulator already supports a variant of allocate that takes no arguments and instead returns the qubit id:
logical_qubit_id allocate()
{
recursive_lock_type l(mutex());
return psi.allocate_qubit();
}The corresponding function on the wave function implementation then uses its internal map to find the first available qubit, only adding a new qubit if no existing entries are available:
/// Allocate a qubit with implicitly assigned logical qubit id.
logical_qubit_id allocate_qubit()
{
#ifndef NDEBUG
assert(usage_ != QubitAllocationPattern::explicitLogicalId);
usage_ = QubitAllocationPattern::implicitLogicalId;
#endif
flush();
wfn_.resize(2 * wfn_.size());
// Reuse a logical qubit id, if any is available.
auto it = std::find(qubitmap_.begin(), qubitmap_.end(), invalid_qubit_position());
if (it != qubitmap_.end())
{
logical_qubit_id num = static_cast<unsigned>(it - qubitmap_.begin());
qubitmap_[num] = num_qubits_++;
return num;
}
else
{
qubitmap_.push_back(num_qubits_++);
return static_cast<unsigned>(qubitmap_.size() - 1);
}
}This seems perfect for usage by the QIR FullStateSimulator wrapper, but just isn't exposed in capi.hpp.
Unless there is a good reason not to that I'm missing, we should expose the smart allocate from the simulator and use that in QIR.