From 4338098728d47eb302f11d75f1d3ad8efe61e311 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Tue, 18 May 2021 22:00:58 -0700 Subject: [PATCH 01/19] Added first draft version of Qubit manager to QIR --- src/Qir/Runtime/lib/QIR/CMakeLists.txt | 1 + .../lib/QIR/QubitManagerRestrictedReuse.cpp | 468 ++++++++++++++++++ .../lib/QIR/QubitManagerRestrictedReuse.hpp | 210 ++++++++ src/Qir/Runtime/unittests/CMakeLists.txt | 1 + .../Runtime/unittests/QubitManagerTests.cpp | 147 ++++++ 5 files changed, 827 insertions(+) create mode 100644 src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp create mode 100644 src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.hpp create mode 100644 src/Qir/Runtime/unittests/QubitManagerTests.cpp diff --git a/src/Qir/Runtime/lib/QIR/CMakeLists.txt b/src/Qir/Runtime/lib/QIR/CMakeLists.txt index 9dbd2b9cd74..e924bce54f0 100644 --- a/src/Qir/Runtime/lib/QIR/CMakeLists.txt +++ b/src/Qir/Runtime/lib/QIR/CMakeLists.txt @@ -19,6 +19,7 @@ set(rt_sup_source_files delegated.cpp strings.cpp utils.cpp + QubitManagerRestrictedReuse.cpp ) # Produce object lib we'll use to create a shared lib (so/dll) later on diff --git a/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp b/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp new file mode 100644 index 00000000000..443a54b3b9b --- /dev/null +++ b/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp @@ -0,0 +1,468 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// This code compiles, but was never executed, be careful! + +#include "QubitManagerRestrictedReuse.hpp" + +namespace Microsoft +{ +namespace Quantum +{ + + +// +// QubitListInSharedArray +// + +QubitListInSharedArray::QubitListInSharedArray(int minElement, int maxElement, int* sharedQubitStatusArray) +{ + // We are not storing pointer to shared array because it can be reallocated. + // Indexes and special values remain the same on such reallocations. + FailIf(minElement > maxElement || minElement < 0 || maxElement == INT_MAX, + "Incorrect boundaries in the linked list initialization."); + + for (int i = minElement; i < maxElement; i++) { + sharedQubitStatusArray[i] = i + 1; + } + sharedQubitStatusArray[maxElement] = NoneMarker; + firstElement = minElement; + lastElement = maxElement; +} + +bool QubitListInSharedArray::IsEmpty() +{ + return firstElement == NoneMarker; +} + +void QubitListInSharedArray::AddQubit(int id, bool addToFront, int* sharedQubitStatusArray) +{ + FailIf(id == NoneMarker, "Incorrect qubit id, cannot add it to the list."); + + // If the list is empty, we initialize it with the new element. + if (IsEmpty()) + { + firstElement = id; + lastElement = id; + sharedQubitStatusArray[id] = NoneMarker; + return; + } + + if (addToFront) + { + sharedQubitStatusArray[id] = firstElement; + firstElement = id; + } else + { + sharedQubitStatusArray[lastElement] = id; + sharedQubitStatusArray[id] = NoneMarker; + } +} + +// TODO: Set status to the taken qubit here. Then counting is reasonable here, but not possible? +// TODO: Rename 'RemoveQubitFromFront'? +int QubitListInSharedArray::TakeQubitFromFront(int* sharedQubitStatusArray) +{ + // First element will be returned. It is 'NoneMarker' if the list is empty. + int result = firstElement; + + // Advance list start to the next element if list is not empty. + if (!IsEmpty()) + { + firstElement = sharedQubitStatusArray[firstElement]; + } + + // Drop pointer to the last element if list becomes empty. + if (IsEmpty()) + { + lastElement = NoneMarker; + } + + return result; +} + +void QubitListInSharedArray::MoveAllQubitsFrom(QubitListInSharedArray& source, int* sharedQubitStatusArray) +{ + // No need to do anthing if source is empty. + if (source.IsEmpty()) + { + return; + } + + if (this->IsEmpty()) + { + // If this list is empty, we'll just set it to the source list. + lastElement = source.lastElement; + } else + { + // Attach source at the beginning of the list if both lists aren't empty. + sharedQubitStatusArray[source.lastElement] = firstElement; + } + firstElement = source.firstElement; + + // Remove all elements from source. + source.firstElement = NoneMarker; + source.lastElement = NoneMarker; +} + + +// +// RestrictedReuseArea +// + +RestrictedReuseArea::RestrictedReuseArea(QubitListInSharedArray freeQubits) +{ + //FreeQubitsReuseProhibited = QubitListInSharedArray(); // This is initialized by default. + FreeQubitsReuseAllowed = freeQubits; // Default shallow copying. +} + +void CRestrictedReuseAreaStack::PushToBack(RestrictedReuseArea area) +{ + this->insert(this->end(), area); +} + + +// +// CRestrictedReuseAreaStack +// + +RestrictedReuseArea CRestrictedReuseAreaStack::PopFromBack() +{ + FailIf(this->empty(), "Cannot remove element from empty set."); + RestrictedReuseArea result = this->back(); + this->pop_back(); + return result; +} + +RestrictedReuseArea& CRestrictedReuseAreaStack::PeekBack() { + return this->back(); +} + +int CRestrictedReuseAreaStack::Count() { + return this->size(); +} + +// +// CQubitManagerRestrictedReuse +// + +// Although it is not necessary to pass area IDs to these functions, such support may be added for extra checks. +void CQubitManagerRestrictedReuse::StartRestrictedReuseArea() +{ + RestrictedReuseArea newArea; + freeQubitsInAreas.PushToBack(newArea); +} + +void CQubitManagerRestrictedReuse::NextRestrictedReuseSegment() +{ + FailIf(freeQubitsInAreas.Count() <= 0, "Internal error! No reuse areas."); + FailIf(freeQubitsInAreas.Count() == 1, "NextRestrictedReuseSegment() without an active area."); + RestrictedReuseArea& currentArea = freeQubitsInAreas.PeekBack(); + // When new segment starts, reuse of all free qubits in the current area becomes prohibited. + currentArea.FreeQubitsReuseProhibited.MoveAllQubitsFrom(currentArea.FreeQubitsReuseAllowed, sharedQubitStatusArray); +} + +void CQubitManagerRestrictedReuse::EndRestrictedReuseArea() +{ + FailIf(freeQubitsInAreas.Count() < 2, "EndRestrictedReuseArea() without an active area."); + RestrictedReuseArea areaAboutToEnd = freeQubitsInAreas.PopFromBack(); + RestrictedReuseArea& containingArea = freeQubitsInAreas.PeekBack(); + // When area ends, reuse of all free qubits from this area becomes allowed. + containingArea.FreeQubitsReuseAllowed.MoveAllQubitsFrom(areaAboutToEnd.FreeQubitsReuseProhibited, sharedQubitStatusArray); + containingArea.FreeQubitsReuseAllowed.MoveAllQubitsFrom(areaAboutToEnd.FreeQubitsReuseAllowed, sharedQubitStatusArray); +} + +long CQubitManagerRestrictedReuse::AllocateQubit() +{ + if (EncourageReuse) { + // When reuse is encouraged, we start with the innermost area + for (int i = freeQubitsInAreas.Count() - 1; i >= 0; i--) { + long id = freeQubitsInAreas[i].FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); + if (id != NoneMarker) { + return id; + } + } + } else { + // When reuse is discouraged, we start with the outermost area + for (int i = 0; i < freeQubitsInAreas.Count(); i++) { + long id = freeQubitsInAreas[i].FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); + if (id != NoneMarker) { + return id; + } + } + } + return NoneMarker; +} + +void CQubitManagerRestrictedReuse::ReleaseQubit(long id) +{ + // Released qubits are added to reuse area/segment in which they were released + // (rather than area/segment where they are allocated). + // Although counterintuitive, this makes code simple. + // This is reasonable because we think qubits should be allocated and released in the same segment. + freeQubitsInAreas.PeekBack().FreeQubitsReuseAllowed.AddQubit(id, EncourageReuse, sharedQubitStatusArray); +} + +CQubitManagerRestrictedReuse::CQubitManagerRestrictedReuse( + int initialQubitCapacity, + bool mayExtendCapacity, + bool encourageReuse) +{ + qubitCapacity = initialQubitCapacity; + if (qubitCapacity <= 0) { + qubitCapacity = FallbackQubitCapacity; + } + sharedQubitStatusArray = new int[qubitCapacity]; + + MayExtendCapacity = mayExtendCapacity; + EncourageReuse = encourageReuse; + + // These objects are passed by value (copies are created) + QubitListInSharedArray FreeQubitsFresh(0, qubitCapacity - 1, sharedQubitStatusArray); + RestrictedReuseArea outermostArea(FreeQubitsFresh); + freeQubitsInAreas.PushToBack(outermostArea); + + AllocatedQubitsCount = 0; + DisabledQubitsCount = 0; + FreeQubitsCount = qubitCapacity; +} + +CQubitManagerRestrictedReuse::~CQubitManagerRestrictedReuse() +{ + if (sharedQubitStatusArray != nullptr) + { + delete[] sharedQubitStatusArray; + sharedQubitStatusArray = nullptr; + } + // freeQubitsInAreas - direct member of the class, no need to delete. +} + +void CQubitManagerRestrictedReuse::EnsureCapacity(int requestedCapacity) +{ + // No need to adjust existing values (NonMarker or indexes in the array). + // TODO: Borrowing/returning are not yet supported. + if (requestedCapacity <= qubitCapacity) + { + return; + } + + // Prepare new shared status array + int* newStatusArray = new int[requestedCapacity]; + memcpy(newStatusArray, sharedQubitStatusArray, qubitCapacity * sizeof(newStatusArray[0])); + QubitListInSharedArray newFreeQubits(qubitCapacity, requestedCapacity - 1, newStatusArray); + + // Set new data. All new qubits are added to the free qubits in the outermost area. + FreeQubitsCount += requestedCapacity - qubitCapacity; + delete[] sharedQubitStatusArray; + sharedQubitStatusArray = newStatusArray; + qubitCapacity = requestedCapacity; + freeQubitsInAreas[0].FreeQubitsReuseAllowed.MoveAllQubitsFrom(newFreeQubits, sharedQubitStatusArray); +} + +bool CQubitManagerRestrictedReuse::IsDisabled(int id) +{ + return sharedQubitStatusArray[id] == DisabledMarker; +} + +bool CQubitManagerRestrictedReuse::IsDisabled(Qubit qubit) +{ + return IsValid(qubit) && IsDisabled(QubitToId(qubit)); +} + +void CQubitManagerRestrictedReuse::Disable(Qubit qubit) +{ + FailIf(!IsValid(qubit), "Invalid qubit."); + int id = QubitToId(qubit); + // We can only disable explicitly allocated qubits that were not borrowed. + FailIf(!IsExplicitlyAllocated(id), "Cannot disable qubit that is not explicitly allocated."); + sharedQubitStatusArray[id] = DisabledMarker; + DisabledQubitsCount++; + + AllocatedQubitsCount--; + FailIf(AllocatedQubitsCount < 0, "Incorrect count of allocated qubits."); +} + +void CQubitManagerRestrictedReuse::Disable(Qubit* qubitsToDisable, int qubitCount) +{ + FailIf(qubitCount < 0, "Qubit count cannot be negative"); + if (qubitsToDisable == nullptr || qubitCount == 0) + { + return; + } + + for (int i = 0; i < qubitCount; i++) + { + Disable(qubitsToDisable[i]); + } +} + +bool CQubitManagerRestrictedReuse::IsExplicitlyAllocated(long id) +{ + return sharedQubitStatusArray[id] == AllocatedMarker; +} + +void CQubitManagerRestrictedReuse::ChangeStatusToAllocated(long id) +{ + FailIf(id >= qubitCapacity, "Internal Error: Cannot change status of an invalid qubit."); + sharedQubitStatusArray[id] = AllocatedMarker; + AllocatedQubitsCount++; + FreeQubitsCount--; +} + +Qubit CQubitManagerRestrictedReuse::Allocate() +{ + long newQubitId = AllocateQubit(); + if (newQubitId == NoneMarker && MayExtendCapacity) + { + EnsureCapacity(qubitCapacity * 2); + newQubitId = AllocateQubit(); + } + FailIf(newQubitId == NoneMarker, "Not enough qubits."); + ChangeStatusToAllocated(newQubitId); + return CreateQubitObject(newQubitId); +} + +Qubit* CQubitManagerRestrictedReuse::Allocate(long numToAllocate) +{ + FailIf(numToAllocate < 0, "Attempt to allocate negative number of qubits."); + if (numToAllocate == 0) + { + return new Qubit[0]; + } + + // Consider optimization for initial allocation of a large array at once + Qubit* result = new Qubit[numToAllocate]; + for (int i = 0; i < numToAllocate; i++) + { + int newQubitId = AllocateQubit(); + if (newQubitId == NoneMarker) + { + for (int k = 0; k < i; k++) + { + Release(result[k]); + } + delete[] result; + result = nullptr; + FailNow("Not enough qubits."); + return nullptr; + } + ChangeStatusToAllocated(newQubitId); + result[i] = CreateQubitObject(newQubitId); + } + + return result; +} + +bool CQubitManagerRestrictedReuse::IsFree(long id) +{ + return sharedQubitStatusArray[id] >= 0; +} + +bool CQubitManagerRestrictedReuse::IsFree(Qubit qubit) +{ + return IsValid(qubit) && IsFree(QubitToId(qubit)); +} + +void CQubitManagerRestrictedReuse::Release(long id) +{ + if (IsDisabled(id)) + { + // Nothing to do. Qubit will stay disabled. + return; + } + + FailIf(!IsExplicitlyAllocated(id), "Attempt to free qubit that has not been allocated."); + + if (MayExtendCapacity && !EncourageReuse) + { + // We can extend capcity and don't want reuse => Qubits will never be reused => Discard qubit. + // We put it in its own "free" list, this list will never be found again and qubit will not be reused. + sharedQubitStatusArray[id] = NoneMarker; + } else + { + ReleaseQubit(id); + } + + FreeQubitsCount++; + AllocatedQubitsCount--; + FailIf(AllocatedQubitsCount < 0, "Incorrect allocated qubit count."); +} + +void CQubitManagerRestrictedReuse::Release(Qubit qubit) +{ + FailIf(!IsValid(qubit), "Qubit is not valid."); + Release(QubitToId(qubit)); + // TODO: should we clean id of released qubit object? +} + +void CQubitManagerRestrictedReuse::Release(Qubit* qubitsToRelease, int qubitCount) { + FailIf(qubitCount < 0, "Attempt to release negative number of qubits."); + if (qubitsToRelease == nullptr) + { + return; + } + + for (int i = 0; i < qubitCount; i++) + { + Release(qubitsToRelease[i]); + qubitsToRelease[i] = nullptr; + } + + delete[] qubitsToRelease; +} + +Qubit CQubitManagerRestrictedReuse::Borrow() +{ + // We don't support true borrowing/returning at the moment. + return Allocate(); +} + +Qubit* CQubitManagerRestrictedReuse::Borrow(long qubitCountToBorrow) +{ + // We don't support true borrowing/returning at the moment. + return Allocate(qubitCountToBorrow); +} + +void CQubitManagerRestrictedReuse::Return(Qubit qubit) +{ + // We don't support true borrowing/returning at the moment. + Release(qubit); +} + +void CQubitManagerRestrictedReuse::Return(Qubit* qubitsToReturn, int qubitCountToReturn) +{ + // We don't support true borrowing/returning at the moment. + Release(qubitsToReturn, qubitCountToReturn); +} + +bool CQubitManagerRestrictedReuse::IsValid(Qubit qubit) +{ + int id = QubitToId(qubit); + if (id >= qubitCapacity) + { + return false; + } + if (id < 0) + { + return false; + } + return true; +} + +Qubit CQubitManagerRestrictedReuse::CreateQubitObject(int id) +{ + FailIf(id < 0 || id > INTPTR_MAX, "Qubit id out of range."); + intptr_t pointerSizedId = static_cast(id); + return reinterpret_cast(pointerSizedId); +} + +int CQubitManagerRestrictedReuse::QubitToId(Qubit qubit) +{ + intptr_t pointerSizedId = reinterpret_cast(qubit); + FailIf(pointerSizedId < 0 || pointerSizedId > INT_MAX, "Qubit id out of range."); + return static_cast(pointerSizedId); +} + +} +} diff --git a/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.hpp b/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.hpp new file mode 100644 index 00000000000..c4b7ab539b7 --- /dev/null +++ b/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.hpp @@ -0,0 +1,210 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include +#include +#include + +#include "CoreTypes.hpp" + +namespace Microsoft +{ +namespace Quantum +{ + + // TODO: Remove this! Use standard ones. + static void FailNow(const char* message) { std::cout << message; throw message; } + static void FailIf(bool condition, const char* message) { + if (condition) { + FailNow(message); + } + } + + + // TODO: Decide on the interface and move this to appropriate place. + struct IQubitManager + { + virtual ~IQubitManager() = default; + virtual Qubit Allocate() = 0; + virtual void Release(Qubit qubit) = 0; + }; + + // The end of free lists are marked with NoneMarker value. It is used like null for pointers. + // This value is non-negative just like other values in the free lists. + constexpr int NoneMarker = INT_MAX; + + // Explicitly allocated qubits are marked with Allocated value. + // Qubits implicitly allocated for borrowing are "refcounted" with values [Allocated+1 .. -2]. + // When refcount needs to be decreased to Allocated value, the qubit is automatically released. + constexpr int AllocatedMarker = INT_MAX; + + // Disabled qubits are marked with this value. + constexpr int DisabledMarker = -1; + + // We want status array to be reasonably large if initialization for it is wrong (i.e. requested size < 1). + constexpr int FallbackQubitCapacity = 8; + + // Indexes in the status array can potentially be in range 0 .. long.MaxValue-1. + // This gives maximum capacity as long.MaxValue. + // Index equal to long.MaxValue doesn't exist and is reserved for 'NoneMarker' - list terminator. + constexpr int MaximumQubitCapacity = INT_MAX; + + // QubitListInSharedArray implements a singly-linked list with "pointers" to the first and the last element stored. + // Pointers are the indexes in a shared array. Shared array isn't sotored here because it can be reallocated. + // This class maintains status of elements in the list by virtue of linking them as part of this list. + // This class doesn't update status of elementes excluded from the list. + // This class is small, contains no pointers and relies on default shallow copying/destruction. + struct QubitListInSharedArray final + { + private: + int firstElement = NoneMarker; + int lastElement = NoneMarker; + + public: + // Initialize empty list + QubitListInSharedArray() = default; + + // Initialize as a list with sequential elements from minElement to maxElement inclusve. + QubitListInSharedArray(int minElement, int maxElement, int* sharedQubitStatusArray); + + bool IsEmpty(); + void AddQubit(int id, bool addToFront, int* sharedQubitStatusArray); + int TakeQubitFromFront(int* sharedQubitStatusArray); + void MoveAllQubitsFrom(QubitListInSharedArray& source, int* sharedQubitStatusArray); + }; + + + // Restricted reuse area consists of multiple segments. Qubits released in one segment cannot be reused in another. + // One restricted reuse area can be nested in a segment of another restricted reuse area. + // This class tracks current segment of an area. Previous segments are tracked collectively (not individually). + // This class is small, contains no pointers and relies on default shallow copying/destruction. + struct RestrictedReuseArea final + { + public: + QubitListInSharedArray FreeQubitsReuseProhibited; + QubitListInSharedArray FreeQubitsReuseAllowed; + + RestrictedReuseArea() = default; + RestrictedReuseArea(QubitListInSharedArray freeQubits); + }; + + + + // This is NOT a stack! We modify it only by push/pop, but we also iterate over elements. + // TODO: Better name? + class CRestrictedReuseAreaStack final : public std::vector + { + public: + // No complex scenarios for now. Don't need to support copying/moving. + CRestrictedReuseAreaStack() = default; + CRestrictedReuseAreaStack(const CRestrictedReuseAreaStack&) = delete; + CRestrictedReuseAreaStack& operator = (const CRestrictedReuseAreaStack&) = delete; + ~CRestrictedReuseAreaStack() = default; + + void PushToBack(RestrictedReuseArea area); + RestrictedReuseArea PopFromBack(); + RestrictedReuseArea& PeekBack(); + // TODO: Remove and use size directly? + int Count(); + }; + + class CQubitManagerRestrictedReuse : public IQubitManager + { + // Restricted reuse area control, allocation and release + + public: void StartRestrictedReuseArea(); + public: void NextRestrictedReuseSegment(); + public: void EndRestrictedReuseArea(); + private: long AllocateQubit(); // Computation complexity is O(number of nested restricted reuse areas). + private: void ReleaseQubit(long id); + + // Configuration Properties + + // TODO: Make sure these are read only + public: bool MayExtendCapacity; + public: bool EncourageReuse; + + // Constructors/destructors, storage and reallocation + + private: int* sharedQubitStatusArray; // Tracks allocation state of all qubits. Stores lists of free qubits. + private: int qubitCapacity = 0; // qubitCapacity is always equal to the array size. + private: CRestrictedReuseAreaStack freeQubitsInAreas; // Fresh Free Qubits are located in freeQubitsInAreas[0].FreeQubitsReuseAllowed + + public: CQubitManagerRestrictedReuse( + int initialQubitCapacity, + bool mayExtendCapacity = false, + bool encourageReuse = true); + + // No complex scenarios for now. Don't need to support copying/moving. + public: CQubitManagerRestrictedReuse(const CQubitManagerRestrictedReuse&) = delete; + public: CQubitManagerRestrictedReuse& operator = (const CQubitManagerRestrictedReuse&) = delete; + public: ~CQubitManagerRestrictedReuse(); + private: void EnsureCapacity(int requestedCapacity); + + // Disable, disabled qubit count and checks + + public: int DisabledQubitsCount = 0; + public: bool IsDisabled(int id); + public: bool IsDisabled(Qubit qubit); + // Disables a given qubit. + // Once a qubit is disabled it can never be "enabled" or reallocated. + public: void Disable(Qubit qubit); + // Disables a set of given qubits. + // Once a qubit is disabled it can never be "enabled" or reallocated. + public: void Disable(Qubit* qubitsToDisable, int qubitCount); + + // Allocate, allocated qubit count and checks + + public: int AllocatedQubitsCount = 0; + public: bool IsExplicitlyAllocated(long id); + public: void ChangeStatusToAllocated(long id); + // Allocates a qubit. Extend capacity if necessary and possible. + // Fails if the qubit cannot be allocated. + public: Qubit Allocate(); + // Allocates numToAllocate new qubits. Extend capacity if necessary and possible. + // Fails without allocating any qubits if the qubits cannot be allocated. + // Returned array of qubits must be passed to Release to release qubits and free memory use for array. + public: Qubit* Allocate(long numToAllocate); + + // Release, free qubit count and checks + + public: long FreeQubitsCount = 0; + public: bool IsFree(long id); + public: bool IsFree(Qubit qubit); + public: void Release(long id); + // Releases a given qubit. + public: void Release(Qubit qubit); + // Releases an array of given qubits and deallocates array memory. + public: void Release(Qubit* qubitsToRelease, int qubitCount); + + // Borrow (We treat borrowing as allocation currently) + + public: Qubit Borrow(); + public: Qubit* Borrow(long qubitCountToBorrow); + + // Return (We treat returning as release currently) + + public: void Return(Qubit qubit); + public: void Return(Qubit* qubitsToReturn, int qubitCountToReturn); + + // Qubit creation, validity check + + public: bool IsValid(Qubit qubit); + + // May be overriden to create a custom Qubit object. + // When not overriden, it just stores qubit Id in place of a pointer to a qubit. + // id: unique qubit id + // Returns a newly instantiated qubit. + public: virtual Qubit CreateQubitObject(int id); + + // May be overriden to get a qubit id from a custom qubit object. + // Must be overriden if CreateQubitObject is overriden. + // When not overriden, it just reinterprets pointer to qubit as a qubit id. + // qubit: pointer to QUBIT + // Returns id of a qubit pointed to by qubit. + public: virtual int QubitToId(Qubit qubit); + }; + + +} +} diff --git a/src/Qir/Runtime/unittests/CMakeLists.txt b/src/Qir/Runtime/unittests/CMakeLists.txt index 40c41776d2a..664fc359a8f 100644 --- a/src/Qir/Runtime/unittests/CMakeLists.txt +++ b/src/Qir/Runtime/unittests/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(qir-runtime-unittests QirRuntimeTests.cpp ToffoliTests.cpp TracerTests.cpp + QubitManagerTests.cpp $ $ $ diff --git a/src/Qir/Runtime/unittests/QubitManagerTests.cpp b/src/Qir/Runtime/unittests/QubitManagerTests.cpp new file mode 100644 index 00000000000..965c5d4b9a4 --- /dev/null +++ b/src/Qir/Runtime/unittests/QubitManagerTests.cpp @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include + +#include "catch.hpp" + +#include "QubitManagerRestrictedReuse.hpp" + +using namespace Microsoft::Quantum; + +TEST_CASE("Simple allocation and release of one qubit", "[QubitManagerBasic]") +{ + std::unique_ptr qm = std::make_unique(1); + Qubit q = qm->Allocate(); + qm->Release(q); +} + +TEST_CASE("Allocation and reallocation of one qubit", "[QubitManagerBasic]") +{ + std::unique_ptr qm = std::make_unique(1); + REQUIRE(qm->FreeQubitsCount == 1); + REQUIRE(qm->AllocatedQubitsCount == 0); + Qubit q = qm->Allocate(); + REQUIRE(qm->QubitToId(q) == 0); + REQUIRE(qm->FreeQubitsCount == 0); + REQUIRE(qm->AllocatedQubitsCount == 1); + REQUIRE_THROWS(qm->Allocate()); + REQUIRE(qm->FreeQubitsCount == 0); + REQUIRE(qm->AllocatedQubitsCount == 1); + qm->Release(q); + REQUIRE(qm->FreeQubitsCount == 1); + REQUIRE(qm->AllocatedQubitsCount == 0); + Qubit q0 = qm->Allocate(); + REQUIRE(qm->QubitToId(q0) == 0); + REQUIRE(qm->FreeQubitsCount == 0); + REQUIRE(qm->AllocatedQubitsCount == 1); + qm->Release(q0); + REQUIRE(qm->FreeQubitsCount == 1); + REQUIRE(qm->AllocatedQubitsCount == 0); +} + + +TEST_CASE("Allocation of released qubits when reuse is encouraged", "[QubitManagerBasic]") +{ + std::unique_ptr qm = std::make_unique(2); + REQUIRE(qm->FreeQubitsCount == 2); + Qubit q0 = qm->Allocate(); + Qubit q1 = qm->Allocate(); + REQUIRE(qm->QubitToId(q0) == 0); // Qubit ids should be in order + REQUIRE(qm->QubitToId(q1) == 1); + REQUIRE_THROWS(qm->Allocate()); + REQUIRE(qm->FreeQubitsCount == 0); + REQUIRE(qm->AllocatedQubitsCount == 2); + + qm->Release(q0); + Qubit q0a = qm->Allocate(); + REQUIRE(qm->QubitToId(q0a) == 0); // It was the only one available + REQUIRE_THROWS(qm->Allocate()); + + qm->Release(q1); + qm->Release(q0a); + REQUIRE(qm->FreeQubitsCount == 2); + REQUIRE(qm->AllocatedQubitsCount == 0); + + Qubit q0b = qm->Allocate(); + Qubit q1a = qm->Allocate(); + REQUIRE(qm->QubitToId(q0b) == 0); // By default reuse is encouraged, LIFO is used + REQUIRE(qm->QubitToId(q1a) == 1); + REQUIRE_THROWS(qm->Allocate()); + REQUIRE(qm->FreeQubitsCount == 0); + REQUIRE(qm->AllocatedQubitsCount == 2); + + qm->Release(q0b); + qm->Release(q1a); +} + +TEST_CASE("Extending capacity", "[QubitManager]") +{ + std::unique_ptr qm = std::make_unique(1, true); + + Qubit q0 = qm->Allocate(); + REQUIRE(qm->QubitToId(q0) == 0); + Qubit q1 = qm->Allocate(); // This should double capacity + REQUIRE(qm->QubitToId(q1) == 1); + REQUIRE(qm->FreeQubitsCount == 0); + REQUIRE(qm->AllocatedQubitsCount == 2); + + qm->Release(q0); + Qubit q0a = qm->Allocate(); + REQUIRE(qm->QubitToId(q0a) == 0); + Qubit q2 = qm->Allocate(); // This should double capacity again + REQUIRE(qm->QubitToId(q2) == 2); + REQUIRE(qm->FreeQubitsCount == 1); + REQUIRE(qm->AllocatedQubitsCount == 3); + + qm->Release(q1); + qm->Release(q0a); + qm->Release(q2); + REQUIRE(qm->FreeQubitsCount == 4); + REQUIRE(qm->AllocatedQubitsCount == 0); + + Qubit* qqq = qm->Allocate(3); + REQUIRE(qm->FreeQubitsCount == 1); + REQUIRE(qm->AllocatedQubitsCount == 3); + qm->Release(qqq, 3); + REQUIRE(qm->FreeQubitsCount == 4); + REQUIRE(qm->AllocatedQubitsCount == 0); +} + +TEST_CASE("Restricted Area", "[QubitManager]") +{ + std::unique_ptr qm = std::make_unique(3, false, true); + + Qubit q0 = qm->Allocate(); + REQUIRE(qm->QubitToId(q0) == 0); + + qm->StartRestrictedReuseArea(); + + // Allocates fresh qubit + Qubit q1 = qm->Allocate(); + REQUIRE(qm->QubitToId(q1) == 1); + qm->Release(q1); // Released, but cannot be used in the next segment. + + qm->NextRestrictedReuseSegment(); + + // Allocates fresh qubit, q1 cannot be reused - it belongs to a differen segment. + Qubit q2 = qm->Allocate(); + REQUIRE(qm->QubitToId(q2) == 2); + qm->Release(q2); + + Qubit q2a = qm->Allocate(); // Getting the same one as the one that's just released. + REQUIRE(qm->QubitToId(q2a) == 2); + qm->Release(q2a); // Released, but cannot be used in the next segment. + + qm->NextRestrictedReuseSegment(); + + // There's no qubits left here. q0 is allocated, q1 and q2 are from different segments. + REQUIRE_THROWS(qm->Allocate()); + + qm->EndRestrictedReuseArea(); + + // Qubits 1 and 2 are available here again. + Qubit* qqq = qm->Allocate(2); + // OK to destruct qubit manager while qubits are still allocated. + REQUIRE_THROWS(qm->Allocate()); +} \ No newline at end of file From a9529e99a83e9fbcd7274c4920be2c12b3e8f6d0 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Tue, 25 May 2021 00:24:14 -0700 Subject: [PATCH 02/19] Review feedback. Rearranged functions, QubitIdType, consts, etc. --- .../lib/QIR/QubitManagerRestrictedReuse.cpp | 456 +++++++++--------- .../lib/QIR/QubitManagerRestrictedReuse.hpp | 208 ++++---- .../Runtime/unittests/QubitManagerTests.cpp | 75 +-- 3 files changed, 374 insertions(+), 365 deletions(-) diff --git a/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp b/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp index 443a54b3b9b..409133f04c4 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT License. // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// This code compiles, but was never executed, be careful! +// This code compiles, passes some unit tests, but isn't yet used in the runtime. #include "QubitManagerRestrictedReuse.hpp" @@ -16,27 +16,25 @@ namespace Quantum // QubitListInSharedArray // -QubitListInSharedArray::QubitListInSharedArray(int minElement, int maxElement, int* sharedQubitStatusArray) +QubitListInSharedArray::QubitListInSharedArray(QubitIdType startId, QubitIdType endId, QubitIdType* sharedQubitStatusArray) { - // We are not storing pointer to shared array because it can be reallocated. - // Indexes and special values remain the same on such reallocations. - FailIf(minElement > maxElement || minElement < 0 || maxElement == INT_MAX, + FailIf(startId > endId || startId < 0 || endId == std::numeric_limits::max(), "Incorrect boundaries in the linked list initialization."); - for (int i = minElement; i < maxElement; i++) { - sharedQubitStatusArray[i] = i + 1; + for (QubitIdType i = startId; i < endId; i++) { + sharedQubitStatusArray[i] = i + 1; // Current element points to the next element. } - sharedQubitStatusArray[maxElement] = NoneMarker; - firstElement = minElement; - lastElement = maxElement; + sharedQubitStatusArray[endId] = NoneMarker; // Last element ends the chain. + firstElement = startId; + lastElement = endId; } -bool QubitListInSharedArray::IsEmpty() +bool QubitListInSharedArray::IsEmpty() const { return firstElement == NoneMarker; } -void QubitListInSharedArray::AddQubit(int id, bool addToFront, int* sharedQubitStatusArray) +void QubitListInSharedArray::AddQubit(QubitIdType id, bool addToFront, QubitIdType* sharedQubitStatusArray) { FailIf(id == NoneMarker, "Incorrect qubit id, cannot add it to the list."); @@ -45,24 +43,25 @@ void QubitListInSharedArray::AddQubit(int id, bool addToFront, int* sharedQubitS { firstElement = id; lastElement = id; - sharedQubitStatusArray[id] = NoneMarker; + sharedQubitStatusArray[id] = NoneMarker; // List with a single elemenet in the chain. return; } if (addToFront) { - sharedQubitStatusArray[id] = firstElement; - firstElement = id; + sharedQubitStatusArray[id] = firstElement; // The new element will point to the former first element. + firstElement = id; // The new element is now the first in the chain. } else { - sharedQubitStatusArray[lastElement] = id; - sharedQubitStatusArray[id] = NoneMarker; + sharedQubitStatusArray[lastElement] = id; // The last element will point to the new element. + sharedQubitStatusArray[id] = NoneMarker; // The new element will end the chain. + lastElement = id; // The new element will be the last element in the chain. } } // TODO: Set status to the taken qubit here. Then counting is reasonable here, but not possible? // TODO: Rename 'RemoveQubitFromFront'? -int QubitListInSharedArray::TakeQubitFromFront(int* sharedQubitStatusArray) +int QubitListInSharedArray::TakeQubitFromFront(QubitIdType* sharedQubitStatusArray) { // First element will be returned. It is 'NoneMarker' if the list is empty. int result = firstElement; @@ -70,7 +69,7 @@ int QubitListInSharedArray::TakeQubitFromFront(int* sharedQubitStatusArray) // Advance list start to the next element if list is not empty. if (!IsEmpty()) { - firstElement = sharedQubitStatusArray[firstElement]; + firstElement = sharedQubitStatusArray[firstElement]; // The second element will be the first. } // Drop pointer to the last element if list becomes empty. @@ -82,7 +81,7 @@ int QubitListInSharedArray::TakeQubitFromFront(int* sharedQubitStatusArray) return result; } -void QubitListInSharedArray::MoveAllQubitsFrom(QubitListInSharedArray& source, int* sharedQubitStatusArray) +void QubitListInSharedArray::MoveAllQubitsFrom(QubitListInSharedArray& source, QubitIdType* sharedQubitStatusArray) { // No need to do anthing if source is empty. if (source.IsEmpty()) @@ -97,9 +96,9 @@ void QubitListInSharedArray::MoveAllQubitsFrom(QubitListInSharedArray& source, i } else { // Attach source at the beginning of the list if both lists aren't empty. - sharedQubitStatusArray[source.lastElement] = firstElement; + sharedQubitStatusArray[source.lastElement] = firstElement; // The last element of the source chain will point to the first element of this chain. } - firstElement = source.firstElement; + firstElement = source.firstElement; // The first element from the source chain will be the first element of this chain. // Remove all elements from source. source.firstElement = NoneMarker; @@ -117,16 +116,17 @@ RestrictedReuseArea::RestrictedReuseArea(QubitListInSharedArray freeQubits) FreeQubitsReuseAllowed = freeQubits; // Default shallow copying. } -void CRestrictedReuseAreaStack::PushToBack(RestrictedReuseArea area) -{ - this->insert(this->end(), area); -} - // // CRestrictedReuseAreaStack // +void CRestrictedReuseAreaStack::PushToBack(RestrictedReuseArea area) +{ + FailIf(Count() >= std::numeric_limits::max(), "Too many nested restricted reuse areas."); + this->insert(this->end(), area); +} + RestrictedReuseArea CRestrictedReuseAreaStack::PopFromBack() { FailIf(this->empty(), "Cannot remove element from empty set."); @@ -135,26 +135,62 @@ RestrictedReuseArea CRestrictedReuseAreaStack::PopFromBack() return result; } -RestrictedReuseArea& CRestrictedReuseAreaStack::PeekBack() { +RestrictedReuseArea& CRestrictedReuseAreaStack::PeekBack() +{ return this->back(); } -int CRestrictedReuseAreaStack::Count() { +int CRestrictedReuseAreaStack::Count() const +{ return this->size(); } // -// CQubitManagerRestrictedReuse +// CQubitManager // +CQubitManager::CQubitManager( + QubitIdType initialQubitCapacity, + bool mayExtendCapacity, + bool encourageReuse) +{ + this->mayExtendCapacity = mayExtendCapacity; + this->encourageReuse = encourageReuse; + + qubitCapacity = initialQubitCapacity; + if (qubitCapacity <= 0) { + qubitCapacity = FallbackQubitCapacity; + } + sharedQubitStatusArray = new int[qubitCapacity]; + + // These objects are passed by value (copies are created) + QubitListInSharedArray FreeQubitsFresh(0, qubitCapacity - 1, sharedQubitStatusArray); + RestrictedReuseArea outermostArea(FreeQubitsFresh); + freeQubitsInAreas.PushToBack(outermostArea); + + allocatedQubitCount = 0; + disabledQubitCount = 0; + freeQubitCount = qubitCapacity; +} + +CQubitManager::~CQubitManager() +{ + if (sharedQubitStatusArray != nullptr) + { + delete[] sharedQubitStatusArray; + sharedQubitStatusArray = nullptr; + } + // freeQubitsInAreas - direct member of the class, no need to delete. +} + // Although it is not necessary to pass area IDs to these functions, such support may be added for extra checks. -void CQubitManagerRestrictedReuse::StartRestrictedReuseArea() +void CQubitManager::StartRestrictedReuseArea() { RestrictedReuseArea newArea; freeQubitsInAreas.PushToBack(newArea); } -void CQubitManagerRestrictedReuse::NextRestrictedReuseSegment() +void CQubitManager::NextRestrictedReuseSegment() { FailIf(freeQubitsInAreas.Count() <= 0, "Internal error! No reuse areas."); FailIf(freeQubitsInAreas.Count() == 1, "NextRestrictedReuseSegment() without an active area."); @@ -163,7 +199,7 @@ void CQubitManagerRestrictedReuse::NextRestrictedReuseSegment() currentArea.FreeQubitsReuseProhibited.MoveAllQubitsFrom(currentArea.FreeQubitsReuseAllowed, sharedQubitStatusArray); } -void CQubitManagerRestrictedReuse::EndRestrictedReuseArea() +void CQubitManager::EndRestrictedReuseArea() { FailIf(freeQubitsInAreas.Count() < 2, "EndRestrictedReuseArea() without an active area."); RestrictedReuseArea areaAboutToEnd = freeQubitsInAreas.PopFromBack(); @@ -173,198 +209,220 @@ void CQubitManagerRestrictedReuse::EndRestrictedReuseArea() containingArea.FreeQubitsReuseAllowed.MoveAllQubitsFrom(areaAboutToEnd.FreeQubitsReuseAllowed, sharedQubitStatusArray); } -long CQubitManagerRestrictedReuse::AllocateQubit() +Qubit CQubitManager::Allocate() { - if (EncourageReuse) { - // When reuse is encouraged, we start with the innermost area - for (int i = freeQubitsInAreas.Count() - 1; i >= 0; i--) { - long id = freeQubitsInAreas[i].FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); - if (id != NoneMarker) { - return id; - } - } - } else { - // When reuse is discouraged, we start with the outermost area - for (int i = 0; i < freeQubitsInAreas.Count(); i++) { - long id = freeQubitsInAreas[i].FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); - if (id != NoneMarker) { - return id; - } - } + int newQubitId = AllocateQubitId(); + if (newQubitId == NoneMarker && mayExtendCapacity) + { + int newQubitCapacity = qubitCapacity * 2; + FailIf(newQubitCapacity <= qubitCapacity, "Cannot extend capacity."); + EnsureCapacity(newQubitCapacity); + newQubitId = AllocateQubitId(); } - return NoneMarker; -} - -void CQubitManagerRestrictedReuse::ReleaseQubit(long id) -{ - // Released qubits are added to reuse area/segment in which they were released - // (rather than area/segment where they are allocated). - // Although counterintuitive, this makes code simple. - // This is reasonable because we think qubits should be allocated and released in the same segment. - freeQubitsInAreas.PeekBack().FreeQubitsReuseAllowed.AddQubit(id, EncourageReuse, sharedQubitStatusArray); + FailIf(newQubitId == NoneMarker, "Not enough qubits."); + ChangeStatusToAllocated(newQubitId); + return CreateQubitObject(newQubitId); } -CQubitManagerRestrictedReuse::CQubitManagerRestrictedReuse( - int initialQubitCapacity, - bool mayExtendCapacity, - bool encourageReuse) +void CQubitManager::Allocate(Qubit* qubitsToAllocate, int qubitCountToAllocate) { - qubitCapacity = initialQubitCapacity; - if (qubitCapacity <= 0) { - qubitCapacity = FallbackQubitCapacity; + FailIf(qubitCountToAllocate < 0, "Attempt to allocate negative number of qubits."); + if (qubitCountToAllocate == 0) + { + return; } - sharedQubitStatusArray = new int[qubitCapacity]; - - MayExtendCapacity = mayExtendCapacity; - EncourageReuse = encourageReuse; - - // These objects are passed by value (copies are created) - QubitListInSharedArray FreeQubitsFresh(0, qubitCapacity - 1, sharedQubitStatusArray); - RestrictedReuseArea outermostArea(FreeQubitsFresh); - freeQubitsInAreas.PushToBack(outermostArea); + FailIf(qubitsToAllocate == nullptr, "No array provided for qubits to be allocated."); - AllocatedQubitsCount = 0; - DisabledQubitsCount = 0; - FreeQubitsCount = qubitCapacity; -} - -CQubitManagerRestrictedReuse::~CQubitManagerRestrictedReuse() -{ - if (sharedQubitStatusArray != nullptr) + // Consider optimization for initial allocation of a large array at once + for (int i = 0; i < qubitCountToAllocate; i++) { - delete[] sharedQubitStatusArray; - sharedQubitStatusArray = nullptr; + QubitIdType newQubitId = AllocateQubitId(); + if (newQubitId == NoneMarker) + { + for (int k = 0; k < i; k++) + { + Release(qubitsToAllocate[k]); + } + FailNow("Not enough qubits."); + return; + } + ChangeStatusToAllocated(newQubitId); + qubitsToAllocate[i] = CreateQubitObject(newQubitId); } - // freeQubitsInAreas - direct member of the class, no need to delete. } -void CQubitManagerRestrictedReuse::EnsureCapacity(int requestedCapacity) +void CQubitManager::Release(Qubit qubit) { - // No need to adjust existing values (NonMarker or indexes in the array). - // TODO: Borrowing/returning are not yet supported. - if (requestedCapacity <= qubitCapacity) + FailIf(!IsValid(qubit), "Qubit is not valid."); + Release(QubitToId(qubit)); +} + +void CQubitManager::Release(Qubit* qubitsToRelease, int qubitCountToRelease) { + FailIf(qubitCountToRelease < 0, "Attempt to release negative number of qubits."); + if (qubitCountToRelease == 0) { return; } + FailIf(qubitsToRelease == nullptr, "No array provided for qubits to be released."); - // Prepare new shared status array - int* newStatusArray = new int[requestedCapacity]; - memcpy(newStatusArray, sharedQubitStatusArray, qubitCapacity * sizeof(newStatusArray[0])); - QubitListInSharedArray newFreeQubits(qubitCapacity, requestedCapacity - 1, newStatusArray); + for (int i = 0; i < qubitCountToRelease; i++) + { + Release(qubitsToRelease[i]); + qubitsToRelease[i] = nullptr; + } +} - // Set new data. All new qubits are added to the free qubits in the outermost area. - FreeQubitsCount += requestedCapacity - qubitCapacity; - delete[] sharedQubitStatusArray; - sharedQubitStatusArray = newStatusArray; - qubitCapacity = requestedCapacity; - freeQubitsInAreas[0].FreeQubitsReuseAllowed.MoveAllQubitsFrom(newFreeQubits, sharedQubitStatusArray); +Qubit CQubitManager::Borrow() +{ + // We don't support true borrowing/returning at the moment. + return Allocate(); } -bool CQubitManagerRestrictedReuse::IsDisabled(int id) +void CQubitManager::Borrow(Qubit* qubitsToBorrow, int qubitCountToBorrow) { - return sharedQubitStatusArray[id] == DisabledMarker; + // We don't support true borrowing/returning at the moment. + return Allocate(qubitsToBorrow, qubitCountToBorrow); } -bool CQubitManagerRestrictedReuse::IsDisabled(Qubit qubit) +void CQubitManager::Return(Qubit qubit) { - return IsValid(qubit) && IsDisabled(QubitToId(qubit)); + // We don't support true borrowing/returning at the moment. + Release(qubit); } -void CQubitManagerRestrictedReuse::Disable(Qubit qubit) +void CQubitManager::Return(Qubit* qubitsToReturn, int qubitCountToReturn) +{ + // We don't support true borrowing/returning at the moment. + Release(qubitsToReturn, qubitCountToReturn); +} + +void CQubitManager::Disable(Qubit qubit) { FailIf(!IsValid(qubit), "Invalid qubit."); - int id = QubitToId(qubit); + QubitIdType id = QubitToId(qubit); // We can only disable explicitly allocated qubits that were not borrowed. FailIf(!IsExplicitlyAllocated(id), "Cannot disable qubit that is not explicitly allocated."); sharedQubitStatusArray[id] = DisabledMarker; - DisabledQubitsCount++; + disabledQubitCount++; - AllocatedQubitsCount--; - FailIf(AllocatedQubitsCount < 0, "Incorrect count of allocated qubits."); + allocatedQubitCount--; + FailIf(allocatedQubitCount < 0, "Incorrect count of allocated qubits."); } -void CQubitManagerRestrictedReuse::Disable(Qubit* qubitsToDisable, int qubitCount) +void CQubitManager::Disable(Qubit* qubitsToDisable, int qubitCountToDisable) { - FailIf(qubitCount < 0, "Qubit count cannot be negative"); - if (qubitsToDisable == nullptr || qubitCount == 0) + FailIf(qubitCountToDisable < 0, "Qubit count cannot be negative"); + if (qubitsToDisable == nullptr || qubitCountToDisable == 0) { return; } - for (int i = 0; i < qubitCount; i++) + for (int i = 0; i < qubitCountToDisable; i++) { Disable(qubitsToDisable[i]); } } -bool CQubitManagerRestrictedReuse::IsExplicitlyAllocated(long id) +bool CQubitManager::IsValid(Qubit qubit) const { - return sharedQubitStatusArray[id] == AllocatedMarker; + QubitIdType id = QubitToId(qubit); + if (id >= qubitCapacity) + { + return false; + } + if (id < 0) + { + return false; + } + return true; } -void CQubitManagerRestrictedReuse::ChangeStatusToAllocated(long id) +bool CQubitManager::IsDisabled(Qubit qubit) const { - FailIf(id >= qubitCapacity, "Internal Error: Cannot change status of an invalid qubit."); - sharedQubitStatusArray[id] = AllocatedMarker; - AllocatedQubitsCount++; - FreeQubitsCount--; + return IsValid(qubit) && IsDisabled(QubitToId(qubit)); } -Qubit CQubitManagerRestrictedReuse::Allocate() +bool CQubitManager::IsFree(Qubit qubit) const { - long newQubitId = AllocateQubit(); - if (newQubitId == NoneMarker && MayExtendCapacity) - { - EnsureCapacity(qubitCapacity * 2); - newQubitId = AllocateQubit(); - } - FailIf(newQubitId == NoneMarker, "Not enough qubits."); - ChangeStatusToAllocated(newQubitId); - return CreateQubitObject(newQubitId); + return IsValid(qubit) && IsFree(QubitToId(qubit)); +} + +Qubit CQubitManager::CreateQubitObject(QubitIdType id) +{ + FailIf(id < 0 || id > INTPTR_MAX, "Qubit id is out of range."); + intptr_t pointerSizedId = static_cast(id); + return reinterpret_cast(pointerSizedId); +} + +QubitIdType CQubitManager::QubitToId(Qubit qubit) const +{ + intptr_t pointerSizedId = reinterpret_cast(qubit); + FailIf(pointerSizedId < 0 || pointerSizedId > std::numeric_limits::max(), "Qubit id is out of range."); + return static_cast(pointerSizedId); } -Qubit* CQubitManagerRestrictedReuse::Allocate(long numToAllocate) +void CQubitManager::EnsureCapacity(QubitIdType requestedCapacity) { - FailIf(numToAllocate < 0, "Attempt to allocate negative number of qubits."); - if (numToAllocate == 0) + // No need to adjust existing values (NonMarker or indexes in the array). + // TODO: Borrowing/returning are not yet supported. + if (requestedCapacity <= qubitCapacity) { - return new Qubit[0]; + return; } - // Consider optimization for initial allocation of a large array at once - Qubit* result = new Qubit[numToAllocate]; - for (int i = 0; i < numToAllocate; i++) - { - int newQubitId = AllocateQubit(); - if (newQubitId == NoneMarker) - { - for (int k = 0; k < i; k++) - { - Release(result[k]); + // Prepare new shared status array + QubitIdType* newStatusArray = new int[requestedCapacity]; + memcpy(newStatusArray, sharedQubitStatusArray, qubitCapacity * sizeof(newStatusArray[0])); + QubitListInSharedArray newFreeQubits(qubitCapacity, requestedCapacity - 1, newStatusArray); + + // Set new data. All new qubits are added to the free qubits in the outermost area. + freeQubitCount += requestedCapacity - qubitCapacity; + delete[] sharedQubitStatusArray; + sharedQubitStatusArray = newStatusArray; + qubitCapacity = requestedCapacity; + freeQubitsInAreas[0].FreeQubitsReuseAllowed.MoveAllQubitsFrom(newFreeQubits, sharedQubitStatusArray); +} + +QubitIdType CQubitManager::AllocateQubitId() +{ + if (encourageReuse) { + // When reuse is encouraged, we start with the innermost area + for (int i = freeQubitsInAreas.Count() - 1; i >= 0; i--) { + QubitIdType id = freeQubitsInAreas[i].FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); + if (id != NoneMarker) { + return id; + } + } + } else { + // When reuse is discouraged, we start with the outermost area + for (int i = 0; i < freeQubitsInAreas.Count(); i++) { + QubitIdType id = freeQubitsInAreas[i].FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); + if (id != NoneMarker) { + return id; } - delete[] result; - result = nullptr; - FailNow("Not enough qubits."); - return nullptr; } - ChangeStatusToAllocated(newQubitId); - result[i] = CreateQubitObject(newQubitId); } - - return result; + return NoneMarker; } -bool CQubitManagerRestrictedReuse::IsFree(long id) +void CQubitManager::ReleaseQubitId(QubitIdType id) { - return sharedQubitStatusArray[id] >= 0; + // Released qubits are added to reuse area/segment in which they were released + // (rather than area/segment where they are allocated). + // Although counterintuitive, this makes code simple. + // This is reasonable because we think qubits should be allocated and released in the same segment. + freeQubitsInAreas.PeekBack().FreeQubitsReuseAllowed.AddQubit(id, encourageReuse, sharedQubitStatusArray); } -bool CQubitManagerRestrictedReuse::IsFree(Qubit qubit) +void CQubitManager::ChangeStatusToAllocated(QubitIdType id) { - return IsValid(qubit) && IsFree(QubitToId(qubit)); + FailIf(id >= qubitCapacity, "Internal Error: Cannot change status of an invalid qubit."); + sharedQubitStatusArray[id] = AllocatedMarker; + allocatedQubitCount++; + freeQubitCount--; } -void CQubitManagerRestrictedReuse::Release(long id) +void CQubitManager::Release(QubitIdType id) { if (IsDisabled(id)) { @@ -374,94 +432,34 @@ void CQubitManagerRestrictedReuse::Release(long id) FailIf(!IsExplicitlyAllocated(id), "Attempt to free qubit that has not been allocated."); - if (MayExtendCapacity && !EncourageReuse) + if (mayExtendCapacity && !encourageReuse) { // We can extend capcity and don't want reuse => Qubits will never be reused => Discard qubit. // We put it in its own "free" list, this list will never be found again and qubit will not be reused. sharedQubitStatusArray[id] = NoneMarker; } else { - ReleaseQubit(id); - } - - FreeQubitsCount++; - AllocatedQubitsCount--; - FailIf(AllocatedQubitsCount < 0, "Incorrect allocated qubit count."); -} - -void CQubitManagerRestrictedReuse::Release(Qubit qubit) -{ - FailIf(!IsValid(qubit), "Qubit is not valid."); - Release(QubitToId(qubit)); - // TODO: should we clean id of released qubit object? -} - -void CQubitManagerRestrictedReuse::Release(Qubit* qubitsToRelease, int qubitCount) { - FailIf(qubitCount < 0, "Attempt to release negative number of qubits."); - if (qubitsToRelease == nullptr) - { - return; - } - - for (int i = 0; i < qubitCount; i++) - { - Release(qubitsToRelease[i]); - qubitsToRelease[i] = nullptr; + ReleaseQubitId(id); } - delete[] qubitsToRelease; + freeQubitCount++; + allocatedQubitCount--; + FailIf(allocatedQubitCount < 0, "Incorrect allocated qubit count."); } -Qubit CQubitManagerRestrictedReuse::Borrow() +bool CQubitManager::IsDisabled(QubitIdType id) const { - // We don't support true borrowing/returning at the moment. - return Allocate(); -} - -Qubit* CQubitManagerRestrictedReuse::Borrow(long qubitCountToBorrow) -{ - // We don't support true borrowing/returning at the moment. - return Allocate(qubitCountToBorrow); -} - -void CQubitManagerRestrictedReuse::Return(Qubit qubit) -{ - // We don't support true borrowing/returning at the moment. - Release(qubit); -} - -void CQubitManagerRestrictedReuse::Return(Qubit* qubitsToReturn, int qubitCountToReturn) -{ - // We don't support true borrowing/returning at the moment. - Release(qubitsToReturn, qubitCountToReturn); -} - -bool CQubitManagerRestrictedReuse::IsValid(Qubit qubit) -{ - int id = QubitToId(qubit); - if (id >= qubitCapacity) - { - return false; - } - if (id < 0) - { - return false; - } - return true; + return sharedQubitStatusArray[id] == DisabledMarker; } -Qubit CQubitManagerRestrictedReuse::CreateQubitObject(int id) +bool CQubitManager::IsExplicitlyAllocated(QubitIdType id) const { - FailIf(id < 0 || id > INTPTR_MAX, "Qubit id out of range."); - intptr_t pointerSizedId = static_cast(id); - return reinterpret_cast(pointerSizedId); + return sharedQubitStatusArray[id] == AllocatedMarker; } -int CQubitManagerRestrictedReuse::QubitToId(Qubit qubit) +bool CQubitManager::IsFree(QubitIdType id) const { - intptr_t pointerSizedId = reinterpret_cast(qubit); - FailIf(pointerSizedId < 0 || pointerSizedId > INT_MAX, "Qubit id out of range."); - return static_cast(pointerSizedId); + return sharedQubitStatusArray[id] >= 0; } } diff --git a/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.hpp b/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.hpp index c4b7ab539b7..523b03eeb93 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.hpp +++ b/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.hpp @@ -1,9 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#include +// Qubit Manager maintains mapping between user qubit objects and +// underlying qubit identifiers (Ids). When user program allocates +// a qubit, Qubit Manager decides whether to allocate a fresh id or +// reuse existing id that was previously freed. When user program +// releases a qubit, Qubit Manager tracks it as a free qubit id. +// Decision to reuse a qubit id is influenced by restricted reuse +// areas. When a qubit id is freed in one section of a restricted +// reuse area, it cannot be reused in other section of the same area. +// Borrowing of qubits is not supported and is currently implemented +// as plain allocation. + #include #include +#include #include "CoreTypes.hpp" @@ -20,64 +31,59 @@ namespace Quantum } } - - // TODO: Decide on the interface and move this to appropriate place. - struct IQubitManager - { - virtual ~IQubitManager() = default; - virtual Qubit Allocate() = 0; - virtual void Release(Qubit qubit) = 0; - }; + using QubitIdType = ::int32_t; // The end of free lists are marked with NoneMarker value. It is used like null for pointers. // This value is non-negative just like other values in the free lists. - constexpr int NoneMarker = INT_MAX; + constexpr QubitIdType NoneMarker = std::numeric_limits::max(); // Explicitly allocated qubits are marked with Allocated value. // Qubits implicitly allocated for borrowing are "refcounted" with values [Allocated+1 .. -2]. // When refcount needs to be decreased to Allocated value, the qubit is automatically released. - constexpr int AllocatedMarker = INT_MAX; + constexpr QubitIdType AllocatedMarker = std::numeric_limits::max(); // Disabled qubits are marked with this value. - constexpr int DisabledMarker = -1; + constexpr QubitIdType DisabledMarker = -1; // We want status array to be reasonably large if initialization for it is wrong (i.e. requested size < 1). - constexpr int FallbackQubitCapacity = 8; + constexpr QubitIdType FallbackQubitCapacity = 8; - // Indexes in the status array can potentially be in range 0 .. long.MaxValue-1. - // This gives maximum capacity as long.MaxValue. - // Index equal to long.MaxValue doesn't exist and is reserved for 'NoneMarker' - list terminator. - constexpr int MaximumQubitCapacity = INT_MAX; + // Indexes in the status array can potentially be in range 0 .. int.MaxValue-1. + // This gives maximum capacity as int.MaxValue. + // Index equal to int.MaxValue doesn't exist and is reserved for 'NoneMarker' - list terminator. + constexpr QubitIdType MaximumQubitCapacity = std::numeric_limits::max(); // QubitListInSharedArray implements a singly-linked list with "pointers" to the first and the last element stored. // Pointers are the indexes in a shared array. Shared array isn't sotored here because it can be reallocated. // This class maintains status of elements in the list by virtue of linking them as part of this list. // This class doesn't update status of elementes excluded from the list. - // This class is small, contains no pointers and relies on default shallow copying/destruction. + // This class is small, contains no C++ pointers and relies on default shallow copying/destruction. struct QubitListInSharedArray final { private: - int firstElement = NoneMarker; - int lastElement = NoneMarker; + QubitIdType firstElement = NoneMarker; + QubitIdType lastElement = NoneMarker; + // We are not storing pointer to shared array because it can be reallocated. + // Indexes and special values remain the same on such reallocations. public: // Initialize empty list QubitListInSharedArray() = default; - // Initialize as a list with sequential elements from minElement to maxElement inclusve. - QubitListInSharedArray(int minElement, int maxElement, int* sharedQubitStatusArray); + // Initialize as a list with sequential elements from startId to endId inclusve. + QubitListInSharedArray(QubitIdType startId, QubitIdType endId, QubitIdType* sharedQubitStatusArray); - bool IsEmpty(); - void AddQubit(int id, bool addToFront, int* sharedQubitStatusArray); - int TakeQubitFromFront(int* sharedQubitStatusArray); - void MoveAllQubitsFrom(QubitListInSharedArray& source, int* sharedQubitStatusArray); + bool IsEmpty() const; + void AddQubit(QubitIdType id, bool addToFront, QubitIdType* sharedQubitStatusArray); + int TakeQubitFromFront(QubitIdType* sharedQubitStatusArray); + void MoveAllQubitsFrom(QubitListInSharedArray& source, QubitIdType* sharedQubitStatusArray); }; // Restricted reuse area consists of multiple segments. Qubits released in one segment cannot be reused in another. // One restricted reuse area can be nested in a segment of another restricted reuse area. // This class tracks current segment of an area. Previous segments are tracked collectively (not individually). - // This class is small, contains no pointers and relies on default shallow copying/destruction. + // This class is small, contains no C++ pointers and relies on default shallow copying/destruction. struct RestrictedReuseArea final { public: @@ -105,104 +111,106 @@ namespace Quantum RestrictedReuseArea PopFromBack(); RestrictedReuseArea& PeekBack(); // TODO: Remove and use size directly? - int Count(); + int Count() const; }; - class CQubitManagerRestrictedReuse : public IQubitManager + class CQubitManager { - // Restricted reuse area control, allocation and release - - public: void StartRestrictedReuseArea(); - public: void NextRestrictedReuseSegment(); - public: void EndRestrictedReuseArea(); - private: long AllocateQubit(); // Computation complexity is O(number of nested restricted reuse areas). - private: void ReleaseQubit(long id); - - // Configuration Properties - - // TODO: Make sure these are read only - public: bool MayExtendCapacity; - public: bool EncourageReuse; - - // Constructors/destructors, storage and reallocation - - private: int* sharedQubitStatusArray; // Tracks allocation state of all qubits. Stores lists of free qubits. - private: int qubitCapacity = 0; // qubitCapacity is always equal to the array size. - private: CRestrictedReuseAreaStack freeQubitsInAreas; // Fresh Free Qubits are located in freeQubitsInAreas[0].FreeQubitsReuseAllowed - - public: CQubitManagerRestrictedReuse( - int initialQubitCapacity, + public: + CQubitManager( + QubitIdType initialQubitCapacity, bool mayExtendCapacity = false, bool encourageReuse = true); // No complex scenarios for now. Don't need to support copying/moving. - public: CQubitManagerRestrictedReuse(const CQubitManagerRestrictedReuse&) = delete; - public: CQubitManagerRestrictedReuse& operator = (const CQubitManagerRestrictedReuse&) = delete; - public: ~CQubitManagerRestrictedReuse(); - private: void EnsureCapacity(int requestedCapacity); - - // Disable, disabled qubit count and checks + CQubitManager(const CQubitManager&) = delete; + CQubitManager& operator = (const CQubitManager&) = delete; + ~CQubitManager(); + + // Restricted reuse area control + void StartRestrictedReuseArea(); + void NextRestrictedReuseSegment(); + void EndRestrictedReuseArea(); + + // Allocate a qubit. Extend capacity if necessary and possible. + // Fail if the qubit cannot be allocated. + // Computation complexity is O(number of nested restricted reuse areas). + Qubit Allocate(); + // Allocate qubitCountToAllocate qubits and store them in the provided array. Extend manager capacity if necessary and possible. + // Fail without allocating any qubits if the qubits cannot be allocated. + // Caller is responsible for providing array of sufficient size to hold qubitCountToAllocate. + void Allocate(Qubit* qubitsToAllocate, int qubitCountToAllocate); - public: int DisabledQubitsCount = 0; - public: bool IsDisabled(int id); - public: bool IsDisabled(Qubit qubit); - // Disables a given qubit. - // Once a qubit is disabled it can never be "enabled" or reallocated. - public: void Disable(Qubit qubit); - // Disables a set of given qubits. - // Once a qubit is disabled it can never be "enabled" or reallocated. - public: void Disable(Qubit* qubitsToDisable, int qubitCount); - - // Allocate, allocated qubit count and checks - - public: int AllocatedQubitsCount = 0; - public: bool IsExplicitlyAllocated(long id); - public: void ChangeStatusToAllocated(long id); - // Allocates a qubit. Extend capacity if necessary and possible. - // Fails if the qubit cannot be allocated. - public: Qubit Allocate(); - // Allocates numToAllocate new qubits. Extend capacity if necessary and possible. - // Fails without allocating any qubits if the qubits cannot be allocated. - // Returned array of qubits must be passed to Release to release qubits and free memory use for array. - public: Qubit* Allocate(long numToAllocate); - - // Release, free qubit count and checks - - public: long FreeQubitsCount = 0; - public: bool IsFree(long id); - public: bool IsFree(Qubit qubit); - public: void Release(long id); // Releases a given qubit. - public: void Release(Qubit qubit); - // Releases an array of given qubits and deallocates array memory. - public: void Release(Qubit* qubitsToRelease, int qubitCount); + void Release(Qubit qubit); + // Releases qubitCountToRelease qubits in the provided array. + // Caller is responsible for managing memory used by the array itself (i.e. delete[] array if it was dynamically allocated). + void Release(Qubit* qubitsToRelease, int qubitCountToRelease); // Borrow (We treat borrowing as allocation currently) - - public: Qubit Borrow(); - public: Qubit* Borrow(long qubitCountToBorrow); - + Qubit Borrow(); + void Borrow(Qubit* qubitsToBorrow, int qubitCountToBorrow); // Return (We treat returning as release currently) + void Return(Qubit qubit); + void Return(Qubit* qubitsToReturn, int qubitCountToReturn); - public: void Return(Qubit qubit); - public: void Return(Qubit* qubitsToReturn, int qubitCountToReturn); - - // Qubit creation, validity check + // Disables a given qubit. + // Once a qubit is disabled it can never be "enabled" or reallocated. + void Disable(Qubit qubit); + // Disables a set of given qubits. + // Once a qubit is disabled it can never be "enabled" or reallocated. + void Disable(Qubit* qubitsToDisable, int qubitCountToDisable); - public: bool IsValid(Qubit qubit); + bool IsValid(Qubit qubit) const; + bool IsDisabled(Qubit qubit) const; + bool IsFree(Qubit qubit) const; // May be overriden to create a custom Qubit object. // When not overriden, it just stores qubit Id in place of a pointer to a qubit. // id: unique qubit id // Returns a newly instantiated qubit. - public: virtual Qubit CreateQubitObject(int id); + virtual Qubit CreateQubitObject(QubitIdType id); // May be overriden to get a qubit id from a custom qubit object. // Must be overriden if CreateQubitObject is overriden. // When not overriden, it just reinterprets pointer to qubit as a qubit id. // qubit: pointer to QUBIT // Returns id of a qubit pointed to by qubit. - public: virtual int QubitToId(Qubit qubit); + virtual QubitIdType QubitToId(Qubit qubit) const; + + // Qubit counts + int GetDisabledQubitCount() const { return disabledQubitCount; } + int GetAllocatedQubitCount() const { return allocatedQubitCount; } + int GetFreeQubitCount() const { return freeQubitCount; } + + bool GetMayExtendCapacity() const { return mayExtendCapacity; } + bool GetEncourageReuse() const { return encourageReuse; } + + private: + void EnsureCapacity(QubitIdType requestedCapacity); + + QubitIdType AllocateQubitId(); + void ReleaseQubitId(QubitIdType id); + void ChangeStatusToAllocated(QubitIdType id); + void Release(QubitIdType id); + + bool IsDisabled(QubitIdType id) const; + bool IsExplicitlyAllocated(QubitIdType id) const; + bool IsFree(QubitIdType id) const; + + // Configuration Properties + bool mayExtendCapacity; + bool encourageReuse; + + // State + QubitIdType* sharedQubitStatusArray; // Tracks allocation state of all qubits. Stores lists of free qubits. + QubitIdType qubitCapacity; // qubitCapacity is always equal to the array size. + CRestrictedReuseAreaStack freeQubitsInAreas; // Fresh Free Qubits are located in freeQubitsInAreas[0].FreeQubitsReuseAllowed + + // Counts + int disabledQubitCount; + int allocatedQubitCount; + int freeQubitCount; }; diff --git a/src/Qir/Runtime/unittests/QubitManagerTests.cpp b/src/Qir/Runtime/unittests/QubitManagerTests.cpp index 965c5d4b9a4..8a86af1bf33 100644 --- a/src/Qir/Runtime/unittests/QubitManagerTests.cpp +++ b/src/Qir/Runtime/unittests/QubitManagerTests.cpp @@ -11,47 +11,47 @@ using namespace Microsoft::Quantum; TEST_CASE("Simple allocation and release of one qubit", "[QubitManagerBasic]") { - std::unique_ptr qm = std::make_unique(1); + std::unique_ptr qm = std::make_unique(1); Qubit q = qm->Allocate(); qm->Release(q); } TEST_CASE("Allocation and reallocation of one qubit", "[QubitManagerBasic]") { - std::unique_ptr qm = std::make_unique(1); - REQUIRE(qm->FreeQubitsCount == 1); - REQUIRE(qm->AllocatedQubitsCount == 0); + std::unique_ptr qm = std::make_unique(1); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 0); Qubit q = qm->Allocate(); REQUIRE(qm->QubitToId(q) == 0); - REQUIRE(qm->FreeQubitsCount == 0); - REQUIRE(qm->AllocatedQubitsCount == 1); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 1); REQUIRE_THROWS(qm->Allocate()); - REQUIRE(qm->FreeQubitsCount == 0); - REQUIRE(qm->AllocatedQubitsCount == 1); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 1); qm->Release(q); - REQUIRE(qm->FreeQubitsCount == 1); - REQUIRE(qm->AllocatedQubitsCount == 0); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 0); Qubit q0 = qm->Allocate(); REQUIRE(qm->QubitToId(q0) == 0); - REQUIRE(qm->FreeQubitsCount == 0); - REQUIRE(qm->AllocatedQubitsCount == 1); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 1); qm->Release(q0); - REQUIRE(qm->FreeQubitsCount == 1); - REQUIRE(qm->AllocatedQubitsCount == 0); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 0); } TEST_CASE("Allocation of released qubits when reuse is encouraged", "[QubitManagerBasic]") { - std::unique_ptr qm = std::make_unique(2); - REQUIRE(qm->FreeQubitsCount == 2); + std::unique_ptr qm = std::make_unique(2); + REQUIRE(qm->GetFreeQubitCount() == 2); Qubit q0 = qm->Allocate(); Qubit q1 = qm->Allocate(); REQUIRE(qm->QubitToId(q0) == 0); // Qubit ids should be in order REQUIRE(qm->QubitToId(q1) == 1); REQUIRE_THROWS(qm->Allocate()); - REQUIRE(qm->FreeQubitsCount == 0); - REQUIRE(qm->AllocatedQubitsCount == 2); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 2); qm->Release(q0); Qubit q0a = qm->Allocate(); @@ -60,16 +60,16 @@ TEST_CASE("Allocation of released qubits when reuse is encouraged", "[QubitManag qm->Release(q1); qm->Release(q0a); - REQUIRE(qm->FreeQubitsCount == 2); - REQUIRE(qm->AllocatedQubitsCount == 0); + REQUIRE(qm->GetFreeQubitCount() == 2); + REQUIRE(qm->GetAllocatedQubitCount() == 0); Qubit q0b = qm->Allocate(); Qubit q1a = qm->Allocate(); REQUIRE(qm->QubitToId(q0b) == 0); // By default reuse is encouraged, LIFO is used REQUIRE(qm->QubitToId(q1a) == 1); REQUIRE_THROWS(qm->Allocate()); - REQUIRE(qm->FreeQubitsCount == 0); - REQUIRE(qm->AllocatedQubitsCount == 2); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 2); qm->Release(q0b); qm->Release(q1a); @@ -77,40 +77,42 @@ TEST_CASE("Allocation of released qubits when reuse is encouraged", "[QubitManag TEST_CASE("Extending capacity", "[QubitManager]") { - std::unique_ptr qm = std::make_unique(1, true); + std::unique_ptr qm = std::make_unique(1, true); Qubit q0 = qm->Allocate(); REQUIRE(qm->QubitToId(q0) == 0); Qubit q1 = qm->Allocate(); // This should double capacity REQUIRE(qm->QubitToId(q1) == 1); - REQUIRE(qm->FreeQubitsCount == 0); - REQUIRE(qm->AllocatedQubitsCount == 2); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 2); qm->Release(q0); Qubit q0a = qm->Allocate(); REQUIRE(qm->QubitToId(q0a) == 0); Qubit q2 = qm->Allocate(); // This should double capacity again REQUIRE(qm->QubitToId(q2) == 2); - REQUIRE(qm->FreeQubitsCount == 1); - REQUIRE(qm->AllocatedQubitsCount == 3); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 3); qm->Release(q1); qm->Release(q0a); qm->Release(q2); - REQUIRE(qm->FreeQubitsCount == 4); - REQUIRE(qm->AllocatedQubitsCount == 0); + REQUIRE(qm->GetFreeQubitCount() == 4); + REQUIRE(qm->GetAllocatedQubitCount() == 0); - Qubit* qqq = qm->Allocate(3); - REQUIRE(qm->FreeQubitsCount == 1); - REQUIRE(qm->AllocatedQubitsCount == 3); + Qubit* qqq = new Qubit[3]; + qm->Allocate(qqq, 3); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 3); qm->Release(qqq, 3); - REQUIRE(qm->FreeQubitsCount == 4); - REQUIRE(qm->AllocatedQubitsCount == 0); + delete[] qqq; + REQUIRE(qm->GetFreeQubitCount() == 4); + REQUIRE(qm->GetAllocatedQubitCount() == 0); } TEST_CASE("Restricted Area", "[QubitManager]") { - std::unique_ptr qm = std::make_unique(3, false, true); + std::unique_ptr qm = std::make_unique(3, false, true); Qubit q0 = qm->Allocate(); REQUIRE(qm->QubitToId(q0) == 0); @@ -141,7 +143,8 @@ TEST_CASE("Restricted Area", "[QubitManager]") qm->EndRestrictedReuseArea(); // Qubits 1 and 2 are available here again. - Qubit* qqq = qm->Allocate(2); + Qubit* qqq = new Qubit[2]; + qm->Allocate(qqq, 2); // OK to destruct qubit manager while qubits are still allocated. REQUIRE_THROWS(qm->Allocate()); } \ No newline at end of file From 74d331f0b92b24c560ad6bf24f8e458af1780ec3 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Tue, 25 May 2021 01:10:29 -0700 Subject: [PATCH 03/19] include cstring for memcpy --- src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp b/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp index 409133f04c4..34391cfbf4a 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp @@ -5,6 +5,8 @@ // This code compiles, passes some unit tests, but isn't yet used in the runtime. #include "QubitManagerRestrictedReuse.hpp" +#include + namespace Microsoft { From afbe47e5fc7977b74e645fd34e8374ac9f062e5b Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Tue, 25 May 2021 19:56:43 -0700 Subject: [PATCH 04/19] Renamed QubitManager --- .../lib/QIR/{QubitManagerRestrictedReuse.cpp => QubitManager.cpp} | 0 .../lib/QIR/{QubitManagerRestrictedReuse.hpp => QubitManager.hpp} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/Qir/Runtime/lib/QIR/{QubitManagerRestrictedReuse.cpp => QubitManager.cpp} (100%) rename src/Qir/Runtime/lib/QIR/{QubitManagerRestrictedReuse.hpp => QubitManager.hpp} (100%) diff --git a/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp similarity index 100% rename from src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.cpp rename to src/Qir/Runtime/lib/QIR/QubitManager.cpp diff --git a/src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.hpp b/src/Qir/Runtime/lib/QIR/QubitManager.hpp similarity index 100% rename from src/Qir/Runtime/lib/QIR/QubitManagerRestrictedReuse.hpp rename to src/Qir/Runtime/lib/QIR/QubitManager.hpp From dbfcdef2b7f96d3fbeabdd0cf6dcb8490a1ff203 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Tue, 25 May 2021 22:40:52 -0700 Subject: [PATCH 05/19] Adjustment after QubitManager rename --- src/Qir/Runtime/lib/QIR/CMakeLists.txt | 2 +- src/Qir/Runtime/lib/QIR/QubitManager.cpp | 4 ++-- src/Qir/Runtime/unittests/QubitManagerTests.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Qir/Runtime/lib/QIR/CMakeLists.txt b/src/Qir/Runtime/lib/QIR/CMakeLists.txt index e924bce54f0..c9812d73d6b 100644 --- a/src/Qir/Runtime/lib/QIR/CMakeLists.txt +++ b/src/Qir/Runtime/lib/QIR/CMakeLists.txt @@ -19,7 +19,7 @@ set(rt_sup_source_files delegated.cpp strings.cpp utils.cpp - QubitManagerRestrictedReuse.cpp + QubitManager.cpp ) # Produce object lib we'll use to create a shared lib (so/dll) later on diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp index 34391cfbf4a..d5ed1a03859 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManager.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -4,9 +4,9 @@ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // This code compiles, passes some unit tests, but isn't yet used in the runtime. -#include "QubitManagerRestrictedReuse.hpp" -#include +#include "QubitManager.hpp" +#include // For memcpy namespace Microsoft { diff --git a/src/Qir/Runtime/unittests/QubitManagerTests.cpp b/src/Qir/Runtime/unittests/QubitManagerTests.cpp index 8a86af1bf33..f89aaa704be 100644 --- a/src/Qir/Runtime/unittests/QubitManagerTests.cpp +++ b/src/Qir/Runtime/unittests/QubitManagerTests.cpp @@ -5,7 +5,7 @@ #include "catch.hpp" -#include "QubitManagerRestrictedReuse.hpp" +#include "QubitManager.hpp" using namespace Microsoft::Quantum; From 1af1260584cc8ddaf80973cc7d4d62cf64c3c2cb Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 26 May 2021 13:38:40 -0700 Subject: [PATCH 06/19] Moved QubitManager.h to public --- src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp | 7 +++++++ src/Qir/Runtime/{lib/QIR => public}/QubitManager.hpp | 0 2 files changed, 7 insertions(+) rename src/Qir/Runtime/{lib/QIR => public}/QubitManager.hpp (100%) diff --git a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp index 6d9103d6779..c1c63e25e72 100644 --- a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp +++ b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp @@ -17,6 +17,7 @@ #include "QSharpSimApi_I.hpp" #include "SimFactory.hpp" #include "OutputStream.hpp" +#include "QubitManager.hpp" #ifdef _WIN32 #include @@ -108,6 +109,7 @@ namespace Quantum const QUANTUM_SIMULATOR handle = 0; unsigned simulatorId = -1; unsigned nextQubitId = 0; // the QuantumSimulator expects contiguous ids, starting from 0 + //CQubitManager* qubitManager = nullptr; unsigned GetQubitId(Qubit qubit) const { @@ -156,6 +158,7 @@ namespace Quantum typedef unsigned (*TInit)(); static TInit initSimulatorInstance = reinterpret_cast(this->GetProc("init")); + CQubitManager* qubitManager = new CQubitManager(4); this->simulatorId = initSimulatorInstance(); } ~CFullstateSimulator() @@ -172,6 +175,10 @@ namespace Quantum // unload it might crash. // UnloadQuantumSimulator(this->handle); } + //if (qubitManager != nullptr) { + // delete qubitManager; + // qubitManager = nullptr; + //} } // Deprecated, use `DumpMachine()` and `DumpRegister()` instead. diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp similarity index 100% rename from src/Qir/Runtime/lib/QIR/QubitManager.hpp rename to src/Qir/Runtime/public/QubitManager.hpp From a14a652ee93622f51466e3b36632b7898999e0cf Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 26 May 2021 14:18:46 -0700 Subject: [PATCH 07/19] Using QubitManager, adjusted defaults, minor fixes --- src/Qir/Runtime/lib/QIR/QubitManager.cpp | 2 +- .../lib/Simulators/FullstateSimulator.cpp | 30 ++++++++++--------- src/Qir/Runtime/public/QubitManager.hpp | 12 ++++---- .../Runtime/unittests/QubitManagerTests.cpp | 8 ++--- 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp index d5ed1a03859..5ab3fba33a4 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManager.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -161,7 +161,7 @@ CQubitManager::CQubitManager( qubitCapacity = initialQubitCapacity; if (qubitCapacity <= 0) { - qubitCapacity = FallbackQubitCapacity; + qubitCapacity = DefaultQubitCapacity; } sharedQubitStatusArray = new int[qubitCapacity]; diff --git a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp index c1c63e25e72..56bb5e4fd25 100644 --- a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp +++ b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp @@ -108,12 +108,13 @@ namespace Quantum const QUANTUM_SIMULATOR handle = 0; unsigned simulatorId = -1; - unsigned nextQubitId = 0; // the QuantumSimulator expects contiguous ids, starting from 0 - //CQubitManager* qubitManager = nullptr; + // the QuantumSimulator expects contiguous ids, starting from 0 + CQubitManager* qubitManager = nullptr; unsigned GetQubitId(Qubit qubit) const { - return static_cast(reinterpret_cast(qubit)); + // Qubit manager uses unsigned range of int32_t for qubit ids. + return static_cast(qubitManager->QubitToId(qubit)); } std::vector GetQubitIds(long num, Qubit* qubits) const @@ -122,7 +123,7 @@ namespace Quantum ids.reserve(num); for (long i = 0; i < num; i++) { - ids.push_back(static_cast(reinterpret_cast(qubits[i]))); + ids.push_back(GetQubitId(qubits[i])); } return ids; } @@ -158,7 +159,7 @@ namespace Quantum typedef unsigned (*TInit)(); static TInit initSimulatorInstance = reinterpret_cast(this->GetProc("init")); - CQubitManager* qubitManager = new CQubitManager(4); + qubitManager = new CQubitManager(); this->simulatorId = initSimulatorInstance(); } ~CFullstateSimulator() @@ -175,10 +176,10 @@ namespace Quantum // unload it might crash. // UnloadQuantumSimulator(this->handle); } - //if (qubitManager != nullptr) { - // delete qubitManager; - // qubitManager = nullptr; - //} + if (qubitManager != nullptr) { + delete qubitManager; + qubitManager = nullptr; + } } // Deprecated, use `DumpMachine()` and `DumpRegister()` instead. @@ -202,10 +203,10 @@ namespace Quantum typedef void (*TAllocateQubit)(unsigned, unsigned); static TAllocateQubit allocateQubit = reinterpret_cast(this->GetProc("allocateQubit")); - const unsigned id = this->nextQubitId; - allocateQubit(this->simulatorId, id); - this->nextQubitId++; - return reinterpret_cast(id); + Qubit q = qubitManager->Allocate(); // Allocate qubit in qubit manager. + unsigned id = GetQubitId(q); // Get its id. + allocateQubit(this->simulatorId, id); // Allocate it in the simulator. + return q; } void ReleaseQubit(Qubit q) override @@ -213,7 +214,8 @@ namespace Quantum typedef void (*TReleaseQubit)(unsigned, unsigned); static TReleaseQubit releaseQubit = reinterpret_cast(this->GetProc("release")); - releaseQubit(this->simulatorId, GetQubitId(q)); + releaseQubit(this->simulatorId, GetQubitId(q)); // Release qubit in the simulator. + qubitManager->Release(q); // Release it in the qubit manager. } Result Measure(long numBases, PauliId bases[], long numTargets, Qubit targets[]) override diff --git a/src/Qir/Runtime/public/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp index 523b03eeb93..666c116f932 100644 --- a/src/Qir/Runtime/public/QubitManager.hpp +++ b/src/Qir/Runtime/public/QubitManager.hpp @@ -12,6 +12,8 @@ // Borrowing of qubits is not supported and is currently implemented // as plain allocation. +#pragma once + #include #include #include @@ -45,8 +47,8 @@ namespace Quantum // Disabled qubits are marked with this value. constexpr QubitIdType DisabledMarker = -1; - // We want status array to be reasonably large if initialization for it is wrong (i.e. requested size < 1). - constexpr QubitIdType FallbackQubitCapacity = 8; + // We want status array to be reasonably large. + constexpr QubitIdType DefaultQubitCapacity = 8; // Indexes in the status array can potentially be in range 0 .. int.MaxValue-1. // This gives maximum capacity as int.MaxValue. @@ -114,12 +116,12 @@ namespace Quantum int Count() const; }; - class CQubitManager + class QIR_SHARED_API CQubitManager { public: CQubitManager( - QubitIdType initialQubitCapacity, - bool mayExtendCapacity = false, + QubitIdType initialQubitCapacity = DefaultQubitCapacity, + bool mayExtendCapacity = true, bool encourageReuse = true); // No complex scenarios for now. Don't need to support copying/moving. diff --git a/src/Qir/Runtime/unittests/QubitManagerTests.cpp b/src/Qir/Runtime/unittests/QubitManagerTests.cpp index f89aaa704be..aa13a47768b 100644 --- a/src/Qir/Runtime/unittests/QubitManagerTests.cpp +++ b/src/Qir/Runtime/unittests/QubitManagerTests.cpp @@ -11,14 +11,14 @@ using namespace Microsoft::Quantum; TEST_CASE("Simple allocation and release of one qubit", "[QubitManagerBasic]") { - std::unique_ptr qm = std::make_unique(1); + std::unique_ptr qm = std::make_unique(); Qubit q = qm->Allocate(); qm->Release(q); } TEST_CASE("Allocation and reallocation of one qubit", "[QubitManagerBasic]") { - std::unique_ptr qm = std::make_unique(1); + std::unique_ptr qm = std::make_unique(1, false, true); REQUIRE(qm->GetFreeQubitCount() == 1); REQUIRE(qm->GetAllocatedQubitCount() == 0); Qubit q = qm->Allocate(); @@ -43,7 +43,7 @@ TEST_CASE("Allocation and reallocation of one qubit", "[QubitManagerBasic]") TEST_CASE("Allocation of released qubits when reuse is encouraged", "[QubitManagerBasic]") { - std::unique_ptr qm = std::make_unique(2); + std::unique_ptr qm = std::make_unique(2, false, true); REQUIRE(qm->GetFreeQubitCount() == 2); Qubit q0 = qm->Allocate(); Qubit q1 = qm->Allocate(); @@ -77,7 +77,7 @@ TEST_CASE("Allocation of released qubits when reuse is encouraged", "[QubitManag TEST_CASE("Extending capacity", "[QubitManager]") { - std::unique_ptr qm = std::make_unique(1, true); + std::unique_ptr qm = std::make_unique(1, true, true); Qubit q0 = qm->Allocate(); REQUIRE(qm->QubitToId(q0) == 0); From 33d4cd2656c5f3d53788ba5d6b73e7612f25997d Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 26 May 2021 14:19:25 -0700 Subject: [PATCH 08/19] Removed unused comment --- src/Qir/Runtime/lib/QIR/QubitManager.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp index 5ab3fba33a4..5f0c6f368c8 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManager.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// This code compiles, passes some unit tests, but isn't yet used in the runtime. - #include "QubitManager.hpp" #include // For memcpy From 7c6113413e23c3d2802850c7a5ae6cacf194c9a8 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Fri, 28 May 2021 19:22:40 -0700 Subject: [PATCH 09/19] CR feedback: fixes to types, failures, braces, etc. --- src/Qir/Runtime/lib/QIR/QubitManager.cpp | 48 +++++++++++++++++------- src/Qir/Runtime/public/QubitManager.hpp | 18 ++------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp index 5f0c6f368c8..325beea7451 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManager.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT License. #include "QubitManager.hpp" - +#include "QirRuntime.hpp" // For quantum__rt__fail_cstr #include // For memcpy namespace Microsoft @@ -10,6 +10,22 @@ namespace Microsoft namespace Quantum { +// +// Failing in case of errors +// + +static void FailNow(const char* message) +{ + quantum__rt__fail_cstr(message); +} + +static void FailIf(bool condition, const char* message) +{ + if (condition) + { + quantum__rt__fail_cstr(message); + } +} // // QubitListInSharedArray @@ -60,10 +76,10 @@ void QubitListInSharedArray::AddQubit(QubitIdType id, bool addToFront, QubitIdTy // TODO: Set status to the taken qubit here. Then counting is reasonable here, but not possible? // TODO: Rename 'RemoveQubitFromFront'? -int QubitListInSharedArray::TakeQubitFromFront(QubitIdType* sharedQubitStatusArray) +QubitIdType QubitListInSharedArray::TakeQubitFromFront(QubitIdType* sharedQubitStatusArray) { // First element will be returned. It is 'NoneMarker' if the list is empty. - int result = firstElement; + QubitIdType result = firstElement; // Advance list start to the next element if list is not empty. if (!IsEmpty()) @@ -160,7 +176,7 @@ CQubitManager::CQubitManager( if (qubitCapacity <= 0) { qubitCapacity = DefaultQubitCapacity; } - sharedQubitStatusArray = new int[qubitCapacity]; + sharedQubitStatusArray = new QubitIdType[qubitCapacity]; // These objects are passed by value (copies are created) QubitListInSharedArray FreeQubitsFresh(0, qubitCapacity - 1, sharedQubitStatusArray); @@ -210,10 +226,10 @@ void CQubitManager::EndRestrictedReuseArea() Qubit CQubitManager::Allocate() { - int newQubitId = AllocateQubitId(); + QubitIdType newQubitId = AllocateQubitId(); if (newQubitId == NoneMarker && mayExtendCapacity) { - int newQubitCapacity = qubitCapacity * 2; + QubitIdType newQubitCapacity = qubitCapacity * 2; FailIf(newQubitCapacity <= qubitCapacity, "Cannot extend capacity."); EnsureCapacity(newQubitCapacity); newQubitId = AllocateQubitId(); @@ -370,7 +386,7 @@ void CQubitManager::EnsureCapacity(QubitIdType requestedCapacity) } // Prepare new shared status array - QubitIdType* newStatusArray = new int[requestedCapacity]; + QubitIdType* newStatusArray = new QubitIdType[requestedCapacity]; memcpy(newStatusArray, sharedQubitStatusArray, qubitCapacity * sizeof(newStatusArray[0])); QubitListInSharedArray newFreeQubits(qubitCapacity, requestedCapacity - 1, newStatusArray); @@ -384,19 +400,25 @@ void CQubitManager::EnsureCapacity(QubitIdType requestedCapacity) QubitIdType CQubitManager::AllocateQubitId() { - if (encourageReuse) { + if (encourageReuse) + { // When reuse is encouraged, we start with the innermost area - for (int i = freeQubitsInAreas.Count() - 1; i >= 0; i--) { + for (int i = freeQubitsInAreas.Count() - 1; i >= 0; i--) + { QubitIdType id = freeQubitsInAreas[i].FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); - if (id != NoneMarker) { + if (id != NoneMarker) + { return id; } } - } else { + } else + { // When reuse is discouraged, we start with the outermost area - for (int i = 0; i < freeQubitsInAreas.Count(); i++) { + for (int i = 0; i < freeQubitsInAreas.Count(); i++) + { QubitIdType id = freeQubitsInAreas[i].FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); - if (id != NoneMarker) { + if (id != NoneMarker) + { return id; } } diff --git a/src/Qir/Runtime/public/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp index 666c116f932..3b8b36dbd88 100644 --- a/src/Qir/Runtime/public/QubitManager.hpp +++ b/src/Qir/Runtime/public/QubitManager.hpp @@ -24,25 +24,15 @@ namespace Microsoft { namespace Quantum { - - // TODO: Remove this! Use standard ones. - static void FailNow(const char* message) { std::cout << message; throw message; } - static void FailIf(bool condition, const char* message) { - if (condition) { - FailNow(message); - } - } - using QubitIdType = ::int32_t; // The end of free lists are marked with NoneMarker value. It is used like null for pointers. // This value is non-negative just like other values in the free lists. constexpr QubitIdType NoneMarker = std::numeric_limits::max(); - // Explicitly allocated qubits are marked with Allocated value. - // Qubits implicitly allocated for borrowing are "refcounted" with values [Allocated+1 .. -2]. - // When refcount needs to be decreased to Allocated value, the qubit is automatically released. - constexpr QubitIdType AllocatedMarker = std::numeric_limits::max(); + // Explicitly allocated qubits are marked with AllocatedMarker value. + // If borrowing is implemented, negative values may be used for refcounting. + constexpr QubitIdType AllocatedMarker = std::numeric_limits::min(); // Disabled qubits are marked with this value. constexpr QubitIdType DisabledMarker = -1; @@ -127,7 +117,7 @@ namespace Quantum // No complex scenarios for now. Don't need to support copying/moving. CQubitManager(const CQubitManager&) = delete; CQubitManager& operator = (const CQubitManager&) = delete; - ~CQubitManager(); + virtual ~CQubitManager(); // Restricted reuse area control void StartRestrictedReuseArea(); From d486cee6bb6bf400fe92f5f5980b0b92ddae18a0 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 2 Jun 2021 00:16:49 -0700 Subject: [PATCH 10/19] CR feedback - updated types, unique_ptr, etc. --- src/Qir/Runtime/lib/QIR/QubitManager.cpp | 2 +- src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp | 8 ++------ src/Qir/Runtime/public/QubitManager.hpp | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp index 325beea7451..0cfb6caf084 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManager.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -33,7 +33,7 @@ static void FailIf(bool condition, const char* message) QubitListInSharedArray::QubitListInSharedArray(QubitIdType startId, QubitIdType endId, QubitIdType* sharedQubitStatusArray) { - FailIf(startId > endId || startId < 0 || endId == std::numeric_limits::max(), + FailIf(startId > endId || startId < 0 || endId == MaximumQubitCapacity, "Incorrect boundaries in the linked list initialization."); for (QubitIdType i = startId; i < endId; i++) { diff --git a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp index 56bb5e4fd25..c3a0f99e2c8 100644 --- a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp +++ b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp @@ -109,7 +109,7 @@ namespace Quantum const QUANTUM_SIMULATOR handle = 0; unsigned simulatorId = -1; // the QuantumSimulator expects contiguous ids, starting from 0 - CQubitManager* qubitManager = nullptr; + std::unique_ptr qubitManager; unsigned GetQubitId(Qubit qubit) const { @@ -159,7 +159,7 @@ namespace Quantum typedef unsigned (*TInit)(); static TInit initSimulatorInstance = reinterpret_cast(this->GetProc("init")); - qubitManager = new CQubitManager(); + qubitManager = std::make_unique(); this->simulatorId = initSimulatorInstance(); } ~CFullstateSimulator() @@ -176,10 +176,6 @@ namespace Quantum // unload it might crash. // UnloadQuantumSimulator(this->handle); } - if (qubitManager != nullptr) { - delete qubitManager; - qubitManager = nullptr; - } } // Deprecated, use `DumpMachine()` and `DumpRegister()` instead. diff --git a/src/Qir/Runtime/public/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp index 3b8b36dbd88..c5a375f4a59 100644 --- a/src/Qir/Runtime/public/QubitManager.hpp +++ b/src/Qir/Runtime/public/QubitManager.hpp @@ -67,7 +67,7 @@ namespace Quantum bool IsEmpty() const; void AddQubit(QubitIdType id, bool addToFront, QubitIdType* sharedQubitStatusArray); - int TakeQubitFromFront(QubitIdType* sharedQubitStatusArray); + QubitIdType TakeQubitFromFront(QubitIdType* sharedQubitStatusArray); void MoveAllQubitsFrom(QubitListInSharedArray& source, QubitIdType* sharedQubitStatusArray); }; From 6ae25309654da2202cee84ac995dd4bcdcc89549 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 2 Jun 2021 16:08:20 -0700 Subject: [PATCH 11/19] Auxilliary stuff is now nested in CQubitmanager --- src/Qir/Runtime/lib/QIR/QubitManager.cpp | 24 +-- src/Qir/Runtime/public/QubitManager.hpp | 179 +++++++++++------------ 2 files changed, 100 insertions(+), 103 deletions(-) diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp index 0cfb6caf084..d63796ba02a 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManager.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -31,7 +31,7 @@ static void FailIf(bool condition, const char* message) // QubitListInSharedArray // -QubitListInSharedArray::QubitListInSharedArray(QubitIdType startId, QubitIdType endId, QubitIdType* sharedQubitStatusArray) +CQubitManager::QubitListInSharedArray::QubitListInSharedArray(QubitIdType startId, QubitIdType endId, QubitIdType* sharedQubitStatusArray) { FailIf(startId > endId || startId < 0 || endId == MaximumQubitCapacity, "Incorrect boundaries in the linked list initialization."); @@ -44,12 +44,12 @@ QubitListInSharedArray::QubitListInSharedArray(QubitIdType startId, QubitIdType lastElement = endId; } -bool QubitListInSharedArray::IsEmpty() const +bool CQubitManager::QubitListInSharedArray::IsEmpty() const { return firstElement == NoneMarker; } -void QubitListInSharedArray::AddQubit(QubitIdType id, bool addToFront, QubitIdType* sharedQubitStatusArray) +void CQubitManager::QubitListInSharedArray::AddQubit(QubitIdType id, bool addToFront, QubitIdType* sharedQubitStatusArray) { FailIf(id == NoneMarker, "Incorrect qubit id, cannot add it to the list."); @@ -76,7 +76,7 @@ void QubitListInSharedArray::AddQubit(QubitIdType id, bool addToFront, QubitIdTy // TODO: Set status to the taken qubit here. Then counting is reasonable here, but not possible? // TODO: Rename 'RemoveQubitFromFront'? -QubitIdType QubitListInSharedArray::TakeQubitFromFront(QubitIdType* sharedQubitStatusArray) +CQubitManager::QubitIdType CQubitManager::QubitListInSharedArray::TakeQubitFromFront(QubitIdType* sharedQubitStatusArray) { // First element will be returned. It is 'NoneMarker' if the list is empty. QubitIdType result = firstElement; @@ -96,7 +96,7 @@ QubitIdType QubitListInSharedArray::TakeQubitFromFront(QubitIdType* sharedQubitS return result; } -void QubitListInSharedArray::MoveAllQubitsFrom(QubitListInSharedArray& source, QubitIdType* sharedQubitStatusArray) +void CQubitManager::QubitListInSharedArray::MoveAllQubitsFrom(QubitListInSharedArray& source, QubitIdType* sharedQubitStatusArray) { // No need to do anthing if source is empty. if (source.IsEmpty()) @@ -125,7 +125,7 @@ void QubitListInSharedArray::MoveAllQubitsFrom(QubitListInSharedArray& source, Q // RestrictedReuseArea // -RestrictedReuseArea::RestrictedReuseArea(QubitListInSharedArray freeQubits) +CQubitManager::RestrictedReuseArea::RestrictedReuseArea(QubitListInSharedArray freeQubits) { //FreeQubitsReuseProhibited = QubitListInSharedArray(); // This is initialized by default. FreeQubitsReuseAllowed = freeQubits; // Default shallow copying. @@ -136,13 +136,13 @@ RestrictedReuseArea::RestrictedReuseArea(QubitListInSharedArray freeQubits) // CRestrictedReuseAreaStack // -void CRestrictedReuseAreaStack::PushToBack(RestrictedReuseArea area) +void CQubitManager::CRestrictedReuseAreaStack::PushToBack(RestrictedReuseArea area) { FailIf(Count() >= std::numeric_limits::max(), "Too many nested restricted reuse areas."); this->insert(this->end(), area); } -RestrictedReuseArea CRestrictedReuseAreaStack::PopFromBack() +CQubitManager::RestrictedReuseArea CQubitManager::CRestrictedReuseAreaStack::PopFromBack() { FailIf(this->empty(), "Cannot remove element from empty set."); RestrictedReuseArea result = this->back(); @@ -150,12 +150,12 @@ RestrictedReuseArea CRestrictedReuseAreaStack::PopFromBack() return result; } -RestrictedReuseArea& CRestrictedReuseAreaStack::PeekBack() +CQubitManager::RestrictedReuseArea& CQubitManager::CRestrictedReuseAreaStack::PeekBack() { return this->back(); } -int CRestrictedReuseAreaStack::Count() const +int CQubitManager::CRestrictedReuseAreaStack::Count() const { return this->size(); } @@ -369,7 +369,7 @@ Qubit CQubitManager::CreateQubitObject(QubitIdType id) return reinterpret_cast(pointerSizedId); } -QubitIdType CQubitManager::QubitToId(Qubit qubit) const +CQubitManager::QubitIdType CQubitManager::QubitToId(Qubit qubit) const { intptr_t pointerSizedId = reinterpret_cast(qubit); FailIf(pointerSizedId < 0 || pointerSizedId > std::numeric_limits::max(), "Qubit id is out of range."); @@ -398,7 +398,7 @@ void CQubitManager::EnsureCapacity(QubitIdType requestedCapacity) freeQubitsInAreas[0].FreeQubitsReuseAllowed.MoveAllQubitsFrom(newFreeQubits, sharedQubitStatusArray); } -QubitIdType CQubitManager::AllocateQubitId() +CQubitManager::QubitIdType CQubitManager::AllocateQubitId() { if (encourageReuse) { diff --git a/src/Qir/Runtime/public/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp index c5a375f4a59..1c90f6af3ac 100644 --- a/src/Qir/Runtime/public/QubitManager.hpp +++ b/src/Qir/Runtime/public/QubitManager.hpp @@ -1,17 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// Qubit Manager maintains mapping between user qubit objects and -// underlying qubit identifiers (Ids). When user program allocates -// a qubit, Qubit Manager decides whether to allocate a fresh id or -// reuse existing id that was previously freed. When user program -// releases a qubit, Qubit Manager tracks it as a free qubit id. -// Decision to reuse a qubit id is influenced by restricted reuse -// areas. When a qubit id is freed in one section of a restricted -// reuse area, it cannot be reused in other section of the same area. -// Borrowing of qubits is not supported and is currently implemented -// as plain allocation. - #pragma once #include @@ -24,90 +13,29 @@ namespace Microsoft { namespace Quantum { - using QubitIdType = ::int32_t; - - // The end of free lists are marked with NoneMarker value. It is used like null for pointers. - // This value is non-negative just like other values in the free lists. - constexpr QubitIdType NoneMarker = std::numeric_limits::max(); - - // Explicitly allocated qubits are marked with AllocatedMarker value. - // If borrowing is implemented, negative values may be used for refcounting. - constexpr QubitIdType AllocatedMarker = std::numeric_limits::min(); - - // Disabled qubits are marked with this value. - constexpr QubitIdType DisabledMarker = -1; - - // We want status array to be reasonably large. - constexpr QubitIdType DefaultQubitCapacity = 8; - - // Indexes in the status array can potentially be in range 0 .. int.MaxValue-1. - // This gives maximum capacity as int.MaxValue. - // Index equal to int.MaxValue doesn't exist and is reserved for 'NoneMarker' - list terminator. - constexpr QubitIdType MaximumQubitCapacity = std::numeric_limits::max(); - - // QubitListInSharedArray implements a singly-linked list with "pointers" to the first and the last element stored. - // Pointers are the indexes in a shared array. Shared array isn't sotored here because it can be reallocated. - // This class maintains status of elements in the list by virtue of linking them as part of this list. - // This class doesn't update status of elementes excluded from the list. - // This class is small, contains no C++ pointers and relies on default shallow copying/destruction. - struct QubitListInSharedArray final - { - private: - QubitIdType firstElement = NoneMarker; - QubitIdType lastElement = NoneMarker; - // We are not storing pointer to shared array because it can be reallocated. - // Indexes and special values remain the same on such reallocations. - - public: - // Initialize empty list - QubitListInSharedArray() = default; - - // Initialize as a list with sequential elements from startId to endId inclusve. - QubitListInSharedArray(QubitIdType startId, QubitIdType endId, QubitIdType* sharedQubitStatusArray); - - bool IsEmpty() const; - void AddQubit(QubitIdType id, bool addToFront, QubitIdType* sharedQubitStatusArray); - QubitIdType TakeQubitFromFront(QubitIdType* sharedQubitStatusArray); - void MoveAllQubitsFrom(QubitListInSharedArray& source, QubitIdType* sharedQubitStatusArray); - }; - - - // Restricted reuse area consists of multiple segments. Qubits released in one segment cannot be reused in another. - // One restricted reuse area can be nested in a segment of another restricted reuse area. - // This class tracks current segment of an area. Previous segments are tracked collectively (not individually). - // This class is small, contains no C++ pointers and relies on default shallow copying/destruction. - struct RestrictedReuseArea final + // CQubitManager maintains mapping between user qubit objects and + // underlying qubit identifiers (Ids). When user program allocates + // a qubit, Qubit Manager decides whether to allocate a fresh id or + // reuse existing id that was previously freed. When user program + // releases a qubit, Qubit Manager tracks it as a free qubit id. + // Decision to reuse a qubit id is influenced by restricted reuse + // areas. When a qubit id is freed in one section of a restricted + // reuse area, it cannot be reused in other section of the same area. + // Borrowing of qubits is not supported and is currently implemented + // as plain allocation. + class QIR_SHARED_API CQubitManager { public: - QubitListInSharedArray FreeQubitsReuseProhibited; - QubitListInSharedArray FreeQubitsReuseAllowed; - - RestrictedReuseArea() = default; - RestrictedReuseArea(QubitListInSharedArray freeQubits); - }; + using QubitIdType = ::int32_t; + // We want status array to be reasonably large. + constexpr static QubitIdType DefaultQubitCapacity = 8; + // Indexes in the status array can potentially be in range 0 .. QubitIdType.MaxValue-1. + // This gives maximum capacity as QubitIdType.MaxValue. + // Index equal to int.MaxValue doesn't exist and is reserved for 'NoneMarker' - list terminator. + constexpr static QubitIdType MaximumQubitCapacity = std::numeric_limits::max(); - // This is NOT a stack! We modify it only by push/pop, but we also iterate over elements. - // TODO: Better name? - class CRestrictedReuseAreaStack final : public std::vector - { - public: - // No complex scenarios for now. Don't need to support copying/moving. - CRestrictedReuseAreaStack() = default; - CRestrictedReuseAreaStack(const CRestrictedReuseAreaStack&) = delete; - CRestrictedReuseAreaStack& operator = (const CRestrictedReuseAreaStack&) = delete; - ~CRestrictedReuseAreaStack() = default; - - void PushToBack(RestrictedReuseArea area); - RestrictedReuseArea PopFromBack(); - RestrictedReuseArea& PeekBack(); - // TODO: Remove and use size directly? - int Count() const; - }; - - class QIR_SHARED_API CQubitManager - { public: CQubitManager( QubitIdType initialQubitCapacity = DefaultQubitCapacity, @@ -178,6 +106,76 @@ namespace Quantum bool GetMayExtendCapacity() const { return mayExtendCapacity; } bool GetEncourageReuse() const { return encourageReuse; } + private: + // The end of free lists are marked with NoneMarker value. It is used like null for pointers. + // This value is non-negative just like other values in the free lists. + constexpr static QubitIdType NoneMarker = std::numeric_limits::max(); + + // Explicitly allocated qubits are marked with AllocatedMarker value. + // If borrowing is implemented, negative values may be used for refcounting. + constexpr static QubitIdType AllocatedMarker = std::numeric_limits::min(); + + // Disabled qubits are marked with this value. + constexpr static QubitIdType DisabledMarker = -1; + + // QubitListInSharedArray implements a singly-linked list with "pointers" to the first and the last element stored. + // Pointers are the indexes in a shared array. Shared array isn't sotored in this class because it can be reallocated. + // This class maintains status of elements in the list by virtue of linking them as part of this list. + // This class doesn't update status of elementes excluded from the list. + // This class is small, contains no C++ pointers and relies on default shallow copying/destruction. + struct QubitListInSharedArray final + { + private: + QubitIdType firstElement = NoneMarker; + QubitIdType lastElement = NoneMarker; + // We are not storing pointer to shared array because it can be reallocated. + // Indexes and special values remain the same on such reallocations. + + public: + // Initialize empty list + QubitListInSharedArray() = default; + + // Initialize as a list with sequential elements from startId to endId inclusve. + QubitListInSharedArray(QubitIdType startId, QubitIdType endId, QubitIdType* sharedQubitStatusArray); + + bool IsEmpty() const; + void AddQubit(QubitIdType id, bool addToFront, QubitIdType* sharedQubitStatusArray); + QubitIdType TakeQubitFromFront(QubitIdType* sharedQubitStatusArray); + void MoveAllQubitsFrom(QubitListInSharedArray& source, QubitIdType* sharedQubitStatusArray); + }; + + // Restricted reuse area consists of multiple segments. Qubits released in one segment cannot be reused in another. + // One restricted reuse area can be nested in a segment of another restricted reuse area. + // This class tracks current segment of an area. Previous segments are tracked collectively (not individually). + // This class is small, contains no C++ pointers and relies on default shallow copying/destruction. + struct RestrictedReuseArea final + { + public: + QubitListInSharedArray FreeQubitsReuseProhibited; + QubitListInSharedArray FreeQubitsReuseAllowed; + + RestrictedReuseArea() = default; + RestrictedReuseArea(QubitListInSharedArray freeQubits); + }; + + // This is NOT a stack! We modify it only by push/pop, but we also iterate over elements. + // TODO: Better name? + class CRestrictedReuseAreaStack final : public std::vector + { + public: + // No complex scenarios for now. Don't need to support copying/moving. + CRestrictedReuseAreaStack() = default; + CRestrictedReuseAreaStack(const CRestrictedReuseAreaStack&) = delete; + CRestrictedReuseAreaStack& operator = (const CRestrictedReuseAreaStack&) = delete; + ~CRestrictedReuseAreaStack() = default; + + void PushToBack(RestrictedReuseArea area); + RestrictedReuseArea PopFromBack(); + RestrictedReuseArea& PeekBack(); + // TODO: Remove and use size directly? + int Count() const; + }; + private: void EnsureCapacity(QubitIdType requestedCapacity); @@ -205,6 +203,5 @@ namespace Quantum int freeQubitCount; }; - } } From 74feed4e15405f4bf92694a26175076cbd356782 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 2 Jun 2021 17:04:43 -0700 Subject: [PATCH 12/19] Initializer lists --- src/Qir/Runtime/lib/QIR/QubitManager.cpp | 25 ++++++++++++------------ src/Qir/Runtime/public/QubitManager.hpp | 14 ++++++------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp index d63796ba02a..2dd025d4f08 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManager.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -31,7 +31,12 @@ static void FailIf(bool condition, const char* message) // QubitListInSharedArray // -CQubitManager::QubitListInSharedArray::QubitListInSharedArray(QubitIdType startId, QubitIdType endId, QubitIdType* sharedQubitStatusArray) +CQubitManager::QubitListInSharedArray::QubitListInSharedArray( + QubitIdType startId, + QubitIdType endId, + QubitIdType* sharedQubitStatusArray): + firstElement(startId), + lastElement(endId) { FailIf(startId > endId || startId < 0 || endId == MaximumQubitCapacity, "Incorrect boundaries in the linked list initialization."); @@ -40,8 +45,6 @@ CQubitManager::QubitListInSharedArray::QubitListInSharedArray(QubitIdType startI sharedQubitStatusArray[i] = i + 1; // Current element points to the next element. } sharedQubitStatusArray[endId] = NoneMarker; // Last element ends the chain. - firstElement = startId; - lastElement = endId; } bool CQubitManager::QubitListInSharedArray::IsEmpty() const @@ -125,10 +128,11 @@ void CQubitManager::QubitListInSharedArray::MoveAllQubitsFrom(QubitListInSharedA // RestrictedReuseArea // -CQubitManager::RestrictedReuseArea::RestrictedReuseArea(QubitListInSharedArray freeQubits) +CQubitManager::RestrictedReuseArea::RestrictedReuseArea( + QubitListInSharedArray freeQubits): + FreeQubitsReuseProhibited(), // Default costructor + FreeQubitsReuseAllowed(freeQubits) // Default shallow copy. { - //FreeQubitsReuseProhibited = QubitListInSharedArray(); // This is initialized by default. - FreeQubitsReuseAllowed = freeQubits; // Default shallow copying. } @@ -167,11 +171,10 @@ int CQubitManager::CRestrictedReuseAreaStack::Count() const CQubitManager::CQubitManager( QubitIdType initialQubitCapacity, bool mayExtendCapacity, - bool encourageReuse) + bool encourageReuse): + mayExtendCapacity(mayExtendCapacity), + encourageReuse(encourageReuse) { - this->mayExtendCapacity = mayExtendCapacity; - this->encourageReuse = encourageReuse; - qubitCapacity = initialQubitCapacity; if (qubitCapacity <= 0) { qubitCapacity = DefaultQubitCapacity; @@ -183,8 +186,6 @@ CQubitManager::CQubitManager( RestrictedReuseArea outermostArea(FreeQubitsFresh); freeQubitsInAreas.PushToBack(outermostArea); - allocatedQubitCount = 0; - disabledQubitCount = 0; freeQubitCount = qubitCapacity; } diff --git a/src/Qir/Runtime/public/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp index 1c90f6af3ac..c0eba03af2d 100644 --- a/src/Qir/Runtime/public/QubitManager.hpp +++ b/src/Qir/Runtime/public/QubitManager.hpp @@ -189,18 +189,18 @@ namespace Quantum bool IsFree(QubitIdType id) const; // Configuration Properties - bool mayExtendCapacity; - bool encourageReuse; + bool mayExtendCapacity = true; + bool encourageReuse = true; // State - QubitIdType* sharedQubitStatusArray; // Tracks allocation state of all qubits. Stores lists of free qubits. - QubitIdType qubitCapacity; // qubitCapacity is always equal to the array size. + QubitIdType* sharedQubitStatusArray = nullptr; // Tracks allocation state of all qubits. Stores lists of free qubits. + QubitIdType qubitCapacity = 0; // qubitCapacity is always equal to the array size. CRestrictedReuseAreaStack freeQubitsInAreas; // Fresh Free Qubits are located in freeQubitsInAreas[0].FreeQubitsReuseAllowed // Counts - int disabledQubitCount; - int allocatedQubitCount; - int freeQubitCount; + int disabledQubitCount = 0; + int allocatedQubitCount = 0; + int freeQubitCount = 0; }; } From afc8943594cd875abf5f10771be77b4c4370191e Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 2 Jun 2021 17:36:35 -0700 Subject: [PATCH 13/19] Updated CRestrictedReuseAreaStack and used iterator. --- src/Qir/Runtime/lib/QIR/QubitManager.cpp | 10 +++++----- src/Qir/Runtime/public/QubitManager.hpp | 4 +--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp index 2dd025d4f08..20e52cacbe5 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManager.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -148,7 +148,7 @@ void CQubitManager::CRestrictedReuseAreaStack::PushToBack(RestrictedReuseArea ar CQubitManager::RestrictedReuseArea CQubitManager::CRestrictedReuseAreaStack::PopFromBack() { - FailIf(this->empty(), "Cannot remove element from empty set."); + FailIf(this->empty(), "Cannot remove restricted reuse area from an empty set."); RestrictedReuseArea result = this->back(); this->pop_back(); return result; @@ -404,9 +404,9 @@ CQubitManager::QubitIdType CQubitManager::AllocateQubitId() if (encourageReuse) { // When reuse is encouraged, we start with the innermost area - for (int i = freeQubitsInAreas.Count() - 1; i >= 0; i--) + for (CRestrictedReuseAreaStack::reverse_iterator rit = freeQubitsInAreas.rbegin(); rit != freeQubitsInAreas.rend(); ++rit) { - QubitIdType id = freeQubitsInAreas[i].FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); + QubitIdType id = rit->FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); if (id != NoneMarker) { return id; @@ -415,9 +415,9 @@ CQubitManager::QubitIdType CQubitManager::AllocateQubitId() } else { // When reuse is discouraged, we start with the outermost area - for (int i = 0; i < freeQubitsInAreas.Count(); i++) + for (CRestrictedReuseAreaStack::iterator it = freeQubitsInAreas.begin(); it != freeQubitsInAreas.end(); ++it) { - QubitIdType id = freeQubitsInAreas[i].FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); + QubitIdType id = it->FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); if (id != NoneMarker) { return id; diff --git a/src/Qir/Runtime/public/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp index c0eba03af2d..9e2eabcdb49 100644 --- a/src/Qir/Runtime/public/QubitManager.hpp +++ b/src/Qir/Runtime/public/QubitManager.hpp @@ -158,8 +158,7 @@ namespace Quantum RestrictedReuseArea(QubitListInSharedArray freeQubits); }; - // This is NOT a stack! We modify it only by push/pop, but we also iterate over elements. - // TODO: Better name? + // This is NOT a pure stack! We modify it only by push/pop, but we also iterate over elements. class CRestrictedReuseAreaStack final : public std::vector { public: @@ -172,7 +171,6 @@ namespace Quantum void PushToBack(RestrictedReuseArea area); RestrictedReuseArea PopFromBack(); RestrictedReuseArea& PeekBack(); - // TODO: Remove and use size directly? int Count() const; }; From c0868658c14c1e434b567555a3c8cbd888ed3be4 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 2 Jun 2021 21:50:54 -0700 Subject: [PATCH 14/19] int32_t, more checks, some refactoring --- src/Qir/Runtime/lib/QIR/QubitManager.cpp | 96 +++++++++++++----------- src/Qir/Runtime/public/QubitManager.hpp | 27 ++++--- 2 files changed, 65 insertions(+), 58 deletions(-) diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp index 20e52cacbe5..93cc76f94a2 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManager.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -14,7 +14,7 @@ namespace Quantum // Failing in case of errors // -static void FailNow(const char* message) +[[noreturn]] static void FailNow(const char* message) { quantum__rt__fail_cstr(message); } @@ -40,6 +40,7 @@ CQubitManager::QubitListInSharedArray::QubitListInSharedArray( { FailIf(startId > endId || startId < 0 || endId == MaximumQubitCapacity, "Incorrect boundaries in the linked list initialization."); + FailIf(sharedQubitStatusArray == nullptr, "Shared status array is not provided."); for (QubitIdType i = startId; i < endId; i++) { sharedQubitStatusArray[i] = i + 1; // Current element points to the next element. @@ -55,6 +56,7 @@ bool CQubitManager::QubitListInSharedArray::IsEmpty() const void CQubitManager::QubitListInSharedArray::AddQubit(QubitIdType id, bool addToFront, QubitIdType* sharedQubitStatusArray) { FailIf(id == NoneMarker, "Incorrect qubit id, cannot add it to the list."); + FailIf(sharedQubitStatusArray == nullptr, "Shared status array is not provided."); // If the list is empty, we initialize it with the new element. if (IsEmpty()) @@ -81,6 +83,8 @@ void CQubitManager::QubitListInSharedArray::AddQubit(QubitIdType id, bool addToF // TODO: Rename 'RemoveQubitFromFront'? CQubitManager::QubitIdType CQubitManager::QubitListInSharedArray::TakeQubitFromFront(QubitIdType* sharedQubitStatusArray) { + FailIf(sharedQubitStatusArray == nullptr, "Shared status array is not provided."); + // First element will be returned. It is 'NoneMarker' if the list is empty. QubitIdType result = firstElement; @@ -101,6 +105,8 @@ CQubitManager::QubitIdType CQubitManager::QubitListInSharedArray::TakeQubitFromF void CQubitManager::QubitListInSharedArray::MoveAllQubitsFrom(QubitListInSharedArray& source, QubitIdType* sharedQubitStatusArray) { + FailIf(sharedQubitStatusArray == nullptr, "Shared status array is not provided."); + // No need to do anthing if source is empty. if (source.IsEmpty()) { @@ -142,7 +148,7 @@ CQubitManager::RestrictedReuseArea::RestrictedReuseArea( void CQubitManager::CRestrictedReuseAreaStack::PushToBack(RestrictedReuseArea area) { - FailIf(Count() >= std::numeric_limits::max(), "Too many nested restricted reuse areas."); + FailIf(Count() >= std::numeric_limits::max(), "Too many nested restricted reuse areas."); this->insert(this->end(), area); } @@ -159,9 +165,10 @@ CQubitManager::RestrictedReuseArea& CQubitManager::CRestrictedReuseAreaStack::Pe return this->back(); } -int CQubitManager::CRestrictedReuseAreaStack::Count() const +int32_t CQubitManager::CRestrictedReuseAreaStack::Count() const { - return this->size(); + // The size should never exceed int32_t. + return static_cast(this->size()); } // @@ -173,12 +180,10 @@ CQubitManager::CQubitManager( bool mayExtendCapacity, bool encourageReuse): mayExtendCapacity(mayExtendCapacity), - encourageReuse(encourageReuse) + encourageReuse(encourageReuse), + qubitCapacity(initialQubitCapacity) { - qubitCapacity = initialQubitCapacity; - if (qubitCapacity <= 0) { - qubitCapacity = DefaultQubitCapacity; - } + FailIf(qubitCapacity <= 0, "Qubit capacity must be positive."); sharedQubitStatusArray = new QubitIdType[qubitCapacity]; // These objects are passed by value (copies are created) @@ -240,27 +245,26 @@ Qubit CQubitManager::Allocate() return CreateQubitObject(newQubitId); } -void CQubitManager::Allocate(Qubit* qubitsToAllocate, int qubitCountToAllocate) +void CQubitManager::Allocate(Qubit* qubitsToAllocate, int32_t qubitCountToAllocate) { - FailIf(qubitCountToAllocate < 0, "Attempt to allocate negative number of qubits."); if (qubitCountToAllocate == 0) { return; } + FailIf(qubitCountToAllocate < 0, "Cannot allocate negative number of qubits."); FailIf(qubitsToAllocate == nullptr, "No array provided for qubits to be allocated."); // Consider optimization for initial allocation of a large array at once - for (int i = 0; i < qubitCountToAllocate; i++) + for (int32_t i = 0; i < qubitCountToAllocate; i++) { QubitIdType newQubitId = AllocateQubitId(); if (newQubitId == NoneMarker) { - for (int k = 0; k < i; k++) + for (int32_t k = 0; k < i; k++) { Release(qubitsToAllocate[k]); } FailNow("Not enough qubits."); - return; } ChangeStatusToAllocated(newQubitId); qubitsToAllocate[i] = CreateQubitObject(newQubitId); @@ -270,18 +274,18 @@ void CQubitManager::Allocate(Qubit* qubitsToAllocate, int qubitCountToAllocate) void CQubitManager::Release(Qubit qubit) { FailIf(!IsValid(qubit), "Qubit is not valid."); - Release(QubitToId(qubit)); + ReleaseQubitId(QubitToId(qubit)); } -void CQubitManager::Release(Qubit* qubitsToRelease, int qubitCountToRelease) { - FailIf(qubitCountToRelease < 0, "Attempt to release negative number of qubits."); +void CQubitManager::Release(Qubit* qubitsToRelease, int32_t qubitCountToRelease) { if (qubitCountToRelease == 0) { return; } - FailIf(qubitsToRelease == nullptr, "No array provided for qubits to be released."); + FailIf(qubitCountToRelease < 0, "Cannot release negative number of qubits."); + FailIf(qubitsToRelease == nullptr, "No array provided with qubits to be released."); - for (int i = 0; i < qubitCountToRelease; i++) + for (int32_t i = 0; i < qubitCountToRelease; i++) { Release(qubitsToRelease[i]); qubitsToRelease[i] = nullptr; @@ -294,7 +298,7 @@ Qubit CQubitManager::Borrow() return Allocate(); } -void CQubitManager::Borrow(Qubit* qubitsToBorrow, int qubitCountToBorrow) +void CQubitManager::Borrow(Qubit* qubitsToBorrow, int32_t qubitCountToBorrow) { // We don't support true borrowing/returning at the moment. return Allocate(qubitsToBorrow, qubitCountToBorrow); @@ -306,7 +310,7 @@ void CQubitManager::Return(Qubit qubit) Release(qubit); } -void CQubitManager::Return(Qubit* qubitsToReturn, int qubitCountToReturn) +void CQubitManager::Return(Qubit* qubitsToReturn, int32_t qubitCountToReturn) { // We don't support true borrowing/returning at the moment. Release(qubitsToReturn, qubitCountToReturn); @@ -314,26 +318,29 @@ void CQubitManager::Return(Qubit* qubitsToReturn, int qubitCountToReturn) void CQubitManager::Disable(Qubit qubit) { - FailIf(!IsValid(qubit), "Invalid qubit."); + FailIf(!IsValid(qubit), "Qubit is not valid."); QubitIdType id = QubitToId(qubit); + // We can only disable explicitly allocated qubits that were not borrowed. FailIf(!IsExplicitlyAllocated(id), "Cannot disable qubit that is not explicitly allocated."); sharedQubitStatusArray[id] = DisabledMarker; - disabledQubitCount++; + disabledQubitCount++; + FailIf(disabledQubitCount <= 0, "Incorrect disabled qubit count."); allocatedQubitCount--; - FailIf(allocatedQubitCount < 0, "Incorrect count of allocated qubits."); + FailIf(allocatedQubitCount < 0, "Incorrect allocated qubit count."); } -void CQubitManager::Disable(Qubit* qubitsToDisable, int qubitCountToDisable) +void CQubitManager::Disable(Qubit* qubitsToDisable, int32_t qubitCountToDisable) { - FailIf(qubitCountToDisable < 0, "Qubit count cannot be negative"); - if (qubitsToDisable == nullptr || qubitCountToDisable == 0) + if (qubitCountToDisable == 0) { return; } + FailIf(qubitCountToDisable < 0, "Cannot disable negative number of qubits."); + FailIf(qubitsToDisable == nullptr, "No array provided with qubits to be disabled."); - for (int i = 0; i < qubitCountToDisable; i++) + for (int32_t i = 0; i < qubitCountToDisable; i++) { Disable(qubitsToDisable[i]); } @@ -374,25 +381,27 @@ CQubitManager::QubitIdType CQubitManager::QubitToId(Qubit qubit) const { intptr_t pointerSizedId = reinterpret_cast(qubit); FailIf(pointerSizedId < 0 || pointerSizedId > std::numeric_limits::max(), "Qubit id is out of range."); - return static_cast(pointerSizedId); + return static_cast(pointerSizedId); } void CQubitManager::EnsureCapacity(QubitIdType requestedCapacity) { - // No need to adjust existing values (NonMarker or indexes in the array). - // TODO: Borrowing/returning are not yet supported. + FailIf(requestedCapacity <= 0, "Requested qubit capacity must be positive."); if (requestedCapacity <= qubitCapacity) { return; } + // We need to reallocate shared status array, but there's no need to adjust + // existing values (NonMarker or indexes in the array). // Prepare new shared status array QubitIdType* newStatusArray = new QubitIdType[requestedCapacity]; memcpy(newStatusArray, sharedQubitStatusArray, qubitCapacity * sizeof(newStatusArray[0])); QubitListInSharedArray newFreeQubits(qubitCapacity, requestedCapacity - 1, newStatusArray); - // Set new data. All new qubits are added to the free qubits in the outermost area. + // Set new data. All fresh new qubits are added to the free qubits in the outermost area. freeQubitCount += requestedCapacity - qubitCapacity; + FailIf(freeQubitCount <= 0, "Incorrect free qubit count."); delete[] sharedQubitStatusArray; sharedQubitStatusArray = newStatusArray; qubitCapacity = requestedCapacity; @@ -427,25 +436,19 @@ CQubitManager::QubitIdType CQubitManager::AllocateQubitId() return NoneMarker; } -void CQubitManager::ReleaseQubitId(QubitIdType id) -{ - // Released qubits are added to reuse area/segment in which they were released - // (rather than area/segment where they are allocated). - // Although counterintuitive, this makes code simple. - // This is reasonable because we think qubits should be allocated and released in the same segment. - freeQubitsInAreas.PeekBack().FreeQubitsReuseAllowed.AddQubit(id, encourageReuse, sharedQubitStatusArray); -} - void CQubitManager::ChangeStatusToAllocated(QubitIdType id) { - FailIf(id >= qubitCapacity, "Internal Error: Cannot change status of an invalid qubit."); + FailIf(id < 0 || id >= qubitCapacity, "Internal Error: Cannot change status of an invalid qubit."); sharedQubitStatusArray[id] = AllocatedMarker; allocatedQubitCount++; + FailIf(allocatedQubitCount <= 0, "Incorrect allocated qubit count."); freeQubitCount--; + FailIf(freeQubitCount < 0, "Incorrect free qubit count."); } -void CQubitManager::Release(QubitIdType id) +void CQubitManager::ReleaseQubitId(QubitIdType id) { + FailIf(id < 0 || id >= qubitCapacity, "Internal Error: Cannot release an invalid qubit."); if (IsDisabled(id)) { // Nothing to do. Qubit will stay disabled. @@ -461,10 +464,15 @@ void CQubitManager::Release(QubitIdType id) sharedQubitStatusArray[id] = NoneMarker; } else { - ReleaseQubitId(id); + // Released qubits are added to reuse area/segment in which they were released + // (rather than area/segment where they are allocated). + // Although counterintuitive, this makes code simple. + // This is reasonable because qubits should be allocated and released in the same segment. (This is not enforced) + freeQubitsInAreas.PeekBack().FreeQubitsReuseAllowed.AddQubit(id, encourageReuse, sharedQubitStatusArray); } freeQubitCount++; + FailIf(freeQubitCount <= 0, "Incorrect free qubit count."); allocatedQubitCount--; FailIf(allocatedQubitCount < 0, "Incorrect allocated qubit count."); } diff --git a/src/Qir/Runtime/public/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp index 9e2eabcdb49..bce594c4d8e 100644 --- a/src/Qir/Runtime/public/QubitManager.hpp +++ b/src/Qir/Runtime/public/QubitManager.hpp @@ -33,7 +33,7 @@ namespace Quantum // Indexes in the status array can potentially be in range 0 .. QubitIdType.MaxValue-1. // This gives maximum capacity as QubitIdType.MaxValue. - // Index equal to int.MaxValue doesn't exist and is reserved for 'NoneMarker' - list terminator. + // Index equal to QubitIdType.MaxValue doesn't exist and is reserved for 'NoneMarker' - list terminator. constexpr static QubitIdType MaximumQubitCapacity = std::numeric_limits::max(); public: @@ -59,27 +59,27 @@ namespace Quantum // Allocate qubitCountToAllocate qubits and store them in the provided array. Extend manager capacity if necessary and possible. // Fail without allocating any qubits if the qubits cannot be allocated. // Caller is responsible for providing array of sufficient size to hold qubitCountToAllocate. - void Allocate(Qubit* qubitsToAllocate, int qubitCountToAllocate); + void Allocate(Qubit* qubitsToAllocate, int32_t qubitCountToAllocate); // Releases a given qubit. void Release(Qubit qubit); // Releases qubitCountToRelease qubits in the provided array. // Caller is responsible for managing memory used by the array itself (i.e. delete[] array if it was dynamically allocated). - void Release(Qubit* qubitsToRelease, int qubitCountToRelease); + void Release(Qubit* qubitsToRelease, int32_t qubitCountToRelease); // Borrow (We treat borrowing as allocation currently) Qubit Borrow(); - void Borrow(Qubit* qubitsToBorrow, int qubitCountToBorrow); + void Borrow(Qubit* qubitsToBorrow, int32_t qubitCountToBorrow); // Return (We treat returning as release currently) void Return(Qubit qubit); - void Return(Qubit* qubitsToReturn, int qubitCountToReturn); + void Return(Qubit* qubitsToReturn, int32_t qubitCountToReturn); // Disables a given qubit. // Once a qubit is disabled it can never be "enabled" or reallocated. void Disable(Qubit qubit); // Disables a set of given qubits. // Once a qubit is disabled it can never be "enabled" or reallocated. - void Disable(Qubit* qubitsToDisable, int qubitCountToDisable); + void Disable(Qubit* qubitsToDisable, int32_t qubitCountToDisable); bool IsValid(Qubit qubit) const; bool IsDisabled(Qubit qubit) const; @@ -99,9 +99,9 @@ namespace Quantum virtual QubitIdType QubitToId(Qubit qubit) const; // Qubit counts - int GetDisabledQubitCount() const { return disabledQubitCount; } - int GetAllocatedQubitCount() const { return allocatedQubitCount; } - int GetFreeQubitCount() const { return freeQubitCount; } + int32_t GetDisabledQubitCount() const { return disabledQubitCount; } + int32_t GetAllocatedQubitCount() const { return allocatedQubitCount; } + int32_t GetFreeQubitCount() const { return freeQubitCount; } bool GetMayExtendCapacity() const { return mayExtendCapacity; } bool GetEncourageReuse() const { return encourageReuse; } @@ -171,7 +171,7 @@ namespace Quantum void PushToBack(RestrictedReuseArea area); RestrictedReuseArea PopFromBack(); RestrictedReuseArea& PeekBack(); - int Count() const; + int32_t Count() const; }; private: @@ -180,7 +180,6 @@ namespace Quantum QubitIdType AllocateQubitId(); void ReleaseQubitId(QubitIdType id); void ChangeStatusToAllocated(QubitIdType id); - void Release(QubitIdType id); bool IsDisabled(QubitIdType id) const; bool IsExplicitlyAllocated(QubitIdType id) const; @@ -196,9 +195,9 @@ namespace Quantum CRestrictedReuseAreaStack freeQubitsInAreas; // Fresh Free Qubits are located in freeQubitsInAreas[0].FreeQubitsReuseAllowed // Counts - int disabledQubitCount = 0; - int allocatedQubitCount = 0; - int freeQubitCount = 0; + int32_t disabledQubitCount = 0; + int32_t allocatedQubitCount = 0; + int32_t freeQubitCount = 0; }; } From 0ee92fcf7a290f2463b6d5823b31de15894b19ba Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 2 Jun 2021 22:32:34 -0700 Subject: [PATCH 15/19] Refactoring: marking and counting is done in allocate/release qubit id --- src/Qir/Runtime/lib/QIR/QubitManager.cpp | 31 +++++++++++------------- src/Qir/Runtime/public/QubitManager.hpp | 1 - 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp index 93cc76f94a2..ccc57e91abf 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManager.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -241,7 +241,6 @@ Qubit CQubitManager::Allocate() newQubitId = AllocateQubitId(); } FailIf(newQubitId == NoneMarker, "Not enough qubits."); - ChangeStatusToAllocated(newQubitId); return CreateQubitObject(newQubitId); } @@ -266,7 +265,6 @@ void CQubitManager::Allocate(Qubit* qubitsToAllocate, int32_t qubitCountToAlloca } FailNow("Not enough qubits."); } - ChangeStatusToAllocated(newQubitId); qubitsToAllocate[i] = CreateQubitObject(newQubitId); } } @@ -410,15 +408,16 @@ void CQubitManager::EnsureCapacity(QubitIdType requestedCapacity) CQubitManager::QubitIdType CQubitManager::AllocateQubitId() { + QubitIdType id = NoneMarker; if (encourageReuse) { // When reuse is encouraged, we start with the innermost area for (CRestrictedReuseAreaStack::reverse_iterator rit = freeQubitsInAreas.rbegin(); rit != freeQubitsInAreas.rend(); ++rit) { - QubitIdType id = rit->FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); + id = rit->FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); if (id != NoneMarker) { - return id; + break; } } } else @@ -426,24 +425,22 @@ CQubitManager::QubitIdType CQubitManager::AllocateQubitId() // When reuse is discouraged, we start with the outermost area for (CRestrictedReuseAreaStack::iterator it = freeQubitsInAreas.begin(); it != freeQubitsInAreas.end(); ++it) { - QubitIdType id = it->FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); + id = it->FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); if (id != NoneMarker) { - return id; + break; } } } - return NoneMarker; -} - -void CQubitManager::ChangeStatusToAllocated(QubitIdType id) -{ - FailIf(id < 0 || id >= qubitCapacity, "Internal Error: Cannot change status of an invalid qubit."); - sharedQubitStatusArray[id] = AllocatedMarker; - allocatedQubitCount++; - FailIf(allocatedQubitCount <= 0, "Incorrect allocated qubit count."); - freeQubitCount--; - FailIf(freeQubitCount < 0, "Incorrect free qubit count."); + if (id != NoneMarker) { + FailIf(id < 0 || id >= qubitCapacity, "Internal Error: Allocated invalid qubit."); + sharedQubitStatusArray[id] = AllocatedMarker; + allocatedQubitCount++; + FailIf(allocatedQubitCount <= 0, "Incorrect allocated qubit count."); + freeQubitCount--; + FailIf(freeQubitCount < 0, "Incorrect free qubit count."); + } + return id; } void CQubitManager::ReleaseQubitId(QubitIdType id) diff --git a/src/Qir/Runtime/public/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp index bce594c4d8e..7c7623d2039 100644 --- a/src/Qir/Runtime/public/QubitManager.hpp +++ b/src/Qir/Runtime/public/QubitManager.hpp @@ -179,7 +179,6 @@ namespace Quantum QubitIdType AllocateQubitId(); void ReleaseQubitId(QubitIdType id); - void ChangeStatusToAllocated(QubitIdType id); bool IsDisabled(QubitIdType id) const; bool IsExplicitlyAllocated(QubitIdType id) const; From 5991b1ee909fd45d36fffe37ec6cd0bdecb3af06 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 2 Jun 2021 22:58:32 -0700 Subject: [PATCH 16/19] Deletion of qubit object, protected members for overrides, Is...Qubit renames --- src/Qir/Runtime/lib/QIR/QubitManager.cpp | 27 ++++++++++++----- .../lib/Simulators/FullstateSimulator.cpp | 2 +- src/Qir/Runtime/public/QubitManager.hpp | 30 ++++++++++++------- .../Runtime/unittests/QubitManagerTests.cpp | 30 +++++++++---------- 4 files changed, 55 insertions(+), 34 deletions(-) diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp index ccc57e91abf..33a67307de7 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManager.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -271,8 +271,9 @@ void CQubitManager::Allocate(Qubit* qubitsToAllocate, int32_t qubitCountToAlloca void CQubitManager::Release(Qubit qubit) { - FailIf(!IsValid(qubit), "Qubit is not valid."); + FailIf(!IsValidQubit(qubit), "Qubit is not valid."); ReleaseQubitId(QubitToId(qubit)); + DeleteQubitObject(qubit); } void CQubitManager::Release(Qubit* qubitsToRelease, int32_t qubitCountToRelease) { @@ -316,7 +317,7 @@ void CQubitManager::Return(Qubit* qubitsToReturn, int32_t qubitCountToReturn) void CQubitManager::Disable(Qubit qubit) { - FailIf(!IsValid(qubit), "Qubit is not valid."); + FailIf(!IsValidQubit(qubit), "Qubit is not valid."); QubitIdType id = QubitToId(qubit); // We can only disable explicitly allocated qubits that were not borrowed. @@ -344,7 +345,7 @@ void CQubitManager::Disable(Qubit* qubitsToDisable, int32_t qubitCountToDisable) } } -bool CQubitManager::IsValid(Qubit qubit) const +bool CQubitManager::IsValidQubit(Qubit qubit) const { QubitIdType id = QubitToId(qubit); if (id >= qubitCapacity) @@ -358,16 +359,23 @@ bool CQubitManager::IsValid(Qubit qubit) const return true; } -bool CQubitManager::IsDisabled(Qubit qubit) const +bool CQubitManager::IsDisabledQubit(Qubit qubit) const { - return IsValid(qubit) && IsDisabled(QubitToId(qubit)); + return IsValidQubit(qubit) && IsDisabled(QubitToId(qubit)); } -bool CQubitManager::IsFree(Qubit qubit) const +bool CQubitManager::IsFreeQubit(Qubit qubit) const { - return IsValid(qubit) && IsFree(QubitToId(qubit)); + return IsValidQubit(qubit) && IsFree(QubitToId(qubit)); } +CQubitManager::QubitIdType CQubitManager::GetQubitId(Qubit qubit) const +{ + FailIf(!IsValidQubit(qubit), "Not a valid qubit."); + return QubitToId(qubit); +} + + Qubit CQubitManager::CreateQubitObject(QubitIdType id) { FailIf(id < 0 || id > INTPTR_MAX, "Qubit id is out of range."); @@ -375,6 +383,11 @@ Qubit CQubitManager::CreateQubitObject(QubitIdType id) return reinterpret_cast(pointerSizedId); } +void CQubitManager::DeleteQubitObject(Qubit qubit) +{ + // Do nothing. By default we store qubit Id in place of a pointer to a qubit. +} + CQubitManager::QubitIdType CQubitManager::QubitToId(Qubit qubit) const { intptr_t pointerSizedId = reinterpret_cast(qubit); diff --git a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp index c3a0f99e2c8..b6da62d9443 100644 --- a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp +++ b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp @@ -114,7 +114,7 @@ namespace Quantum unsigned GetQubitId(Qubit qubit) const { // Qubit manager uses unsigned range of int32_t for qubit ids. - return static_cast(qubitManager->QubitToId(qubit)); + return static_cast(qubitManager->GetQubitId(qubit)); } std::vector GetQubitIds(long num, Qubit* qubits) const diff --git a/src/Qir/Runtime/public/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp index 7c7623d2039..fb1ba7b7aba 100644 --- a/src/Qir/Runtime/public/QubitManager.hpp +++ b/src/Qir/Runtime/public/QubitManager.hpp @@ -81,16 +81,32 @@ namespace Quantum // Once a qubit is disabled it can never be "enabled" or reallocated. void Disable(Qubit* qubitsToDisable, int32_t qubitCountToDisable); - bool IsValid(Qubit qubit) const; - bool IsDisabled(Qubit qubit) const; - bool IsFree(Qubit qubit) const; + bool IsValidQubit(Qubit qubit) const; + bool IsDisabledQubit(Qubit qubit) const; + bool IsFreeQubit(Qubit qubit) const; + QubitIdType GetQubitId(Qubit qubit) const; + // Qubit counts + int32_t GetDisabledQubitCount() const { return disabledQubitCount; } + int32_t GetAllocatedQubitCount() const { return allocatedQubitCount; } + int32_t GetFreeQubitCount() const { return freeQubitCount; } + + bool GetMayExtendCapacity() const { return mayExtendCapacity; } + bool GetEncourageReuse() const { return encourageReuse; } + + protected: // May be overriden to create a custom Qubit object. // When not overriden, it just stores qubit Id in place of a pointer to a qubit. // id: unique qubit id // Returns a newly instantiated qubit. virtual Qubit CreateQubitObject(QubitIdType id); + // May be overriden to delete a custom Qubit object. + // Must be overriden if CreateQubitObject is overriden. + // When not overriden, it does nothing. + // qubit: pointer to QUBIT + virtual void DeleteQubitObject(Qubit qubit); + // May be overriden to get a qubit id from a custom qubit object. // Must be overriden if CreateQubitObject is overriden. // When not overriden, it just reinterprets pointer to qubit as a qubit id. @@ -98,14 +114,6 @@ namespace Quantum // Returns id of a qubit pointed to by qubit. virtual QubitIdType QubitToId(Qubit qubit) const; - // Qubit counts - int32_t GetDisabledQubitCount() const { return disabledQubitCount; } - int32_t GetAllocatedQubitCount() const { return allocatedQubitCount; } - int32_t GetFreeQubitCount() const { return freeQubitCount; } - - bool GetMayExtendCapacity() const { return mayExtendCapacity; } - bool GetEncourageReuse() const { return encourageReuse; } - private: // The end of free lists are marked with NoneMarker value. It is used like null for pointers. // This value is non-negative just like other values in the free lists. diff --git a/src/Qir/Runtime/unittests/QubitManagerTests.cpp b/src/Qir/Runtime/unittests/QubitManagerTests.cpp index aa13a47768b..6f5a02596a7 100644 --- a/src/Qir/Runtime/unittests/QubitManagerTests.cpp +++ b/src/Qir/Runtime/unittests/QubitManagerTests.cpp @@ -22,7 +22,7 @@ TEST_CASE("Allocation and reallocation of one qubit", "[QubitManagerBasic]") REQUIRE(qm->GetFreeQubitCount() == 1); REQUIRE(qm->GetAllocatedQubitCount() == 0); Qubit q = qm->Allocate(); - REQUIRE(qm->QubitToId(q) == 0); + REQUIRE(qm->GetQubitId(q) == 0); REQUIRE(qm->GetFreeQubitCount() == 0); REQUIRE(qm->GetAllocatedQubitCount() == 1); REQUIRE_THROWS(qm->Allocate()); @@ -32,7 +32,7 @@ TEST_CASE("Allocation and reallocation of one qubit", "[QubitManagerBasic]") REQUIRE(qm->GetFreeQubitCount() == 1); REQUIRE(qm->GetAllocatedQubitCount() == 0); Qubit q0 = qm->Allocate(); - REQUIRE(qm->QubitToId(q0) == 0); + REQUIRE(qm->GetQubitId(q0) == 0); REQUIRE(qm->GetFreeQubitCount() == 0); REQUIRE(qm->GetAllocatedQubitCount() == 1); qm->Release(q0); @@ -47,15 +47,15 @@ TEST_CASE("Allocation of released qubits when reuse is encouraged", "[QubitManag REQUIRE(qm->GetFreeQubitCount() == 2); Qubit q0 = qm->Allocate(); Qubit q1 = qm->Allocate(); - REQUIRE(qm->QubitToId(q0) == 0); // Qubit ids should be in order - REQUIRE(qm->QubitToId(q1) == 1); + REQUIRE(qm->GetQubitId(q0) == 0); // Qubit ids should be in order + REQUIRE(qm->GetQubitId(q1) == 1); REQUIRE_THROWS(qm->Allocate()); REQUIRE(qm->GetFreeQubitCount() == 0); REQUIRE(qm->GetAllocatedQubitCount() == 2); qm->Release(q0); Qubit q0a = qm->Allocate(); - REQUIRE(qm->QubitToId(q0a) == 0); // It was the only one available + REQUIRE(qm->GetQubitId(q0a) == 0); // It was the only one available REQUIRE_THROWS(qm->Allocate()); qm->Release(q1); @@ -65,8 +65,8 @@ TEST_CASE("Allocation of released qubits when reuse is encouraged", "[QubitManag Qubit q0b = qm->Allocate(); Qubit q1a = qm->Allocate(); - REQUIRE(qm->QubitToId(q0b) == 0); // By default reuse is encouraged, LIFO is used - REQUIRE(qm->QubitToId(q1a) == 1); + REQUIRE(qm->GetQubitId(q0b) == 0); // By default reuse is encouraged, LIFO is used + REQUIRE(qm->GetQubitId(q1a) == 1); REQUIRE_THROWS(qm->Allocate()); REQUIRE(qm->GetFreeQubitCount() == 0); REQUIRE(qm->GetAllocatedQubitCount() == 2); @@ -80,17 +80,17 @@ TEST_CASE("Extending capacity", "[QubitManager]") std::unique_ptr qm = std::make_unique(1, true, true); Qubit q0 = qm->Allocate(); - REQUIRE(qm->QubitToId(q0) == 0); + REQUIRE(qm->GetQubitId(q0) == 0); Qubit q1 = qm->Allocate(); // This should double capacity - REQUIRE(qm->QubitToId(q1) == 1); + REQUIRE(qm->GetQubitId(q1) == 1); REQUIRE(qm->GetFreeQubitCount() == 0); REQUIRE(qm->GetAllocatedQubitCount() == 2); qm->Release(q0); Qubit q0a = qm->Allocate(); - REQUIRE(qm->QubitToId(q0a) == 0); + REQUIRE(qm->GetQubitId(q0a) == 0); Qubit q2 = qm->Allocate(); // This should double capacity again - REQUIRE(qm->QubitToId(q2) == 2); + REQUIRE(qm->GetQubitId(q2) == 2); REQUIRE(qm->GetFreeQubitCount() == 1); REQUIRE(qm->GetAllocatedQubitCount() == 3); @@ -115,24 +115,24 @@ TEST_CASE("Restricted Area", "[QubitManager]") std::unique_ptr qm = std::make_unique(3, false, true); Qubit q0 = qm->Allocate(); - REQUIRE(qm->QubitToId(q0) == 0); + REQUIRE(qm->GetQubitId(q0) == 0); qm->StartRestrictedReuseArea(); // Allocates fresh qubit Qubit q1 = qm->Allocate(); - REQUIRE(qm->QubitToId(q1) == 1); + REQUIRE(qm->GetQubitId(q1) == 1); qm->Release(q1); // Released, but cannot be used in the next segment. qm->NextRestrictedReuseSegment(); // Allocates fresh qubit, q1 cannot be reused - it belongs to a differen segment. Qubit q2 = qm->Allocate(); - REQUIRE(qm->QubitToId(q2) == 2); + REQUIRE(qm->GetQubitId(q2) == 2); qm->Release(q2); Qubit q2a = qm->Allocate(); // Getting the same one as the one that's just released. - REQUIRE(qm->QubitToId(q2a) == 2); + REQUIRE(qm->GetQubitId(q2a) == 2); qm->Release(q2a); // Released, but cannot be used in the next segment. qm->NextRestrictedReuseSegment(); From 55b05f6d4a2fce40b475ed849589b0e65a959e3b Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 2 Jun 2021 23:12:57 -0700 Subject: [PATCH 17/19] Moved allocated marking to listQubitListInSharedArray class --- src/Qir/Runtime/lib/QIR/QubitManager.cpp | 8 +++++--- src/Qir/Runtime/public/QubitManager.hpp | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp index 33a67307de7..e7d914a5b98 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManager.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -79,8 +79,6 @@ void CQubitManager::QubitListInSharedArray::AddQubit(QubitIdType id, bool addToF } } -// TODO: Set status to the taken qubit here. Then counting is reasonable here, but not possible? -// TODO: Rename 'RemoveQubitFromFront'? CQubitManager::QubitIdType CQubitManager::QubitListInSharedArray::TakeQubitFromFront(QubitIdType* sharedQubitStatusArray) { FailIf(sharedQubitStatusArray == nullptr, "Shared status array is not provided."); @@ -100,6 +98,11 @@ CQubitManager::QubitIdType CQubitManager::QubitListInSharedArray::TakeQubitFromF lastElement = NoneMarker; } + if (result != NoneMarker) + { + sharedQubitStatusArray[result] = AllocatedMarker; + } + return result; } @@ -447,7 +450,6 @@ CQubitManager::QubitIdType CQubitManager::AllocateQubitId() } if (id != NoneMarker) { FailIf(id < 0 || id >= qubitCapacity, "Internal Error: Allocated invalid qubit."); - sharedQubitStatusArray[id] = AllocatedMarker; allocatedQubitCount++; FailIf(allocatedQubitCount <= 0, "Incorrect allocated qubit count."); freeQubitCount--; diff --git a/src/Qir/Runtime/public/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp index fb1ba7b7aba..fca87acb06d 100644 --- a/src/Qir/Runtime/public/QubitManager.hpp +++ b/src/Qir/Runtime/public/QubitManager.hpp @@ -129,7 +129,7 @@ namespace Quantum // QubitListInSharedArray implements a singly-linked list with "pointers" to the first and the last element stored. // Pointers are the indexes in a shared array. Shared array isn't sotored in this class because it can be reallocated. // This class maintains status of elements in the list by virtue of linking them as part of this list. - // This class doesn't update status of elementes excluded from the list. + // This class sets Allocated status of elementes taken from the list (via TakeQubitFromFront). // This class is small, contains no C++ pointers and relies on default shallow copying/destruction. struct QubitListInSharedArray final { From 2119eed638a7f312d2a9e6936de560cb0d1eb487 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Wed, 2 Jun 2021 23:45:43 -0700 Subject: [PATCH 18/19] Added comments, mostly status array --- src/Qir/Runtime/public/QubitManager.hpp | 60 ++++++++++++++++--------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/src/Qir/Runtime/public/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp index fca87acb06d..d2deb7f3140 100644 --- a/src/Qir/Runtime/public/QubitManager.hpp +++ b/src/Qir/Runtime/public/QubitManager.hpp @@ -20,9 +20,9 @@ namespace Quantum // releases a qubit, Qubit Manager tracks it as a free qubit id. // Decision to reuse a qubit id is influenced by restricted reuse // areas. When a qubit id is freed in one section of a restricted - // reuse area, it cannot be reused in other section of the same area. - // Borrowing of qubits is not supported and is currently implemented - // as plain allocation. + // reuse area, it cannot be reused in other sections of the same area. + // True borrowing of qubits is not supported and is currently + // implemented as a plain allocation. class QIR_SHARED_API CQubitManager { public: @@ -32,7 +32,7 @@ namespace Quantum constexpr static QubitIdType DefaultQubitCapacity = 8; // Indexes in the status array can potentially be in range 0 .. QubitIdType.MaxValue-1. - // This gives maximum capacity as QubitIdType.MaxValue. + // This gives maximum capacity as QubitIdType.MaxValue. Actual configured capacity may be less than this. // Index equal to QubitIdType.MaxValue doesn't exist and is reserved for 'NoneMarker' - list terminator. constexpr static QubitIdType MaximumQubitCapacity = std::numeric_limits::max(); @@ -116,20 +116,23 @@ namespace Quantum private: // The end of free lists are marked with NoneMarker value. It is used like null for pointers. - // This value is non-negative just like other values in the free lists. + // This value is non-negative just like other values in the free lists. See sharedQubitStatusArray. constexpr static QubitIdType NoneMarker = std::numeric_limits::max(); // Explicitly allocated qubits are marked with AllocatedMarker value. // If borrowing is implemented, negative values may be used for refcounting. + // See sharedQubitStatusArray. constexpr static QubitIdType AllocatedMarker = std::numeric_limits::min(); - // Disabled qubits are marked with this value. + // Disabled qubits are marked with this value. See sharedQubitStatusArray. constexpr static QubitIdType DisabledMarker = -1; - // QubitListInSharedArray implements a singly-linked list with "pointers" to the first and the last element stored. - // Pointers are the indexes in a shared array. Shared array isn't sotored in this class because it can be reallocated. - // This class maintains status of elements in the list by virtue of linking them as part of this list. - // This class sets Allocated status of elementes taken from the list (via TakeQubitFromFront). + // QubitListInSharedArray implements a singly-linked list with "pointers" + // to the first and the last element stored. Pointers are the indexes + // in a single shared array. Shared array isn't sotored in this class + // because it can be reallocated. This class maintains status of elements + // in the list by virtue of linking them as part of this list. This class + // sets Allocated status of elementes taken from the list (via TakeQubitFromFront). // This class is small, contains no C++ pointers and relies on default shallow copying/destruction. struct QubitListInSharedArray final { @@ -152,9 +155,12 @@ namespace Quantum void MoveAllQubitsFrom(QubitListInSharedArray& source, QubitIdType* sharedQubitStatusArray); }; - // Restricted reuse area consists of multiple segments. Qubits released in one segment cannot be reused in another. - // One restricted reuse area can be nested in a segment of another restricted reuse area. - // This class tracks current segment of an area. Previous segments are tracked collectively (not individually). + // Restricted reuse area consists of multiple segments. Qubits released + // in one segment cannot be reused in another. One restricted reuse area + // can be nested in a segment of another restricted reuse area. This class + // tracks current segment of an area by maintaining a list of free qubits + // in a shared status array FreeQubitsReuseAllowed. Previous segments are + // tracked collectively (not individually) by maintaining FreeQubitsReuseProhibited. // This class is small, contains no C++ pointers and relies on default shallow copying/destruction. struct RestrictedReuseArea final { @@ -192,16 +198,30 @@ namespace Quantum bool IsExplicitlyAllocated(QubitIdType id) const; bool IsFree(QubitIdType id) const; - // Configuration Properties + // Configuration Properties: bool mayExtendCapacity = true; bool encourageReuse = true; - // State - QubitIdType* sharedQubitStatusArray = nullptr; // Tracks allocation state of all qubits. Stores lists of free qubits. - QubitIdType qubitCapacity = 0; // qubitCapacity is always equal to the array size. - CRestrictedReuseAreaStack freeQubitsInAreas; // Fresh Free Qubits are located in freeQubitsInAreas[0].FreeQubitsReuseAllowed - - // Counts + // State: + // sharedQubitStatusArray is used to store statuses of all known qubits. + // Integer value at the index of the qubit id represents the status of that qubit. + // (Ex: sharedQubitStatusArray[4] is the status of qubit with id = 4). + // Therefore qubit ids are in the range of [0..qubitCapacity). + // Capacity may be extended if MayExtendCapacity = true. + // If qubit X is allocated, sharedQubitStatusArray[X] = AllocatedMarker (negative number) + // If qubit X is disabled, sharedQubitStatusArray[X] = DisabledMarker (negative number) + // If qubit X is free, sharedQubitStatusArray[X] is a non-negative number, denote it Next(X). + // Next(X) is either the index of the next element in the list or the list terminator - NoneMarker. + // All free qubits form disjoint singly linked lists bound to to respective resricted reuse areas. + // Each area has two lists of free qubits - see RestrictedReuseArea. + QubitIdType* sharedQubitStatusArray = nullptr; + // qubitCapacity is always equal to the array size. + QubitIdType qubitCapacity = 0; + // All nested restricted reuse areas at the current moment. + // Fresh Free Qubits are added to the outermost area: freeQubitsInAreas[0].FreeQubitsReuseAllowed + CRestrictedReuseAreaStack freeQubitsInAreas; + + // Counts: int32_t disabledQubitCount = 0; int32_t allocatedQubitCount = 0; int32_t freeQubitCount = 0; From f40cc91a744f980aa850130b9a01246521dcd8ff Mon Sep 17 00:00:00 2001 From: Dmitry Vasilevsky Date: Fri, 4 Jun 2021 22:28:17 -0700 Subject: [PATCH 19/19] Tests, refactoring, replaced IsFreeQubit with IsFreeQubitId --- src/Qir/Runtime/lib/QIR/QubitManager.cpp | 92 ++++++++------- src/Qir/Runtime/public/QubitManager.hpp | 34 +++++- .../Runtime/unittests/QubitManagerTests.cpp | 105 +++++++++++++++++- .../FullstateSimulatorTests.cpp | 30 +++++ 4 files changed, 216 insertions(+), 45 deletions(-) diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp index e7d914a5b98..7e200aa5c44 100644 --- a/src/Qir/Runtime/lib/QIR/QubitManager.cpp +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -236,13 +236,6 @@ void CQubitManager::EndRestrictedReuseArea() Qubit CQubitManager::Allocate() { QubitIdType newQubitId = AllocateQubitId(); - if (newQubitId == NoneMarker && mayExtendCapacity) - { - QubitIdType newQubitCapacity = qubitCapacity * 2; - FailIf(newQubitCapacity <= qubitCapacity, "Cannot extend capacity."); - EnsureCapacity(newQubitCapacity); - newQubitId = AllocateQubitId(); - } FailIf(newQubitId == NoneMarker, "Not enough qubits."); return CreateQubitObject(newQubitId); } @@ -324,7 +317,7 @@ void CQubitManager::Disable(Qubit qubit) QubitIdType id = QubitToId(qubit); // We can only disable explicitly allocated qubits that were not borrowed. - FailIf(!IsExplicitlyAllocated(id), "Cannot disable qubit that is not explicitly allocated."); + FailIf(!IsExplicitlyAllocatedId(id), "Cannot disable qubit that is not explicitly allocated."); sharedQubitStatusArray[id] = DisabledMarker; disabledQubitCount++; @@ -348,28 +341,45 @@ void CQubitManager::Disable(Qubit* qubitsToDisable, int32_t qubitCountToDisable) } } +bool CQubitManager::IsValidId(QubitIdType id) const +{ + return (id >= 0) && (id < qubitCapacity); +} + +bool CQubitManager::IsDisabledId(QubitIdType id) const +{ + return sharedQubitStatusArray[id] == DisabledMarker; +} + +bool CQubitManager::IsExplicitlyAllocatedId(QubitIdType id) const +{ + return sharedQubitStatusArray[id] == AllocatedMarker; +} + +bool CQubitManager::IsFreeId(QubitIdType id) const +{ + return sharedQubitStatusArray[id] >= 0; +} + + bool CQubitManager::IsValidQubit(Qubit qubit) const { - QubitIdType id = QubitToId(qubit); - if (id >= qubitCapacity) - { - return false; - } - if (id < 0) - { - return false; - } - return true; + return IsValidId(QubitToId(qubit)); } bool CQubitManager::IsDisabledQubit(Qubit qubit) const { - return IsValidQubit(qubit) && IsDisabled(QubitToId(qubit)); + return IsValidQubit(qubit) && IsDisabledId(QubitToId(qubit)); +} + +bool CQubitManager::IsExplicitlyAllocatedQubit(Qubit qubit) const +{ + return IsValidQubit(qubit) && IsExplicitlyAllocatedId(QubitToId(qubit)); } -bool CQubitManager::IsFreeQubit(Qubit qubit) const +bool CQubitManager::IsFreeQubitId(QubitIdType id) const { - return IsValidQubit(qubit) && IsFree(QubitToId(qubit)); + return IsValidId(id) && IsFreeId(id); } CQubitManager::QubitIdType CQubitManager::GetQubitId(Qubit qubit) const @@ -381,7 +391,8 @@ CQubitManager::QubitIdType CQubitManager::GetQubitId(Qubit qubit) const Qubit CQubitManager::CreateQubitObject(QubitIdType id) { - FailIf(id < 0 || id > INTPTR_MAX, "Qubit id is out of range."); + // Make sure the static_cast won't overflow: + FailIf(id < 0 || id > std::numeric_limits::max(), "Qubit id is out of range."); intptr_t pointerSizedId = static_cast(id); return reinterpret_cast(pointerSizedId); } @@ -394,6 +405,7 @@ void CQubitManager::DeleteQubitObject(Qubit qubit) CQubitManager::QubitIdType CQubitManager::QubitToId(Qubit qubit) const { intptr_t pointerSizedId = reinterpret_cast(qubit); + // Make sure the static_cast won't overflow: FailIf(pointerSizedId < 0 || pointerSizedId > std::numeric_limits::max(), "Qubit id is out of range."); return static_cast(pointerSizedId); } @@ -422,8 +434,11 @@ void CQubitManager::EnsureCapacity(QubitIdType requestedCapacity) freeQubitsInAreas[0].FreeQubitsReuseAllowed.MoveAllQubitsFrom(newFreeQubits, sharedQubitStatusArray); } -CQubitManager::QubitIdType CQubitManager::AllocateQubitId() +CQubitManager::QubitIdType CQubitManager::TakeFreeQubitId() { + // Possible future optimization: we may store and maintain links to the next + // area with non-empty free list. Need to check amortized complexity... + QubitIdType id = NoneMarker; if (encourageReuse) { @@ -458,16 +473,29 @@ CQubitManager::QubitIdType CQubitManager::AllocateQubitId() return id; } +CQubitManager::QubitIdType CQubitManager::AllocateQubitId() +{ + QubitIdType newQubitId = TakeFreeQubitId(); + if (newQubitId == NoneMarker && mayExtendCapacity) + { + QubitIdType newQubitCapacity = qubitCapacity * 2; + FailIf(newQubitCapacity <= qubitCapacity, "Cannot extend capacity."); + EnsureCapacity(newQubitCapacity); + newQubitId = TakeFreeQubitId(); + } + return newQubitId; +} + void CQubitManager::ReleaseQubitId(QubitIdType id) { FailIf(id < 0 || id >= qubitCapacity, "Internal Error: Cannot release an invalid qubit."); - if (IsDisabled(id)) + if (IsDisabledId(id)) { // Nothing to do. Qubit will stay disabled. return; } - FailIf(!IsExplicitlyAllocated(id), "Attempt to free qubit that has not been allocated."); + FailIf(!IsExplicitlyAllocatedId(id), "Attempt to free qubit that has not been allocated."); if (mayExtendCapacity && !encourageReuse) { @@ -489,20 +517,6 @@ void CQubitManager::ReleaseQubitId(QubitIdType id) FailIf(allocatedQubitCount < 0, "Incorrect allocated qubit count."); } -bool CQubitManager::IsDisabled(QubitIdType id) const -{ - return sharedQubitStatusArray[id] == DisabledMarker; -} - -bool CQubitManager::IsExplicitlyAllocated(QubitIdType id) const -{ - return sharedQubitStatusArray[id] == AllocatedMarker; -} - -bool CQubitManager::IsFree(QubitIdType id) const -{ - return sharedQubitStatusArray[id] >= 0; -} } } diff --git a/src/Qir/Runtime/public/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp index d2deb7f3140..c45a74ce292 100644 --- a/src/Qir/Runtime/public/QubitManager.hpp +++ b/src/Qir/Runtime/public/QubitManager.hpp @@ -83,14 +83,32 @@ namespace Quantum bool IsValidQubit(Qubit qubit) const; bool IsDisabledQubit(Qubit qubit) const; - bool IsFreeQubit(Qubit qubit) const; + bool IsExplicitlyAllocatedQubit(Qubit qubit) const; + bool IsFreeQubitId(QubitIdType id) const; + QubitIdType GetQubitId(Qubit qubit) const; - // Qubit counts + // Qubit counts: + + // Number of qubits that are disabled. When an explicitly allocated qubit + // gets disabled, it is removed from allocated count and is added to + // disabled count immediately. Subsequent Release doesn't affect counts. int32_t GetDisabledQubitCount() const { return disabledQubitCount; } + + // Number of qubits that are explicitly allocated. This counter gets + // increased on allocation of a qubit and decreased on release of a qubit. + // Note that we treat borrowing as allocation now. int32_t GetAllocatedQubitCount() const { return allocatedQubitCount; } + + // Number of free qubits that are currently tracked by this qubit manager. + // Note that when qubit manager may extend capacity, this doesn't account + // for qubits that may be potentially added in future via capacity extension. + // If qubit manager may extend capacity and reuse is discouraged, released + // qubits still increase this number even though they cannot be reused. int32_t GetFreeQubitCount() const { return freeQubitCount; } + // Total number of qubits that are currently tracked by this qubit manager. + int32_t GetQubitCapacity() const { return qubitCapacity; } bool GetMayExtendCapacity() const { return mayExtendCapacity; } bool GetEncourageReuse() const { return encourageReuse; } @@ -191,12 +209,18 @@ namespace Quantum private: void EnsureCapacity(QubitIdType requestedCapacity); + // Take free qubit id from a free list without extending capacity. + // First non-empty free list among nested restricted reuse areas are considered. + QubitIdType TakeFreeQubitId(); + // Allocate free qubit id extending capacity if necessary and possible. QubitIdType AllocateQubitId(); + // Put qubit id back into a free list for the current restricted reuse area. void ReleaseQubitId(QubitIdType id); - bool IsDisabled(QubitIdType id) const; - bool IsExplicitlyAllocated(QubitIdType id) const; - bool IsFree(QubitIdType id) const; + bool IsValidId(QubitIdType id) const; + bool IsDisabledId(QubitIdType id) const; + bool IsFreeId(QubitIdType id) const; + bool IsExplicitlyAllocatedId(QubitIdType id) const; // Configuration Properties: bool mayExtendCapacity = true; diff --git a/src/Qir/Runtime/unittests/QubitManagerTests.cpp b/src/Qir/Runtime/unittests/QubitManagerTests.cpp index 6f5a02596a7..966b36f9645 100644 --- a/src/Qir/Runtime/unittests/QubitManagerTests.cpp +++ b/src/Qir/Runtime/unittests/QubitManagerTests.cpp @@ -40,6 +40,108 @@ TEST_CASE("Allocation and reallocation of one qubit", "[QubitManagerBasic]") REQUIRE(qm->GetAllocatedQubitCount() == 0); } +TEST_CASE("Qubit Status", "[QubitManagerBasic]") +{ + std::unique_ptr qm = std::make_unique(2, false, true); + + Qubit q0 = qm->Allocate(); + CQubitManager::QubitIdType q0id = qm->GetQubitId(q0); + REQUIRE(qm->IsValidQubit(q0)); + REQUIRE(qm->IsExplicitlyAllocatedQubit(q0)); + REQUIRE(!qm->IsDisabledQubit(q0)); + REQUIRE(!qm->IsFreeQubitId(q0id)); + REQUIRE(qm->GetAllocatedQubitCount() == 1); + + qm->Disable(q0); + REQUIRE(qm->IsValidQubit(q0)); + REQUIRE(!qm->IsExplicitlyAllocatedQubit(q0)); + REQUIRE(qm->IsDisabledQubit(q0)); + REQUIRE(!qm->IsFreeQubitId(q0id)); + REQUIRE(qm->GetDisabledQubitCount() == 1); + + qm->Release(q0); + REQUIRE(!qm->IsFreeQubitId(q0id)); + REQUIRE(qm->GetFreeQubitCount() == 1); + + Qubit q1 = qm->Allocate(); + CQubitManager::QubitIdType q1id = qm->GetQubitId(q1); + REQUIRE(q0id != q1id); + + REQUIRE(qm->IsValidQubit(q1)); + REQUIRE(qm->IsExplicitlyAllocatedQubit(q1)); + REQUIRE(!qm->IsDisabledQubit(q1)); + REQUIRE(!qm->IsFreeQubitId(q1id)); + + REQUIRE(qm->GetAllocatedQubitCount() == 1); + REQUIRE(qm->GetDisabledQubitCount() == 1); + REQUIRE(qm->GetFreeQubitCount() == 0); + + qm->Release(q1); + REQUIRE(qm->IsFreeQubitId(q1id)); +} + +TEST_CASE("Qubit Counts", "[QubitManagerBasic]") +{ + constexpr int totalQubitCount = 100; + constexpr int disabledQubitCount = 29; + constexpr int extraQubitCount = 43; + static_assert(extraQubitCount <= totalQubitCount); + static_assert(disabledQubitCount <= totalQubitCount); + // We don't want capacity to be extended at first... + static_assert(extraQubitCount + disabledQubitCount <= totalQubitCount); + + std::unique_ptr qm = std::make_unique(totalQubitCount, true, true); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == 0); + + Qubit* qubits = new Qubit[disabledQubitCount]; + qm->Allocate(qubits, disabledQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == disabledQubitCount); + REQUIRE(qm->GetDisabledQubitCount() == 0); + + qm->Disable(qubits, disabledQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + + qm->Release(qubits, disabledQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + delete[] qubits; + + qubits = new Qubit[extraQubitCount]; + qm->Allocate(qubits, extraQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount-extraQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == extraQubitCount); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + + qm->Release(qubits, extraQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + delete[] qubits; + + qubits = new Qubit[totalQubitCount]; + qm->Allocate(qubits, totalQubitCount); + REQUIRE(qm->GetQubitCapacity() > totalQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == totalQubitCount); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + + qm->Release(qubits, totalQubitCount); + REQUIRE(qm->GetQubitCapacity() > totalQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + delete[] qubits; +} TEST_CASE("Allocation of released qubits when reuse is encouraged", "[QubitManagerBasic]") { @@ -147,4 +249,5 @@ TEST_CASE("Restricted Area", "[QubitManager]") qm->Allocate(qqq, 2); // OK to destruct qubit manager while qubits are still allocated. REQUIRE_THROWS(qm->Allocate()); -} \ No newline at end of file +} + diff --git a/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp b/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp index f9369c87fa6..7fe5e789d80 100644 --- a/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp +++ b/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp @@ -77,6 +77,36 @@ TEST_CASE("Fullstate simulator: X and measure", "[fullstate_simulator]") sim->ReleaseResult(r2); } +TEST_CASE("Fullstate simulator: X, M, reuse, M", "[fullstate_simulator]") +{ + std::unique_ptr sim = CreateFullstateSimulator(); + IQuantumGateSet* iqa = dynamic_cast(sim.get()); + + Qubit q = sim->AllocateQubit(); + Result r1 = MZ(iqa, q); + REQUIRE(Result_Zero == sim->GetResultValue(r1)); + REQUIRE(sim->AreEqualResults(r1, sim->UseZero())); + + iqa->X(q); + Result r2 = MZ(iqa, q); + REQUIRE(Result_One == sim->GetResultValue(r2)); + REQUIRE(sim->AreEqualResults(r2, sim->UseOne())); + + sim->ReleaseQubit(q); + sim->ReleaseResult(r1); + sim->ReleaseResult(r2); + + Qubit qq = sim->AllocateQubit(); + Result r3 = MZ(iqa, qq); + // Allocated qubit should always be in |0> state even though we released + // q in |1> state, and qq is likely reusing the same underlying qubit as q. + REQUIRE(Result_Zero == sim->GetResultValue(r3)); + REQUIRE(sim->AreEqualResults(r3, sim->UseZero())); + + sim->ReleaseQubit(qq); + sim->ReleaseResult(r3); +} + TEST_CASE("Fullstate simulator: measure Bell state", "[fullstate_simulator]") { std::unique_ptr sim = CreateFullstateSimulator();