\r\n",
+ " "
+ ],
+ "text/plain": [
+ "Microsoft.Quantum.Experimental.StabilizerState"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "()"
+ ]
+ },
+ "execution_count": 32,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "DumpBellPair.simulate_noise()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a64c1a27",
+ "metadata": {},
+ "source": [
+ "## Epilogue"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "id": "56168b7e",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'iqsharp': LooseVersion ('1.0.0'),\n",
+ " 'Jupyter Core': LooseVersion ('1.5.0.0'),\n",
+ " '.NET Runtime': LooseVersion ('.NETCoreApp,Version=v3.1'),\n",
+ " 'qsharp': LooseVersion ('0.0.1.0a1'),\n",
+ " 'experimental': {'simulators': {'features': ['DEFAULT'],\n",
+ " 'name': 'Microsoft.Quantum.Experimental.Simulators',\n",
+ " 'opt_level': '3',\n",
+ " 'target': 'x86_64-pc-windows-msvc',\n",
+ " 'version': '0.17.210627752-alpha'}}}"
+ ]
+ },
+ "execution_count": 33,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "qsharp.component_versions()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "85f8f2e8",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.10"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/documentation/experimental-simulators.md b/documentation/experimental-simulators.md
new file mode 100644
index 00000000000..1336b286761
--- /dev/null
+++ b/documentation/experimental-simulators.md
@@ -0,0 +1,39 @@
+# Using the Experimental Simulators for the Quantum Development Kit
+
+As an experimental feature, the Quantum Development Kit provides capabilities for noisy and stabilizer simulation. This feature allows for simulating the behavior of Q# programs under the influence of noise, and for using the stabilizer representation (a.k.a. CHP simulation) with programs that only call Clifford operations.
+
+> For more information about the development of this feature, please see the GitHub issue at .
+
+Currently, the experimental simulators are supported for use with:
+
+- C# host programs
+- Python host programs
+- Q# standalone notebooks
+
+The experimental simulators are not yet supported by:
+
+- Q# standalone command-line programs
+- QIR-based executables
+
+## Using Experimental Simulators from Python
+
+> ### **ⓘ** TIP
+>
+> See the [example on using the experimental simulators from Python](./examples/experimental-simulators-from-python.ipynb) for more details.
+
+Once you have the right version of the `qsharp-core` Python package installed, you can enable the use of the experimental simulators by using the `qsharp.experimental` module:
+
+```python
+import qsharp
+import qsharp.experimental
+qsharp.experimental.enable_noisy_simulation()
+```
+
+After calling `enable_noisy_simulation()`, Q# operations imported into Python will expose a `.simulate_noise()` method that can be used to run Q# programs against the experimental simulators.
+
+By default, `.simulate_noise()` will assume an ideal error model (that is, no noise). To configure a particular error model, use the `qsharp.experimental.get_noise_model` and `qsharp.experimental.set_noise_model` functions to get and set the current noise model for the experimental simulators. Each error model is represented as a dictionary from intrinsic operation names to objects representing the errors in those intrinsic operations.
+
+For open systems simulation, error channels can be represented by [QuTiP](https://qutip.org/) `Qobj` objects encoding superoperators.
+
+> **Known limitation**: Currently, error channels for stabilizer simulation must be specified manually by their JSON serialization.
+
diff --git a/omnisharp.json b/omnisharp.json
new file mode 100644
index 00000000000..d705c6415c8
--- /dev/null
+++ b/omnisharp.json
@@ -0,0 +1,5 @@
+{
+ "script": {
+ "enableScriptNuGetReferences": true
+ }
+}
\ No newline at end of file
diff --git a/src/Qir/Runtime/lib/QIR/CMakeLists.txt b/src/Qir/Runtime/lib/QIR/CMakeLists.txt
index 9dbd2b9cd74..c9812d73d6b 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
+ 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
new file mode 100644
index 00000000000..7e200aa5c44
--- /dev/null
+++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp
@@ -0,0 +1,522 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#include "QubitManager.hpp"
+#include "QirRuntime.hpp" // For quantum__rt__fail_cstr
+#include // For memcpy
+
+namespace Microsoft
+{
+namespace Quantum
+{
+
+//
+// Failing in case of errors
+//
+
+[[noreturn]] 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
+//
+
+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.");
+ 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.
+ }
+ sharedQubitStatusArray[endId] = NoneMarker; // Last element ends the chain.
+}
+
+bool CQubitManager::QubitListInSharedArray::IsEmpty() const
+{
+ return firstElement == NoneMarker;
+}
+
+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())
+ {
+ firstElement = id;
+ lastElement = id;
+ sharedQubitStatusArray[id] = NoneMarker; // List with a single elemenet in the chain.
+ return;
+ }
+
+ if (addToFront)
+ {
+ 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; // 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.
+ }
+}
+
+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;
+
+ // Advance list start to the next element if list is not empty.
+ if (!IsEmpty())
+ {
+ firstElement = sharedQubitStatusArray[firstElement]; // The second element will be the first.
+ }
+
+ // Drop pointer to the last element if list becomes empty.
+ if (IsEmpty())
+ {
+ lastElement = NoneMarker;
+ }
+
+ if (result != NoneMarker)
+ {
+ sharedQubitStatusArray[result] = AllocatedMarker;
+ }
+
+ return result;
+}
+
+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())
+ {
+ 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; // The last element of the source chain will point to the first element of this chain.
+ }
+ 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;
+ source.lastElement = NoneMarker;
+}
+
+
+//
+// RestrictedReuseArea
+//
+
+CQubitManager::RestrictedReuseArea::RestrictedReuseArea(
+ QubitListInSharedArray freeQubits):
+ FreeQubitsReuseProhibited(), // Default costructor
+ FreeQubitsReuseAllowed(freeQubits) // Default shallow copy.
+{
+}
+
+
+//
+// CRestrictedReuseAreaStack
+//
+
+void CQubitManager::CRestrictedReuseAreaStack::PushToBack(RestrictedReuseArea area)
+{
+ FailIf(Count() >= std::numeric_limits::max(), "Too many nested restricted reuse areas.");
+ this->insert(this->end(), area);
+}
+
+CQubitManager::RestrictedReuseArea CQubitManager::CRestrictedReuseAreaStack::PopFromBack()
+{
+ FailIf(this->empty(), "Cannot remove restricted reuse area from an empty set.");
+ RestrictedReuseArea result = this->back();
+ this->pop_back();
+ return result;
+}
+
+CQubitManager::RestrictedReuseArea& CQubitManager::CRestrictedReuseAreaStack::PeekBack()
+{
+ return this->back();
+}
+
+int32_t CQubitManager::CRestrictedReuseAreaStack::Count() const
+{
+ // The size should never exceed int32_t.
+ return static_cast(this->size());
+}
+
+//
+// CQubitManager
+//
+
+CQubitManager::CQubitManager(
+ QubitIdType initialQubitCapacity,
+ bool mayExtendCapacity,
+ bool encourageReuse):
+ mayExtendCapacity(mayExtendCapacity),
+ encourageReuse(encourageReuse),
+ qubitCapacity(initialQubitCapacity)
+{
+ FailIf(qubitCapacity <= 0, "Qubit capacity must be positive.");
+ sharedQubitStatusArray = new QubitIdType[qubitCapacity];
+
+ // These objects are passed by value (copies are created)
+ QubitListInSharedArray FreeQubitsFresh(0, qubitCapacity - 1, sharedQubitStatusArray);
+ RestrictedReuseArea outermostArea(FreeQubitsFresh);
+ freeQubitsInAreas.PushToBack(outermostArea);
+
+ 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 CQubitManager::StartRestrictedReuseArea()
+{
+ RestrictedReuseArea newArea;
+ freeQubitsInAreas.PushToBack(newArea);
+}
+
+void CQubitManager::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 CQubitManager::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);
+}
+
+Qubit CQubitManager::Allocate()
+{
+ QubitIdType newQubitId = AllocateQubitId();
+ FailIf(newQubitId == NoneMarker, "Not enough qubits.");
+ return CreateQubitObject(newQubitId);
+}
+
+void CQubitManager::Allocate(Qubit* qubitsToAllocate, int32_t qubitCountToAllocate)
+{
+ 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 (int32_t i = 0; i < qubitCountToAllocate; i++)
+ {
+ QubitIdType newQubitId = AllocateQubitId();
+ if (newQubitId == NoneMarker)
+ {
+ for (int32_t k = 0; k < i; k++)
+ {
+ Release(qubitsToAllocate[k]);
+ }
+ FailNow("Not enough qubits.");
+ }
+ qubitsToAllocate[i] = CreateQubitObject(newQubitId);
+ }
+}
+
+void CQubitManager::Release(Qubit qubit)
+{
+ FailIf(!IsValidQubit(qubit), "Qubit is not valid.");
+ ReleaseQubitId(QubitToId(qubit));
+ DeleteQubitObject(qubit);
+}
+
+void CQubitManager::Release(Qubit* qubitsToRelease, int32_t qubitCountToRelease) {
+ if (qubitCountToRelease == 0)
+ {
+ return;
+ }
+ FailIf(qubitCountToRelease < 0, "Cannot release negative number of qubits.");
+ FailIf(qubitsToRelease == nullptr, "No array provided with qubits to be released.");
+
+ for (int32_t i = 0; i < qubitCountToRelease; i++)
+ {
+ Release(qubitsToRelease[i]);
+ qubitsToRelease[i] = nullptr;
+ }
+}
+
+Qubit CQubitManager::Borrow()
+{
+ // We don't support true borrowing/returning at the moment.
+ return Allocate();
+}
+
+void CQubitManager::Borrow(Qubit* qubitsToBorrow, int32_t qubitCountToBorrow)
+{
+ // We don't support true borrowing/returning at the moment.
+ return Allocate(qubitsToBorrow, qubitCountToBorrow);
+}
+
+void CQubitManager::Return(Qubit qubit)
+{
+ // We don't support true borrowing/returning at the moment.
+ Release(qubit);
+}
+
+void CQubitManager::Return(Qubit* qubitsToReturn, int32_t qubitCountToReturn)
+{
+ // We don't support true borrowing/returning at the moment.
+ Release(qubitsToReturn, qubitCountToReturn);
+}
+
+void CQubitManager::Disable(Qubit qubit)
+{
+ FailIf(!IsValidQubit(qubit), "Qubit is not valid.");
+ QubitIdType id = QubitToId(qubit);
+
+ // We can only disable explicitly allocated qubits that were not borrowed.
+ FailIf(!IsExplicitlyAllocatedId(id), "Cannot disable qubit that is not explicitly allocated.");
+ sharedQubitStatusArray[id] = DisabledMarker;
+
+ disabledQubitCount++;
+ FailIf(disabledQubitCount <= 0, "Incorrect disabled qubit count.");
+ allocatedQubitCount--;
+ FailIf(allocatedQubitCount < 0, "Incorrect allocated qubit count.");
+}
+
+void CQubitManager::Disable(Qubit* qubitsToDisable, int32_t qubitCountToDisable)
+{
+ 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 (int32_t i = 0; i < qubitCountToDisable; i++)
+ {
+ Disable(qubitsToDisable[i]);
+ }
+}
+
+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
+{
+ return IsValidId(QubitToId(qubit));
+}
+
+bool CQubitManager::IsDisabledQubit(Qubit qubit) const
+{
+ return IsValidQubit(qubit) && IsDisabledId(QubitToId(qubit));
+}
+
+bool CQubitManager::IsExplicitlyAllocatedQubit(Qubit qubit) const
+{
+ return IsValidQubit(qubit) && IsExplicitlyAllocatedId(QubitToId(qubit));
+}
+
+bool CQubitManager::IsFreeQubitId(QubitIdType id) const
+{
+ return IsValidId(id) && IsFreeId(id);
+}
+
+CQubitManager::QubitIdType CQubitManager::GetQubitId(Qubit qubit) const
+{
+ FailIf(!IsValidQubit(qubit), "Not a valid qubit.");
+ return QubitToId(qubit);
+}
+
+
+Qubit CQubitManager::CreateQubitObject(QubitIdType id)
+{
+ // 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);
+}
+
+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);
+ // 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);
+}
+
+void CQubitManager::EnsureCapacity(QubitIdType requestedCapacity)
+{
+ 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 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;
+ freeQubitsInAreas[0].FreeQubitsReuseAllowed.MoveAllQubitsFrom(newFreeQubits, sharedQubitStatusArray);
+}
+
+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)
+ {
+ // When reuse is encouraged, we start with the innermost area
+ for (CRestrictedReuseAreaStack::reverse_iterator rit = freeQubitsInAreas.rbegin(); rit != freeQubitsInAreas.rend(); ++rit)
+ {
+ id = rit->FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray);
+ if (id != NoneMarker)
+ {
+ break;
+ }
+ }
+ } else
+ {
+ // When reuse is discouraged, we start with the outermost area
+ for (CRestrictedReuseAreaStack::iterator it = freeQubitsInAreas.begin(); it != freeQubitsInAreas.end(); ++it)
+ {
+ id = it->FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray);
+ if (id != NoneMarker)
+ {
+ break;
+ }
+ }
+ }
+ if (id != NoneMarker) {
+ FailIf(id < 0 || id >= qubitCapacity, "Internal Error: Allocated invalid qubit.");
+ allocatedQubitCount++;
+ FailIf(allocatedQubitCount <= 0, "Incorrect allocated qubit count.");
+ freeQubitCount--;
+ FailIf(freeQubitCount < 0, "Incorrect free qubit count.");
+ }
+ 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 (IsDisabledId(id))
+ {
+ // Nothing to do. Qubit will stay disabled.
+ return;
+ }
+
+ FailIf(!IsExplicitlyAllocatedId(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
+ {
+ // 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/lib/Simulators/FullstateSimulator.cpp b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp
index 6d9103d6779..b6da62d9443 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
@@ -107,11 +108,13 @@ namespace Quantum
const QUANTUM_SIMULATOR handle = 0;
unsigned simulatorId = -1;
- unsigned nextQubitId = 0; // the QuantumSimulator expects contiguous ids, starting from 0
+ // the QuantumSimulator expects contiguous ids, starting from 0
+ std::unique_ptr qubitManager;
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->GetQubitId(qubit));
}
std::vector GetQubitIds(long num, Qubit* qubits) const
@@ -120,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;
}
@@ -156,6 +159,7 @@ namespace Quantum
typedef unsigned (*TInit)();
static TInit initSimulatorInstance = reinterpret_cast(this->GetProc("init"));
+ qubitManager = std::make_unique();
this->simulatorId = initSimulatorInstance();
}
~CFullstateSimulator()
@@ -195,10 +199,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
@@ -206,7 +210,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
new file mode 100644
index 00000000000..c45a74ce292
--- /dev/null
+++ b/src/Qir/Runtime/public/QubitManager.hpp
@@ -0,0 +1,255 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "CoreTypes.hpp"
+
+namespace Microsoft
+{
+namespace Quantum
+{
+ // 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 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:
+ 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. 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();
+
+ public:
+ CQubitManager(
+ QubitIdType initialQubitCapacity = DefaultQubitCapacity,
+ bool mayExtendCapacity = true,
+ bool encourageReuse = true);
+
+ // No complex scenarios for now. Don't need to support copying/moving.
+ CQubitManager(const CQubitManager&) = delete;
+ CQubitManager& operator = (const CQubitManager&) = delete;
+ virtual ~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, 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, int32_t qubitCountToRelease);
+
+ // Borrow (We treat borrowing as allocation currently)
+ Qubit Borrow();
+ void Borrow(Qubit* qubitsToBorrow, int32_t qubitCountToBorrow);
+ // Return (We treat returning as release currently)
+ void Return(Qubit qubit);
+ 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, int32_t qubitCountToDisable);
+
+ bool IsValidQubit(Qubit qubit) const;
+ bool IsDisabledQubit(Qubit qubit) const;
+ bool IsExplicitlyAllocatedQubit(Qubit qubit) const;
+ bool IsFreeQubitId(QubitIdType id) const;
+
+ QubitIdType GetQubitId(Qubit qubit) const;
+
+ // 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; }
+
+ 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.
+ // qubit: pointer to QUBIT
+ // Returns id of a qubit pointed to by qubit.
+ virtual QubitIdType QubitToId(Qubit qubit) const;
+
+ 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. 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. 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 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
+ {
+ 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 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
+ {
+ public:
+ QubitListInSharedArray FreeQubitsReuseProhibited;
+ QubitListInSharedArray FreeQubitsReuseAllowed;
+
+ RestrictedReuseArea() = default;
+ RestrictedReuseArea(QubitListInSharedArray freeQubits);
+ };
+
+ // 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:
+ // 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();
+ int32_t Count() const;
+ };
+
+ 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 IsValidId(QubitIdType id) const;
+ bool IsDisabledId(QubitIdType id) const;
+ bool IsFreeId(QubitIdType id) const;
+ bool IsExplicitlyAllocatedId(QubitIdType id) const;
+
+ // Configuration Properties:
+ bool mayExtendCapacity = true;
+ bool encourageReuse = true;
+
+ // 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;
+ };
+
+}
+}
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..966b36f9645
--- /dev/null
+++ b/src/Qir/Runtime/unittests/QubitManagerTests.cpp
@@ -0,0 +1,253 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#include
+
+#include "catch.hpp"
+
+#include "QubitManager.hpp"
+
+using namespace Microsoft::Quantum;
+
+TEST_CASE("Simple allocation and release of one qubit", "[QubitManagerBasic]")
+{
+ 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, false, true);
+ REQUIRE(qm->GetFreeQubitCount() == 1);
+ REQUIRE(qm->GetAllocatedQubitCount() == 0);
+ Qubit q = qm->Allocate();
+ REQUIRE(qm->GetQubitId(q) == 0);
+ REQUIRE(qm->GetFreeQubitCount() == 0);
+ REQUIRE(qm->GetAllocatedQubitCount() == 1);
+ REQUIRE_THROWS(qm->Allocate());
+ REQUIRE(qm->GetFreeQubitCount() == 0);
+ REQUIRE(qm->GetAllocatedQubitCount() == 1);
+ qm->Release(q);
+ REQUIRE(qm->GetFreeQubitCount() == 1);
+ REQUIRE(qm->GetAllocatedQubitCount() == 0);
+ Qubit q0 = qm->Allocate();
+ REQUIRE(qm->GetQubitId(q0) == 0);
+ REQUIRE(qm->GetFreeQubitCount() == 0);
+ REQUIRE(qm->GetAllocatedQubitCount() == 1);
+ qm->Release(q0);
+ REQUIRE(qm->GetFreeQubitCount() == 1);
+ 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]")
+{
+ std::unique_ptr qm = std::make_unique(2, false, true);
+ REQUIRE(qm->GetFreeQubitCount() == 2);
+ Qubit q0 = qm->Allocate();
+ Qubit q1 = qm->Allocate();
+ 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->GetQubitId(q0a) == 0); // It was the only one available
+ REQUIRE_THROWS(qm->Allocate());
+
+ qm->Release(q1);
+ qm->Release(q0a);
+ REQUIRE(qm->GetFreeQubitCount() == 2);
+ REQUIRE(qm->GetAllocatedQubitCount() == 0);
+
+ Qubit q0b = qm->Allocate();
+ Qubit q1a = qm->Allocate();
+ 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);
+
+ qm->Release(q0b);
+ qm->Release(q1a);
+}
+
+TEST_CASE("Extending capacity", "[QubitManager]")
+{
+ std::unique_ptr qm = std::make_unique(1, true, true);
+
+ Qubit q0 = qm->Allocate();
+ REQUIRE(qm->GetQubitId(q0) == 0);
+ Qubit q1 = qm->Allocate(); // This should double capacity
+ REQUIRE(qm->GetQubitId(q1) == 1);
+ REQUIRE(qm->GetFreeQubitCount() == 0);
+ REQUIRE(qm->GetAllocatedQubitCount() == 2);
+
+ qm->Release(q0);
+ Qubit q0a = qm->Allocate();
+ REQUIRE(qm->GetQubitId(q0a) == 0);
+ Qubit q2 = qm->Allocate(); // This should double capacity again
+ REQUIRE(qm->GetQubitId(q2) == 2);
+ REQUIRE(qm->GetFreeQubitCount() == 1);
+ REQUIRE(qm->GetAllocatedQubitCount() == 3);
+
+ qm->Release(q1);
+ qm->Release(q0a);
+ qm->Release(q2);
+ REQUIRE(qm->GetFreeQubitCount() == 4);
+ REQUIRE(qm->GetAllocatedQubitCount() == 0);
+
+ Qubit* qqq = new Qubit[3];
+ qm->Allocate(qqq, 3);
+ REQUIRE(qm->GetFreeQubitCount() == 1);
+ REQUIRE(qm->GetAllocatedQubitCount() == 3);
+ qm->Release(qqq, 3);
+ 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);
+
+ Qubit q0 = qm->Allocate();
+ REQUIRE(qm->GetQubitId(q0) == 0);
+
+ qm->StartRestrictedReuseArea();
+
+ // Allocates fresh qubit
+ Qubit q1 = qm->Allocate();
+ 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->GetQubitId(q2) == 2);
+ qm->Release(q2);
+
+ Qubit q2a = qm->Allocate(); // Getting the same one as the one that's just released.
+ REQUIRE(qm->GetQubitId(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 = new Qubit[2];
+ qm->Allocate(qqq, 2);
+ // OK to destruct qubit manager while qubits are still allocated.
+ REQUIRE_THROWS(qm->Allocate());
+}
+
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();
diff --git a/src/Simulation/Common/Simulators.Dev.props b/src/Simulation/Common/Simulators.Dev.props
index 62a9ed4319b..3efc2a5f4ef 100644
--- a/src/Simulation/Common/Simulators.Dev.props
+++ b/src/Simulation/Common/Simulators.Dev.props
@@ -5,8 +5,10 @@
bin\$(BuildConfiguration)\$(TargetFramework)\$(AssemblyName).xml$([MSBuild]::NormalizeDirectory($(MSBuildThisFileDirectory)..\..\..\))$([MSBuild]::NormalizePath($(EnlistmentRoot)src/Simulation/Native/build/drop))
+ $([MSBuild]::NormalizePath($(EnlistmentRoot)src/Simulation/qdk_sim_rs/drop))
+
$([MSBuild]::NormalizePath($(NativeBuildPath)/libMicrosoft.Quantum.Simulator.Runtime.dylib))$([MSBuild]::NormalizePath($(NativeBuildPath)/libMicrosoft.Quantum.Simulator.Runtime.so))
@@ -28,7 +30,25 @@
false
-
+
+
+
+ $([MSBuild]::NormalizePath($(ExperimentalSimBuildPath)/libqdk_sim.dylib))
+ $([MSBuild]::NormalizePath($(ExperimentalSimBuildPath)/libqdk_sim.so))
+ $([MSBuild]::NormalizePath($(ExperimentalSimBuildPath)/qdk_sim.dll))
+ $(ExperimentalSimDllMac)
+ $(ExperimentalSimDllLinux)
+ $(ExperimentalSimDllWindows)
+
+
+
+
+ Microsoft.Quantum.Experimental.Simulators.Runtime.dll
+ PreserveNewest
+ false
+
+
+
diff --git a/src/Simulation/Simulators.Tests/Circuits/TeleportTest.qs b/src/Simulation/Simulators.Tests/Circuits/TeleportTest.qs
index 64d4808065e..829ee22d483 100644
--- a/src/Simulation/Simulators.Tests/Circuits/TeleportTest.qs
+++ b/src/Simulation/Simulators.Tests/Circuits/TeleportTest.qs
@@ -1,42 +1,45 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Quantum.Simulation.Simulators.Tests.Circuits {
-
open Microsoft.Quantum.Intrinsic;
-
-
- operation TeleportTest () : Unit {
-
-
- using (qubits = Qubit[3]) {
- let q1 = qubits[0];
- let q2 = qubits[1];
- let q3 = qubits[2];
-
- // create a Bell pair
- H(q1);
- CNOT(q1, q2);
-
- // create quantum state
- H(q3);
- Rz(1.1, q3);
-
- // teleport
- CNOT(q3, q2);
- H(q3);
- Controlled X([q2], q1);
- Controlled Z([q3], q1);
-
- // check teleportation success
- Rz(-1.1, q1);
- H(q1);
-
- // Make sure all allocated qubits are retrurned to zero before release
- ResetAll(qubits);
+ open Microsoft.Quantum.Diagnostics;
+
+ @Test("QuantumSimulator")
+ // TODO: Disabled until we have a noise model for Rz.
+ // @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator")
+ operation TestTeleport() : Unit {
+ use q1 = Qubit();
+ use q2 = Qubit();
+ use q3 = Qubit();
+
+ // create a Bell pair
+ H(q1);
+ CNOT(q1, q2);
+
+ // create quantum state
+ H(q3);
+ Rz(1.1, q3);
+
+ // teleport
+ CNOT(q3, q2);
+ H(q3);
+ Controlled X([q2], q1);
+ Controlled Z([q3], q1);
+
+ // check teleportation success
+ Rz(-1.1, q1);
+ H(q1);
+
+ // Measure and make sure we get Zero.
+ // We do so without use of diagnostics functions and operations, since
+ // many don't exist yet at this point in the build.
+ if M(q1) != Zero {
+ fail "Expected Zero after teleportation, but got One.";
}
- }
-
-}
+ // Make sure all allocated qubits are retrurned to zero before release
+ ResetAll([q1, q2, q3]);
+ }
+}
diff --git a/src/Simulation/Simulators.Tests/OpenSystemsSimulatorTests/NativeInterfaceTests.cs b/src/Simulation/Simulators.Tests/OpenSystemsSimulatorTests/NativeInterfaceTests.cs
new file mode 100644
index 00000000000..cb95eaba9af
--- /dev/null
+++ b/src/Simulation/Simulators.Tests/OpenSystemsSimulatorTests/NativeInterfaceTests.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Quantum.Simulation.Core;
+using Microsoft.Quantum.Simulation.Simulators.Exceptions;
+using Xunit;
+
+namespace Microsoft.Quantum.Experimental
+{
+ public partial class NativeInterfaceTests
+ {
+ [Fact]
+ public void GetIdealNoiseModelByNameWorks()
+ {
+ var ideal = NativeInterface.GetNoiseModelByName("ideal");
+ // TODO: Add assertions here to check properties of the above noise model.
+ }
+
+ [Fact]
+ public void GetIdealStabilizerNoiseModelByNameWorks()
+ {
+ var idealStabilizer = NativeInterface.GetNoiseModelByName("ideal_stabilizer");
+ // TODO: Add assertions here to check properties of each noise model.
+ }
+
+ [Fact]
+ public void GetNoiseModelByNameThrowsExceptionForInvalidNames()
+ {
+ Assert.Throws(() => {
+ NativeInterface.GetNoiseModelByName("invalid");
+ });
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Simulation/Simulators.Tests/SerializationTests/NoiseModelSerializationTests.cs b/src/Simulation/Simulators.Tests/SerializationTests/NoiseModelSerializationTests.cs
new file mode 100644
index 00000000000..57af996e521
--- /dev/null
+++ b/src/Simulation/Simulators.Tests/SerializationTests/NoiseModelSerializationTests.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.IO;
+using System.Text;
+using System.Text.Json;
+using Microsoft.Quantum.Experimental;
+using Microsoft.Quantum.Simulation.Core;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.Quantum.Simulation.Simulators.Tests
+{
+ public class NoiseModelSerializationTests
+ {
+ private const string idealJson = @"{""initial_state"":{""n_qubits"":1,""data"":{""Mixed"":{""v"":1,""dim"":[2,2],""data"":[[1,0],[0,0],[0,0],[0,0]]}}},""i"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[1,0],[0,0],[0,0],[1,0]]}}},""x"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[0,0],[1,0],[1,0],[0,0]]}}},""y"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[0,0],[0,1],[0,-1],[0,0]]}}},""z"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[1,0],[0,0],[0,0],[-1,0]]}}},""h"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[0.7071067811865476,0],[0.7071067811865476,0],[0.7071067811865476,0],[-0.7071067811865476,0]]}}},""s"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,1.0]]}}},""s_adj"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[1.0,-0.0],[0.0,-0.0],[0.0,-0.0],[0.0,-1.0]]}}},""t"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.7071067811865476,0.7071067811865476]]}}},""t_adj"":{""n_qubits"":1,""data"":{""Unitary"":{""v"":1,""dim"":[2,2],""data"":[[1.0,-0.0],[0.0,-0.0],[0.0,-0.0],[0.7071067811865476,-0.7071067811865476]]}}},""cnot"":{""n_qubits"":2,""data"":{""Unitary"":{""v"":1,""dim"":[4,4],""data"":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0]]}}},""z_meas"":{""Effects"":[{""n_qubits"":1,""data"":{""KrausDecomposition"":{""v"":1,""dim"":[1,2,2],""data"":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0]]}}},{""n_qubits"":1,""data"":{""KrausDecomposition"":{""v"":1,""dim"":[1,2,2],""data"":[[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0]]}}}]}}";
+
+ [Fact]
+ public void IdealNoiseModelDeserializes()
+ {
+ var idealNoiseModel = JsonSerializer.Deserialize(idealJson);
+
+ // Assert some stuff about the ideal noise model to make sure we got it right.
+ idealNoiseModel.ZMeas.AssertTypeAnd(instrument =>
+ {
+ Assert.Equal(2, instrument.Effects.Count);
+ });
+ idealNoiseModel.Z.AssertTypeAnd(process =>
+ {
+ Assert.Equal(1, process.NQubits);
+ });
+ }
+
+ [Fact]
+ public void IdealNoiseModelRoundTrips()
+ {
+ var idealNoiseModel = (
+ NoiseModel.TryGetByName("ideal", out var model)
+ ? model
+ : throw new Exception("Failed to get noise model by name.")
+ );
+ idealNoiseModel.AssertSerializationRoundTrips();
+ }
+
+ [Fact]
+ public void IdealStabilizerNoiseModelRoundTrips()
+ {
+ var idealStabilizerModel = (
+ NoiseModel.TryGetByName("ideal_stabilizer", out var model)
+ ? model
+ : throw new Exception("Could not get noise model by name.")
+ );
+ idealStabilizerModel.AssertSerializationRoundTrips();
+ }
+ }
+}
diff --git a/src/Simulation/Simulators.Tests/SerializationTests/ProcessSerializationTests.cs b/src/Simulation/Simulators.Tests/SerializationTests/ProcessSerializationTests.cs
new file mode 100644
index 00000000000..1e518bff509
--- /dev/null
+++ b/src/Simulation/Simulators.Tests/SerializationTests/ProcessSerializationTests.cs
@@ -0,0 +1,118 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Text.Json;
+using Microsoft.Quantum.Experimental;
+using Microsoft.Quantum.Simulation.Core;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.Quantum.Simulation.Simulators.Tests
+{
+ public class ProcessSerializationTests
+ {
+ [Fact]
+ public void MixedPauliSerializesCorrectly()
+ {
+ var mixedPauli = new MixedPauliProcess(
+ 1,
+ new List<(double, IList)>
+ {
+ (0.9, new List { Pauli.PauliI }),
+ (0.1, new List { Pauli.PauliX }),
+ }
+ );
+ var actualJson = JsonSerializer.Serialize(mixedPauli);
+ @"{
+ ""n_qubits"": 1,
+ ""data"": {
+ ""MixedPauli"": [
+ [0.9, [""I""]],
+ [0.1, [""X""]]
+ ]
+ }
+ }".AssertJsonIsEqualTo(actualJson);
+ }
+
+ [Fact]
+ public void MixedPauliRoundTripsCorrectly()
+ {
+ var mixedPauli = new MixedPauliProcess(
+ 1,
+ new List<(double, IList)>
+ {
+ (0.9, new List { Pauli.PauliI }),
+ (0.1, new List { Pauli.PauliX }),
+ }
+ );
+ var expectedJson = JsonSerializer.Serialize(mixedPauli);
+ var actualJson = JsonSerializer.Serialize(JsonSerializer.Deserialize(expectedJson));
+ expectedJson.AssertJsonIsEqualTo(actualJson);
+ }
+
+ [Fact]
+ public void CnotSerializesCorrectly()
+ {
+ var cnot = new ChpOperation.Cnot
+ {
+ IdxControl = 0,
+ IdxTarget = 1
+ };
+ var json = JsonSerializer.Serialize(cnot);
+ var expectedJson = @"{
+ ""Cnot"": [0, 1]
+ }";
+
+ expectedJson.AssertJsonIsEqualTo(json);
+ }
+
+ [Fact]
+ public void HadamardSerializesCorrectly()
+ {
+ var h = new ChpOperation.Hadamard
+ {
+ IdxTarget = 1
+ };
+ var json = JsonSerializer.Serialize(h);
+ var expectedJson = @"{
+ ""Hadamard"": 1
+ }";
+
+ expectedJson.AssertJsonIsEqualTo(json);
+ }
+
+ [Fact]
+ public void PhaseSerializesCorrectly()
+ {
+ var s = new ChpOperation.Phase
+ {
+ IdxTarget = 1
+ };
+ var json = JsonSerializer.Serialize(s);
+ var expectedJson = @"{
+ ""Phase"": 1
+ }";
+
+ expectedJson.AssertJsonIsEqualTo(json);
+ }
+
+ [Fact]
+ public void AdjointPhaseSerializesCorrectly()
+ {
+ var sAdj = new ChpOperation.AdjointPhase
+ {
+ IdxTarget = 1
+ };
+ var json = JsonSerializer.Serialize(sAdj);
+ var expectedJson = @"{
+ ""AdjointPhase"": 1
+ }";
+
+ expectedJson.AssertJsonIsEqualTo(json);
+ }
+ }
+}
diff --git a/src/Simulation/Simulators.Tests/SerializationTests/SerializationExtensions.cs b/src/Simulation/Simulators.Tests/SerializationTests/SerializationExtensions.cs
new file mode 100644
index 00000000000..8194aa7b0b8
--- /dev/null
+++ b/src/Simulation/Simulators.Tests/SerializationTests/SerializationExtensions.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.IO;
+using System.Text;
+using System.Text.Json;
+using Microsoft.Quantum.Experimental;
+using Microsoft.Quantum.Simulation.Core;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.Quantum.Simulation.Simulators.Tests
+{
+ internal static class SerializationExtensions
+ {
+ internal static void AssertTypeAnd(this object obj, Action then)
+ {
+ if (!(obj is T t))
+ {
+ Assert.IsType(obj);
+ }
+ else
+ {
+ then(t);
+ }
+ }
+
+ internal static string Serialize(this JsonDocument document)
+ {
+ var stream = new MemoryStream();
+ var writer = new Utf8JsonWriter(stream);
+ document.WriteTo(writer);
+ writer.Flush();
+ return Encoding.UTF8.GetString(stream.ToArray());
+ }
+
+ internal static void AssertSerializationRoundTrips(this T obj)
+ {
+ var expected = JsonSerializer.Serialize(obj);
+ var actual = JsonSerializer.Serialize(JsonSerializer.Deserialize(expected));
+ expected.AssertJsonIsEqualTo(actual);
+ }
+
+ internal static void AssertJsonIsEqualTo(this string expectedJson, string actualJson)
+ {
+ // To get a stable text representation, we first parse both strings to JsonDocument
+ // objects, then re-serialize them. This avoids numerical precision issues in
+ // JToken.DeepEquals, and allows for highlighting diffs more easily.
+ var expectedNormalized = JsonDocument.Parse(expectedJson).Serialize();
+ var actualNormalized = JsonDocument.Parse(actualJson).Serialize();
+ Assert.Equal(expectedNormalized, actualNormalized);
+ }
+ }
+}
diff --git a/src/Simulation/Simulators.Tests/SerializationTests/StateSerializationTests.cs b/src/Simulation/Simulators.Tests/SerializationTests/StateSerializationTests.cs
new file mode 100644
index 00000000000..e90fce0465b
--- /dev/null
+++ b/src/Simulation/Simulators.Tests/SerializationTests/StateSerializationTests.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Text.Json;
+using Microsoft.Quantum.Experimental;
+using Microsoft.Quantum.Simulation.Core;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.Quantum.Simulation.Simulators.Tests
+{
+ public class StateSerializationTests
+ {
+ [Fact]
+ public void StabilizerStateSerializesCorrectly()
+ {
+ var state = new StabilizerState
+ {
+ NQubits = 2,
+ Table = new StabilizerState.TableArray
+ {
+ Data = new List
+ {
+ true, false, false, false, true, false
+ },
+ Dimensions = new List { 2, 3 }
+ }
+ };
+ var json = JsonSerializer.Serialize(state);
+ var expectedJson = @"{
+ ""n_qubits"": 2,
+ ""data"": {
+ ""Stabilizer"": {
+ ""n_qubits"": 2,
+ ""table"": {
+ ""v"": 1,
+ ""dim"": [2, 3],
+ ""data"": [true,false,false,false,true,false]
+ }
+ }
+ }
+ }";
+
+ expectedJson.AssertJsonIsEqualTo(json);
+ }
+
+ [Fact]
+ public void StabilizerArrayDeserializesCorrectly()
+ {
+ var expectedJson = @"
+ {
+ ""v"": 1,
+ ""dim"": [2, 3],
+ ""data"": [true,false,false,false,true,false]
+ }
+ ";
+ var reader = new Utf8JsonReader(System.Text.Encoding.UTF8.GetBytes(expectedJson));
+ var array = JsonSerializer.Deserialize(ref reader);
+ var state = new StabilizerState
+ {
+ NQubits = 2,
+ Table = array
+ };
+ }
+ }
+}
diff --git a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Arrays/Tests.qs b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Arrays/Tests.qs
index 3bbc428984a..518bb5128ea 100644
--- a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Arrays/Tests.qs
+++ b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Arrays/Tests.qs
@@ -8,6 +8,7 @@ namespace Microsoft.Quantum.Arrays {
/// Checks that empty arrays are indeed empty.
@Test("QuantumSimulator")
@Test("ToffoliSimulator")
+ @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator")
function EmptyArraysAreEmpty() : Unit {
Fact(
Length(EmptyArray()) == 0,
diff --git a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Random/Tests.qs b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Random/Tests.qs
index c646960b606..3dfe4f48f75 100644
--- a/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Random/Tests.qs
+++ b/src/Simulation/Simulators.Tests/TestProjects/IntrinsicTests/Random/Tests.qs
@@ -14,7 +14,7 @@ namespace Microsoft.Quantum.Tests {
internal function SampleMeanAndVariance(samples : Double[]) : (Double, Double) {
mutable meanAcc = 0.0;
mutable varAcc = 0.0;
- for (idx in 0..Length(samples) - 1) {
+ for idx in 0..Length(samples) - 1 {
let sample = samples[idx];
let oldMeanAcc = meanAcc;
let delta = (sample - meanAcc);
@@ -27,7 +27,7 @@ namespace Microsoft.Quantum.Tests {
internal operation EstimateMeanAndVariance(dist : ContinuousDistribution, nSamples : Int) : (Double, Double) {
mutable samples = new Double[nSamples];
- for (idx in 0..nSamples - 1) {
+ for idx in 0..nSamples - 1 {
set samples w/= idx <- dist::Sample();
}
return SampleMeanAndVariance(samples);
@@ -60,8 +60,9 @@ namespace Microsoft.Quantum.Tests {
/// Checks that @"microsoft.quantum.random.drawrandomdouble" obeys ranges.
@Test("QuantumSimulator")
@Test("ToffoliSimulator")
+ @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator")
operation CheckDrawRandomDoubleObeysRanges() : Unit {
- for (j in 0..10000) {
+ for j in 0..10000 {
let random = DrawRandomDouble(0.0, 1.0);
if (random < 0.0 or random > 1.0) {
fail $"DrawRandomDouble(0.0, 1.0) returned {random}, outside the allowed interval.";
@@ -73,6 +74,7 @@ namespace Microsoft.Quantum.Tests {
/// Checks that @"microsoft.quantum.random.drawrandomdint" obeys ranges.
@Test("QuantumSimulator")
@Test("ToffoliSimulator")
+ @Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator")
operation CheckDrawRandomIntObeysRanges() : Unit {
mutable randomInt = DrawRandomInt(0, 45);
if (randomInt > 45 or randomInt < 0) {
diff --git a/src/Simulation/Simulators/Microsoft.Quantum.Simulators.csproj b/src/Simulation/Simulators/Microsoft.Quantum.Simulators.csproj
index 1cb8ed1a068..9baf8e3e8b6 100644
--- a/src/Simulation/Simulators/Microsoft.Quantum.Simulators.csproj
+++ b/src/Simulation/Simulators/Microsoft.Quantum.Simulators.csproj
@@ -24,6 +24,12 @@
+
+
+
+
+
+
runtimes\win-x64\native\%(RecursiveDir)%(FileName)%(Extension)
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/ArrayConverter.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/ArrayConverter.cs
new file mode 100644
index 00000000000..67a23acd2af
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/ArrayConverter.cs
@@ -0,0 +1,251 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using NumSharp;
+
+namespace Microsoft.Quantum.Experimental
+{
+ ///
+ /// Converts instances of dtype double
+ /// and with a trailing index of length 2 to and from a JSON
+ /// serialization.
+ ///
+ ///
+ ///
+ /// The JSON representation consumed and produced by this converter is
+ /// compatible with the representation used by Rust's ndarray
+ /// and serde crates.
+ ///
+ ///
+ /// In particular, each JSON object produced by this converter has
+ /// three properties:
+ ///
+ ///
+ /// "v": Indicates the ndarray format version being
+ /// being used. Always 1 for this implementation.
+ /// "dim": Lists the dimensions of the array being
+ /// serialized. Will always contain one less dimension than the
+ /// original array, as one dimension represents the real and imaginary
+ /// components of each element.
+ /// "data": Lists the elements of the array, with the
+ /// right-most dimension varying the fastest (defined by ndarray
+ /// as "logical order", and by NumPy as "C-ordered").
+ ///
+ ///
+ public class ComplexArrayConverter : JsonConverter
+ {
+ public override NDArray Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ // By the end, we need to have read "v", "dim", and "data".
+ int? version = null;
+ List? dims = null;
+ List? data = null;
+
+ // We require that the reader be in the right state to read an
+ // object.
+ if (reader.TokenType != JsonTokenType.StartObject)
+ {
+ throw new JsonException();
+ }
+
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndObject)
+ {
+ // We're at the end of the array, and can break out of the
+ // read loop.
+ break;
+ }
+
+ // If it's not the end of the object, the current token needs
+ // to be a property name.
+ if (reader.TokenType != JsonTokenType.PropertyName)
+ {
+ throw new JsonException();
+ }
+
+ var propertyName = reader.GetString();
+
+ switch (propertyName)
+ {
+ case "v":
+ reader.Read();
+ version = reader.GetInt32();
+ break;
+
+ case "dim":
+ dims = new List();
+ reader.Require(JsonTokenType.StartArray);
+ while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
+ {
+ dims.Add(reader.GetInt32());
+ }
+ break;
+
+ case "data":
+ data = new List();
+ reader.Require(JsonTokenType.StartArray);
+
+ while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
+ {
+ // We expect an inner array at this point, so
+ // go on and read the begin array token.
+ reader.Require(JsonTokenType.StartArray, read: false);
+
+ reader.Read();
+ var real = reader.GetDouble();
+
+ reader.Read();
+ var imag = reader.GetDouble();
+
+ reader.Require(JsonTokenType.EndArray);
+
+ data.Add(new Complex(real, imag));
+ }
+ break;
+
+ default:
+ throw new JsonException();
+ }
+ }
+
+ // At this point, none of version, dims, or data should be null.
+ if (version == null) throw new JsonException(nameof(version));
+ if (dims == null) throw new JsonException(nameof(dims));
+ if (data == null) throw new JsonException(nameof(data));
+
+ // We now know we have everything we need to make the new array.
+ // In doing so, we allocate an ndarray with of shape (nElements, 2)
+ // where the last index represents real-vs-imag. We'll reshape
+ // it to the correct shape at the end.
+ var array = np.zeros((data.Count, 2));
+ foreach (var idx in Enumerable.Range(0, data.Count))
+ {
+ var element = data[idx];
+ array[idx, 0] = element.Real;
+ array[idx, 1] = element.Imaginary;
+ }
+ return np.reshape(array, dims.Concat(new [] { 2 }).ToArray());
+ }
+
+ public override void Write(Utf8JsonWriter writer, NDArray value, JsonSerializerOptions options)
+ {
+ // Before proceeding, check that `value` is complex-like. That is,
+ // that `value` is of dtype double, and has a trailing dimension
+ // of length 2.
+ if (!value.IsComplexLike())
+ {
+ throw new ArgumentException($"Cannot serialize ndarray, as it is not complex-like: {value}");
+ }
+
+ writer.WriteStartObject();
+ writer.WriteNumber("v", 1);
+
+ writer.WritePropertyName("dim");
+ writer.WriteStartArray();
+ foreach (var dim in value.shape[0..^1])
+ {
+ writer.WriteNumberValue(dim);
+ }
+ writer.WriteEndArray();
+
+ writer.WritePropertyName("data");
+ writer.WriteStartArray();
+ // By default, NumSharp reshapes in C-order, matching
+ // ndarray's logical ordering. Thus, we reshape down to
+ // a two-axis array, and loop over the left most axis
+ // (corresponding to elements of the serialized array),
+ // leaving the second axis (corresponding to
+ // real-vs-imag).
+ var nElements = value.shape[0..^1].Aggregate((acc, dim) => acc * dim);
+ var flattened = value.reshape((nElements, 2));
+ foreach (var idx in Enumerable.Range(0, flattened.shape[0]))
+ {
+ var element = flattened[idx];
+ // Each element is a JSON array `[real, imag]`.
+ writer.WriteStartArray();
+ writer.WriteNumberValue((double) element[0]);
+ writer.WriteNumberValue((double) element[1]);
+ writer.WriteEndArray();
+ }
+ writer.WriteEndArray();
+ writer.WriteEndObject();
+ }
+
+ public static (int nQubits, string kind, NDArray data) ReadQubitSizedArray(ref Utf8JsonReader reader, JsonSerializerOptions options)
+ {
+ var arrayConverter = new ComplexArrayConverter();
+ int? nQubits = null;
+ NDArray? data = null;
+ string? kind = null;
+
+ // We require that the reader be in the right state to read an
+ // object.
+ if (reader.TokenType != JsonTokenType.StartObject)
+ {
+ throw new JsonException();
+ }
+
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndObject)
+ {
+ // We're at the end of the array, and can break out of the
+ // read loop.
+ break;
+ }
+
+ // If it's not the end of the object, the current token needs
+ // to be a property name.
+ if (reader.TokenType != JsonTokenType.PropertyName)
+ {
+ throw new JsonException();
+ }
+
+ var propertyName = reader.GetString();
+
+ switch (propertyName)
+ {
+ case "n_qubits":
+ reader.Read();
+ nQubits = reader.GetInt32();
+ break;
+
+ case "data":
+ // Here, we expect an object with one property indicating
+ // the kind of state. The value of that property can
+ // be read using the complexarrayconverter from above.
+ reader.Require(JsonTokenType.StartObject);
+ reader.Require(JsonTokenType.PropertyName);
+ kind = reader.GetString();
+
+ // Advance the reader onto the array itself and use
+ // the converter.
+ reader.Read();
+ data = arrayConverter.Read(ref reader, typeof(NDArray), options);
+
+ // Finally, require an end to the object.
+ reader.Require(JsonTokenType.EndObject);
+ break;
+
+ default:
+ throw new JsonException($"Unexpected property name {propertyName}.");
+ }
+ }
+
+ if (nQubits == null) throw new JsonException(nameof(nQubits));
+ if (data == null) throw new JsonException(nameof(data));
+ if (kind == null) throw new JsonException(nameof(kind));
+
+ return (nQubits.Value, kind!, data!);
+ }
+ }
+}
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/ChpDecomposition.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/ChpDecomposition.cs
new file mode 100644
index 00000000000..85900a3e4a7
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/ChpDecomposition.cs
@@ -0,0 +1,132 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Quantum.Experimental
+{
+
+ public class ChpOperationConverter : JsonConverter
+ {
+ public override ChpOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ reader.Require(JsonTokenType.StartObject, read: false);
+ reader.Require(JsonTokenType.PropertyName);
+ ChpOperation? operation = null;
+ ulong idx;
+ switch (reader.GetString())
+ {
+ case "Cnot":
+ var idxs = JsonSerializer.Deserialize>(ref reader);
+ operation = new ChpOperation.Cnot
+ {
+ IdxControl = idxs[0],
+ IdxTarget = idxs[1]
+ };
+ break;
+
+ case "Hadamard":
+ reader.Read();
+ idx = reader.GetUInt64();
+ operation = new ChpOperation.Hadamard
+ {
+ IdxTarget = idx
+ };
+ break;
+
+ case "Phase":
+ reader.Read();
+ idx = reader.GetUInt64();
+ operation = new ChpOperation.Phase
+ {
+ IdxTarget = idx
+ };
+ break;
+
+ case "AdjointPhase":
+ reader.Read();
+ idx = reader.GetUInt64();
+ operation = new ChpOperation.AdjointPhase
+ {
+ IdxTarget = idx
+ };
+ break;
+ }
+ if (operation == null)
+ {
+ throw new JsonException("Did not read an operation.");
+ }
+ reader.Require(JsonTokenType.EndObject);
+ return operation;
+ }
+
+ public override void Write(Utf8JsonWriter writer, ChpOperation value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WritePropertyName(
+ value switch
+ {
+ ChpOperation.Cnot _ => "Cnot",
+ ChpOperation.Hadamard _ => "Hadamard",
+ ChpOperation.Phase _ => "Phase",
+ ChpOperation.AdjointPhase _ => "AdjointPhase",
+ _ => throw new JsonException()
+ }
+ );
+ if (value is ChpOperation.Cnot cnot)
+ {
+ writer.WriteStartArray();
+ writer.WriteNumberValue(cnot.IdxControl);
+ writer.WriteNumberValue(cnot.IdxTarget);
+ writer.WriteEndArray();
+ }
+ else
+ {
+ writer.WriteNumberValue(
+ value switch
+ {
+ ChpOperation.AdjointPhase { IdxTarget: var target } => target,
+ ChpOperation.Phase { IdxTarget: var target } => target,
+ ChpOperation.Hadamard { IdxTarget: var target } => target,
+ _ => throw new JsonException()
+ }
+ );
+ }
+ writer.WriteEndObject();
+ }
+ }
+
+ [JsonConverter(typeof(ChpOperationConverter))]
+ public abstract class ChpOperation
+ {
+ private ChpOperation()
+ { }
+
+ public class Cnot : ChpOperation
+ {
+ public ulong IdxControl { get; set; }
+ public ulong IdxTarget { get; set; }
+ }
+
+ public class Hadamard : ChpOperation
+ {
+ public ulong IdxTarget { get; set; }
+ }
+
+ public class Phase : ChpOperation
+ {
+ public ulong IdxTarget { get; set; }
+ }
+
+ public class AdjointPhase : ChpOperation
+ {
+ public ulong IdxTarget { get; set; }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/DelegatedConverter.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/DelegatedConverter.cs
new file mode 100644
index 00000000000..7ec7db03fa9
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/DelegatedConverter.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Numerics;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using NumSharp;
+
+namespace Microsoft.Quantum.Experimental
+{
+ public class DelegatedConverter : Newtonsoft.Json.JsonConverter
+ {
+ public override T ReadJson(JsonReader reader, Type objectType, [AllowNull] T existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
+ {
+ var serialized = JToken.ReadFrom(reader).ToString();
+ return System.Text.Json.JsonSerializer.Deserialize(serialized);
+ }
+
+ public override void WriteJson(JsonWriter writer, [AllowNull] T value, Newtonsoft.Json.JsonSerializer serializer)
+ {
+ var serialized = System.Text.Json.JsonSerializer.Serialize(value);
+ var deserialized = Newtonsoft.Json.Linq.JToken.Parse(serialized);
+ deserialized.WriteTo(writer);
+ }
+ }
+}
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Extensions.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Extensions.cs
new file mode 100644
index 00000000000..670234325ab
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Extensions.cs
@@ -0,0 +1,164 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json;
+using System.Collections.Generic;
+using System.Linq;
+using NumSharp;
+using System;
+
+namespace Microsoft.Quantum.Experimental
+{
+ internal delegate Func ReaderContinuation(ref Utf8JsonReader reader, string variant);
+ internal static class Extensions
+ {
+ internal static bool HasProperty(this JsonElement element, string propertyName) =>
+ element.TryGetProperty(propertyName, out var _);
+
+ internal static string AsLaTeXMatrixOfComplex(this NDArray array) =>
+ // NB: Assumes 𝑛 × 𝑛 × 2 array, where the trailing index is
+ // [real, imag].
+ // TODO: Consolidate with logic at:
+ // https://github.com/microsoft/QuantumLibraries/blob/505fc27383c9914c3e1f60fb63d0acfe60b11956/Visualization/src/DisplayableUnitaryEncoders.cs#L43
+ string.Join(
+ "\\\\\n",
+ Enumerable
+ .Range(0, array.Shape[0])
+ .Select(
+ idxRow => string.Join(" & ",
+ Enumerable
+ .Range(0, array.Shape[1])
+ .Select(
+ idxCol => $"{array[idxRow, idxCol, 0]} + {array[idxRow, idxCol, 1]} i"
+ )
+ )
+ )
+ );
+
+ internal static IEnumerable IterateOverLeftmostIndex(this NDArray array)
+ {
+ foreach (var idx in Enumerable.Range(0, array.shape[0]))
+ {
+ yield return array[idx, Slice.Ellipsis];
+ }
+ }
+
+ internal static string AsTextMatrixOfComplex(this NDArray array, string rowSep = "\n") =>
+ // NB: Assumes 𝑛 × 𝑛 × 2 array, where the trailing index is
+ // [real, imag].
+ // TODO: Consolidate with logic at:
+ // https://github.com/microsoft/QuantumLibraries/blob/505fc27383c9914c3e1f60fb63d0acfe60b11956/Visualization/src/DisplayableUnitaryEncoders.cs#L43
+ "[" + rowSep + string.Join(
+ rowSep,
+ Enumerable
+ .Range(0, array.Shape[0])
+ .Select(
+ idxRow => "[" + string.Join(", ",
+ Enumerable
+ .Range(0, array.Shape[1])
+ .Select(
+ idxCol => $"{array[idxRow, idxCol, 0]} + {array[idxRow, idxCol, 1]} i"
+ )
+ ) + "]"
+ )
+ ) + rowSep + "]";
+
+ public static void Require(this ref Utf8JsonReader reader, JsonTokenType type, bool read = true)
+ {
+ if (read) reader.Read();
+ if (reader.TokenType != type)
+ {
+ // Try to read what it actually was.
+ string? value = reader.TokenType switch
+ {
+ JsonTokenType.String => reader.GetString(),
+ JsonTokenType.Number => reader.GetDecimal().ToString(),
+ JsonTokenType.True => "true",
+ JsonTokenType.False => "false",
+ JsonTokenType.Null => "null",
+ JsonTokenType.PropertyName => reader.GetString(),
+ _ => null
+ };
+ throw new JsonException($"Expected a JSON token of type {type}, got {reader.TokenType} instead.{(value == null ? "" : $"\nValue: {value}")}");
+ }
+ }
+ public static bool IsComplexLike(this NDArray array) =>
+ array.dtype == typeof(double) &&
+ array.shape[^1] == 2;
+
+ public static Func Bind(this TInput input, Func action) =>
+ (completion) => action(completion, input);
+
+ internal static TResult ReadQubitSizedData(this ref Utf8JsonReader reader, ReaderContinuation readData)
+ {
+
+ reader.Require(JsonTokenType.StartObject, read: false);
+
+ int? nQubits = null;
+ Func? completion = null;
+
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndObject)
+ {
+ // We're at the end of the object, and can break out of the
+ // read loop.
+ break;
+ }
+
+ // If it's not the end of the object, the current token needs
+ // to be a property name.
+ if (reader.TokenType != JsonTokenType.PropertyName)
+ {
+ throw new JsonException();
+ }
+
+ var propertyName = reader.GetString();
+
+ switch (propertyName)
+ {
+ case "n_qubits":
+ reader.Read();
+ nQubits = reader.GetInt32();
+ break;
+
+ case "data":
+ // In most cases, we expect an object with one property
+ // indicating
+ // the kind of data for the object.
+ // For variants of type unit, it's just the string.
+ reader.Read();
+ if (reader.TokenType == JsonTokenType.StartObject)
+ {
+ reader.Require(JsonTokenType.PropertyName);
+ var kind = reader.GetString();
+
+ reader.Read();
+ completion = readData(ref reader, kind);
+
+ // Finally, require an end to the object.
+ reader.Require(JsonTokenType.EndObject);
+ }
+ else if (reader.TokenType == JsonTokenType.String)
+ {
+ var kind = reader.GetString();
+ completion = readData(ref reader, kind);
+ }
+ else
+ {
+ throw new JsonException($"Expected either the start of an object or a string.");
+ }
+ break;
+
+ default:
+ throw new JsonException($"Unexpected property name {propertyName}.");
+ }
+ }
+
+ if (nQubits == null) throw new JsonException(nameof(nQubits));
+ if (completion == null) throw new JsonException();
+
+ return completion(nQubits.Value);
+ }
+ }
+}
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Instrument.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Instrument.cs
new file mode 100644
index 00000000000..f99737d0a54
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Instrument.cs
@@ -0,0 +1,92 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using NumSharp;
+
+namespace Microsoft.Quantum.Experimental
+{
+ public class InstrumentConverter : JsonConverter
+ {
+ public override Instrument Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ // We use the technique at
+ // https://stackoverflow.com/questions/58074304/is-polymorphic-deserialization-possible-in-system-text-json/59744873#59744873
+ // to implement polymorphic deserialization, based on the property name.
+ reader.Require(JsonTokenType.StartObject, read: false);
+ reader.Require(JsonTokenType.PropertyName);
+
+ var variant = reader.GetString();
+
+ Instrument result = variant switch
+ {
+ "Effects" => new EffectsInstrument
+ {
+ Effects = JsonSerializer.Deserialize>(ref reader)
+ },
+ "ZMeasurement" => JsonSerializer.Deserialize(ref reader),
+ _ => throw new JsonException($"Enum variant {variant} not yet supported.")
+ };
+
+ reader.Require(JsonTokenType.EndObject);
+
+ return result;
+ }
+
+ public override void Write(Utf8JsonWriter writer, Instrument value, JsonSerializerOptions options)
+ {
+
+ switch (value)
+ {
+ case EffectsInstrument effectsInstrument:
+ writer.WriteStartObject();
+ writer.WritePropertyName("Effects");
+ JsonSerializer.Serialize(writer, effectsInstrument.Effects);
+ writer.WriteEndObject();
+ break;
+
+ case ZMeasurementInstrument zInstrument:
+ writer.WriteStartObject();
+ writer.WritePropertyName("ZMeasurement");
+ JsonSerializer.Serialize(writer, zInstrument);
+ writer.WriteEndObject();
+ break;
+
+ default:
+ throw new JsonException($"Enum variant {value.GetType()} not yet supported.");
+ }
+
+ }
+ }
+
+ [JsonConverter(typeof(InstrumentConverter))]
+ public abstract class Instrument
+ {
+
+ }
+
+ public class EffectsInstrument : Instrument
+ {
+ [JsonPropertyName("effects")]
+ public IList Effects { get; set; } = new List();
+
+ public override string ToString() =>
+ $"Instrument {{ Effects = {String.Join(", ", Effects.Select(effect => effect.ToString()))} }}";
+ }
+
+ public class ZMeasurementInstrument : Instrument
+ {
+ [JsonPropertyName("pr_readout_error")]
+ public double PrReadoutError { get; set; } = 0.0;
+
+ public override string ToString() =>
+ $"Instrument {{ Z measurement with readout error = {PrReadoutError} }}";
+ }
+}
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/NoiseModel.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/NoiseModel.cs
new file mode 100644
index 00000000000..79c122a102d
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/NoiseModel.cs
@@ -0,0 +1,73 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using NumSharp;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.Quantum.Experimental
+{
+ // NB: To make this compatible with Newtonsoft.Json as well as
+ // System.Text.Json, we use a Newtonsoft converter that delegates to
+ // S.T.Json.
+ [Newtonsoft.Json.JsonConverter(typeof(DelegatedConverter))]
+ public class NoiseModel
+ {
+ [JsonPropertyName("initial_state")]
+ public State? InitialState { get; set; }
+
+ [JsonPropertyName("cnot")]
+ public Process? Cnot { get; set; }
+
+ [JsonPropertyName("i")]
+ public Process? I { get; set; }
+
+ [JsonPropertyName("s")]
+ public Process? S { get; set; }
+
+ [JsonPropertyName("s_adj")]
+ public Process? SAdj { get; set; }
+
+ [JsonPropertyName("t")]
+ public Process? T { get; set; }
+
+ [JsonPropertyName("t_adj")]
+ public Process? TAdj { get; set; }
+
+ [JsonPropertyName("h")]
+ public Process? H { get; set; }
+
+ [JsonPropertyName("x")]
+ public Process? X { get; set; }
+
+ [JsonPropertyName("y")]
+ public Process? Y { get; set; }
+
+ [JsonPropertyName("z")]
+ public Process? Z { get; set; }
+
+ [JsonPropertyName("z_meas")]
+ public Instrument? ZMeas { get; set; }
+
+ public static bool TryGetByName(string name, [NotNullWhen(true)] out NoiseModel? model)
+ {
+ try
+ {
+ model = NativeInterface.GetNoiseModelByName(name);
+ return true;
+ }
+ catch (SimulationException)
+ {
+ model = null;
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Process.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Process.cs
new file mode 100644
index 00000000000..cde27687137
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/Process.cs
@@ -0,0 +1,263 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.Quantum.Simulation.Core;
+using NumSharp;
+using static System.Math;
+
+namespace Microsoft.Quantum.Experimental
+{
+ public class ProcessConverter : JsonConverter
+ {
+ private static IList<(double, IList)> ReadMixedPauliData(ref Utf8JsonReader reader)
+ {
+ var results = new List<(double, IList)>();
+ reader.Require(JsonTokenType.StartArray, read: false);
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndArray)
+ {
+ break;
+ }
+
+ reader.Require(JsonTokenType.StartArray, read: false);
+ reader.Read();
+ var p = reader.GetDouble();
+ var ops = new List();
+ reader.Require(JsonTokenType.StartArray);
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndArray)
+ {
+ break;
+ }
+
+ ops.Add(reader.GetString() switch
+ {
+ "I" => Pauli.PauliI,
+ "X" => Pauli.PauliX,
+ "Y" => Pauli.PauliY,
+ "Z" => Pauli.PauliZ,
+ var unknown => throw new JsonException($"Expected I, X, Y, or Z for a Pauli value, but got {unknown}.")
+ });
+ }
+ reader.Require(JsonTokenType.EndArray);
+
+ results.Add((p, ops));
+ }
+ return results;
+ }
+
+ public override Process Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ reader.Require(JsonTokenType.StartObject, read: false);
+
+ var arrayConverter = new ComplexArrayConverter();
+ return reader.ReadQubitSizedData((ref Utf8JsonReader reader, string kind) =>
+ kind switch
+ {
+ "Unitary" => arrayConverter.Read(ref reader, typeof(NDArray), options).Bind(
+ (int nQubits, NDArray data) => new UnitaryProcess(nQubits, data)
+ ),
+ "KrausDecomposition" => arrayConverter.Read(ref reader, typeof(NDArray), options).Bind(
+ (int nQubits, NDArray data) => new KrausDecompositionProcess(nQubits, data)
+ ),
+ "MixedPauli" => ReadMixedPauliData(ref reader).Bind(
+ (int nQubits, IList<(double, IList)> data) => new MixedPauliProcess(nQubits, data)
+ ),
+ "Sequence" => JsonSerializer.Deserialize>(ref reader).Bind(
+ (int nQubits, IList processes) => new SequenceProcess(nQubits, processes)
+ ),
+ "ChpDecomposition" => JsonSerializer.Deserialize>(ref reader).Bind(
+ (int nQubits, IList operations) => new ChpDecompositionProcess(nQubits, operations)
+ ),
+ "Unsupported" => (null as object).Bind(
+ (int nQubits, object? _) => new UnsupportedProcess(nQubits)
+ ),
+ _ => throw new JsonException($"Process kind {kind} not supported for deserialization.")
+ }
+ );
+ }
+
+ public override void Write(Utf8JsonWriter writer, Process value, JsonSerializerOptions options)
+ {
+ var arrayConverter = new ComplexArrayConverter();
+
+ writer.WriteStartObject();
+ writer.WriteNumber("n_qubits", value.NQubits);
+
+ writer.WritePropertyName("data");
+ if (value is UnsupportedProcess)
+ {
+ writer.WriteStringValue("Unsupported");
+ }
+ else
+ {
+ writer.WriteStartObject();
+ writer.WritePropertyName(
+ value switch
+ {
+ UnitaryProcess _ => "Unitary",
+ KrausDecompositionProcess _ => "KrausDecomposition",
+ MixedPauliProcess _ => "MixedPauli",
+ SequenceProcess _ => "Sequence",
+ ChpDecompositionProcess _ => "ChpDecomposition",
+ _ => throw new JsonException()
+ }
+ );
+
+ if (value is ArrayProcess { Data: var data })
+ {
+ arrayConverter.Write(writer, data, options);
+ }
+ else if (value is MixedPauliProcess mixedPauliProcess)
+ {
+ writer.WriteStartArray();
+ foreach (var op in mixedPauliProcess.Operators)
+ {
+ writer.WriteStartArray();
+ writer.WriteNumberValue(op.Item1);
+ writer.WriteStartArray();
+ foreach (var p in op.Item2)
+ {
+ writer.WriteStringValue(p switch
+ {
+ Pauli.PauliI => "I",
+ Pauli.PauliX => "X",
+ Pauli.PauliY => "Y",
+ Pauli.PauliZ => "Z",
+ var unknown => throw new JsonException($"Unexpected Pauli value {unknown}.")
+ });
+ }
+ writer.WriteEndArray();
+ writer.WriteEndArray();
+ }
+ writer.WriteEndArray();
+ }
+ else if (value is ChpDecompositionProcess chpDecomposition)
+ {
+ JsonSerializer.Serialize(writer, chpDecomposition.Operations);
+ }
+ else if (value is SequenceProcess sequence)
+ {
+ JsonSerializer.Serialize(writer, sequence.Processes);
+ }
+ writer.WriteEndObject();
+ }
+ writer.WriteEndObject();
+ }
+ }
+
+ [JsonConverter(typeof(ProcessConverter))]
+ public abstract class Process
+ {
+ [JsonPropertyName("n_qubits")]
+ public int NQubits { get; }
+
+ internal Process(int nQubits)
+ {
+ NQubits = nQubits;
+ }
+ }
+
+ public abstract class ArrayProcess : Process
+ {
+ [JsonPropertyName("data")]
+ public NDArray Data { get; }
+
+ internal ArrayProcess(int nQubits, NDArray data) : base(nQubits)
+ {
+ this.Data = data;
+ }
+ }
+
+ public class MixedPauliProcess : Process
+ {
+ public IList<(double, IList)> Operators;
+
+ internal MixedPauliProcess(int nQubits, IList<(double, IList)> operators) : base(nQubits)
+ {
+ this.Operators = operators;
+ }
+ }
+
+ public class ChpDecompositionProcess : Process
+ {
+ public IList Operations;
+
+ internal ChpDecompositionProcess(int nQubits, IList operations) : base(nQubits)
+ {
+ this.Operations = operations;
+ }
+ }
+
+ public class SequenceProcess : Process
+ {
+ public IList Processes;
+
+ internal SequenceProcess(int nQubits, IList processes) : base(nQubits)
+ {
+ this.Processes = processes;
+ }
+ }
+
+ public class UnsupportedProcess : Process
+ {
+ internal UnsupportedProcess(int nQubits) : base(nQubits)
+ { }
+ }
+
+ public class UnitaryProcess : ArrayProcess
+ {
+ public UnitaryProcess(int nQubits, NDArray data) : base(nQubits, data)
+ {
+ // Unitary matrices should be of dimension (2^n, 2^n, 2), with the last
+ // index indicating real-vs-imag.
+ var dim = (int) Pow(2, nQubits);
+ if (data.shape.Length != 3 || data.shape[0] != dim || data.shape[1] != dim || data.shape[2] != 2)
+ {
+ throw new ArgumentException("Expected (2^nQubits, 2) array.", nameof(data));
+ }
+ }
+
+ public override string ToString() =>
+ $@"Unitary process on {NQubits} qubits: {Data.AsTextMatrixOfComplex(rowSep: " ")}";
+ }
+ public class KrausDecompositionProcess : ArrayProcess
+ {
+ public KrausDecompositionProcess(int nQubits, NDArray data) : base(nQubits, data)
+ {
+ // Kraus decompositions should have between 1 and 4^n operators,
+ // each of which should be 2^n by 2^n, for a final dims of
+ // [k, 2^n, 2^n, 2] where 1 ≤ k ≤ 4^n.
+ var dim = (int) Pow(2, nQubits);
+ var superDim = dim * dim;
+
+ if (data.shape.Length != 4 || data.shape[0] > superDim || data.shape[0] < 1
+ || data.shape[1] != dim
+ || data.shape[2] != dim
+ || data.shape[3] != 2)
+ {
+ throw new ArgumentException("Expected (k, 2^nQubits, 2^nQubits, 2) array.", nameof(data));
+ }
+ }
+
+ public override string ToString()
+ {
+ var ops = String.Join(",",
+ Data.IterateOverLeftmostIndex()
+ .Select(op => op.AsTextMatrixOfComplex(rowSep: " "))
+ );
+ return $@"Kraus decomposition of process on {NQubits} qubits: {ops}";
+ }
+ }
+
+}
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/README.md b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/README.md
new file mode 100644
index 00000000000..8fa99c08df3
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/README.md
@@ -0,0 +1,9 @@
+# Open systems simulation data model
+
+The C# classes in this folder define a data model and JSON serialization logic that can be used to exchange data with the native runtime for the open systems simulator.
+
+## Newtonsoft.Json versus System.Text.Json
+
+The JSON converters in this folder target System.Text.Json as this is the recommended path for .NET Core 3.1 and forward, both in terms of where new functionality will be developed, and in terms of performance improvements due to `Span` and other recent additions to the .NET platform.
+
+Since IQ# and many other Quantum Development Kit components use Newtonsoft.Json, however, the `DelegatedConverter` class allows for exposing a System.Text.Json converter as a Newtonsoft.Json converter; this has a significant performance implication, but provides a path forward for using System.Text.Json in the future.
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/State.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/State.cs
new file mode 100644
index 00000000000..439e3570b81
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/State.cs
@@ -0,0 +1,116 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using NumSharp;
+using static System.Math;
+
+namespace Microsoft.Quantum.Experimental
+{
+ [JsonConverter(typeof(StateConverter))]
+ public abstract class State
+ {
+ [JsonPropertyName("n_qubits")]
+ public int NQubits { get; set; } = 1;
+
+ internal State()
+ { }
+
+ internal State(int nQubits)
+ {
+ NQubits = nQubits;
+ }
+ }
+
+ public class StabilizerState : State
+ {
+ // Design note:
+ // We could use the same array converter as used of complex-like arrays
+ // of floats, but it's a bit easier in this case to directly
+ // deserialize into a type that represents the underlying data that
+ // we need.
+ public class TableArray
+ {
+ // When serializing multidimensional arrays with serde, the
+ // `ndarray` crate for Rust uses the "v" property to denote
+ // serialization schema versions. This property name is hardcoded
+ // at https://github.com/rust-ndarray/ndarray/blob/master/src/array_serde.rs#L96,
+ // such that we follow that property name here to make it easier
+ // to interoperate with `ndarray`.
+ [JsonPropertyName("v")]
+ public int SchemaVersion { get; set; } = 1;
+
+ [JsonPropertyName("dim")]
+ public List? Dimensions { get; set; }
+
+ [JsonPropertyName("data")]
+ public List? Data { get; set; }
+
+ [JsonIgnore]
+ public NDArray? AsArray =>
+ Dimensions == null || Data == null
+ ? null
+ : np.ndarray(
+ new Shape(Dimensions.ToArray()),
+ typeof(bool),
+ Data.ToArray()
+ );
+ }
+
+ [JsonPropertyName("table")]
+ public TableArray? Table { get; set; }
+
+ [JsonIgnore]
+ public NDArray? Data => Table?.AsArray;
+ }
+
+ public abstract class ArrayState : State
+ {
+ public NDArray Data { get; }
+
+ internal ArrayState(int nQubits, NDArray data) : base(nQubits)
+ {
+ this.Data = data;
+ }
+ }
+
+ public class PureState : ArrayState
+ {
+ public PureState(int nQubits, NDArray data) : base(nQubits, data)
+ {
+ // Pure states should be of dimension (2^n, 2), with the last
+ // index indicating real-vs-imag.
+ if (data.shape.Length != 2 || data.shape[0] != (int) (Pow(2, nQubits)) || data.shape[1] != 2)
+ {
+ throw new ArgumentException("Expected (2^nQubits, 2) array.", nameof(data));
+ }
+ }
+
+ // TODO: Override ToString here!
+ }
+
+ public class MixedState : ArrayState
+ {
+ public MixedState(int nQubits, NDArray data) : base(nQubits, data)
+ {
+ // Pure states should be of dimension (2^n, 2^n, 2), with the last
+ // index indicating real-vs-imag.
+ var dim = (int) Pow(2, nQubits);
+ if (data.shape.Length != 3 || data.shape[0] != dim || data.shape[1] != dim || data.shape[2] != 2)
+ {
+ throw new ArgumentException("Expected (2^nQubits, 2) array.", nameof(data));
+ }
+ }
+
+ public override string ToString() =>
+ $@"Mixed state on {NQubits} qubits: {Data.AsTextMatrixOfComplex(rowSep: " ")}";
+ }
+
+}
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/StateConverter.cs b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/StateConverter.cs
new file mode 100644
index 00000000000..22c1fa3867c
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/DataModel/StateConverter.cs
@@ -0,0 +1,112 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using NumSharp;
+using static System.Math;
+
+namespace Microsoft.Quantum.Experimental
+{
+ public class StateConverter : JsonConverter
+ {
+
+ /*
+ We expect JSON of the form:
+
+ {
+ "n_qubits": 1,
+ "data": {
+ "Mixed": {
+ "v": 1,
+ "dim": [2, 2],
+ "data": [
+ [1, 0],
+ [0, 0],
+ [0, 0],
+ [0, 0]
+ ]
+ }
+ }
+ }
+
+ Here, the presence of either $.data.Mixed or $.data.Pure tells us how
+ to interpret data.
+ */
+
+ public override State Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ reader.Require(JsonTokenType.StartObject, read: false);
+
+ var arrayConverter = new ComplexArrayConverter();
+ return reader.ReadQubitSizedData((ref Utf8JsonReader reader, string kind) =>
+ kind switch
+ {
+ "Pure" => arrayConverter.Read(ref reader, typeof(NDArray), options).Bind(
+ (int nQubits, NDArray data) => new PureState(nQubits, data)
+ ),
+ "Mixed" => arrayConverter.Read(ref reader, typeof(NDArray), options).Bind(
+ (int nQubits, NDArray data) => new MixedState(nQubits, data)
+ ),
+ "Stabilizer" => JsonSerializer.Deserialize(ref reader).Bind(
+ (int nQubits, StabilizerState state) =>
+ {
+ System.Diagnostics.Debug.Assert((state?.Data as object) != null);
+ System.Diagnostics.Debug.Assert(nQubits == state.NQubits);
+ return state;
+ }
+ ),
+ _ => throw new JsonException($"Unknown state kind {kind}.")
+ }
+ );
+ }
+
+ public override void Write(Utf8JsonWriter writer, State value, JsonSerializerOptions options)
+ {
+ var arrayConverter = new ComplexArrayConverter();
+
+ writer.WriteStartObject();
+ writer.WriteNumber("n_qubits", value.NQubits);
+
+ writer.WritePropertyName("data");
+ writer.WriteStartObject();
+ writer.WritePropertyName(
+ value switch
+ {
+ PureState _ => "Pure",
+ MixedState _ => "Mixed",
+ StabilizerState _ => "Stabilizer",
+ _ => throw new JsonException($"Unknown state type {value.GetType()}.")
+ }
+ );
+
+ if (value is ArrayState { Data: var data })
+ {
+ arrayConverter.Write(writer, data, options);
+ }
+ else if (value is StabilizerState stabilizerState)
+ {
+ var array = new StabilizerState.TableArray
+ {
+ Data = stabilizerState.Data.flat.ToArray().ToList(),
+ Dimensions = stabilizerState.Data.Shape.Dimensions.ToList()
+ };
+ writer.WriteStartObject();
+ writer.WritePropertyName("n_qubits");
+ writer.WriteNumberValue(stabilizerState.NQubits);
+ writer.WritePropertyName("table");
+ JsonSerializer.Serialize(writer, array);
+ writer.WriteEndObject();
+ }
+ writer.WriteEndObject();
+ writer.WriteEndObject();
+ }
+ }
+
+}
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/CZ.qs b/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/CZ.qs
new file mode 100644
index 00000000000..7fc5eea3f53
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/CZ.qs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.Quantum.Experimental {
+
+ internal operation ApplyCZUsingCNOT(control : Qubit, target : Qubit) : Unit {
+ within {
+ ApplyUncontrolledH(target);
+ } apply {
+ ApplySinglyControlledX(control, target);
+ }
+ }
+}
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/Interface.qs b/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/Interface.qs
new file mode 100644
index 00000000000..31059665b33
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/Interface.qs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.Quantum.Experimental {
+ open Microsoft.Quantum.Intrinsic;
+
+ operation ApplyUncontrolledH(target : Qubit) : Unit is Adj {
+ body intrinsic;
+ adjoint self;
+ }
+
+ operation ApplySinglyControlledX(control : Qubit, target : Qubit) : Unit is Adj {
+ body intrinsic;
+ adjoint self;
+ }
+
+}
+
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/README.md b/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/README.md
new file mode 100644
index 00000000000..f687ce8d0d6
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/Decompositions/README.md
@@ -0,0 +1,4 @@
+# TODO
+
+The decompositions in this folder should eventually be converted into a new "type 4" targeting package.
+We use the legacy approach to making intrinsics available to simulators, however, on a temporary basis for compatibility with Python and IQ#.
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/NativeInterface.cs b/src/Simulation/Simulators/OpenSystemsSimulator/NativeInterface.cs
new file mode 100644
index 00000000000..84db6154488
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/NativeInterface.cs
@@ -0,0 +1,265 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.IO;
+using System.Reflection;
+using System.Text.Json;
+using System.Runtime.InteropServices;
+using Microsoft.Quantum.Simulation.Core;
+using Newtonsoft.Json.Linq;
+
+#nullable enable
+
+namespace Microsoft.Quantum.Experimental
+{
+ ///
+ /// Represents an exception that is raised by native simulator code.
+ ///
+ [Serializable()]
+ public class SimulationException : Exception
+ {
+ private readonly string? source;
+
+ ///
+ /// The name of the native shared library which raised this
+ /// exception if known, null otherwise.
+ ///
+ public string? SourceLibrary => source;
+
+ internal SimulationException(string message, string? source) : base(message)
+ {
+ this.source = source;
+ }
+ }
+
+ ///
+ /// Abstracts away calls to and from the experimental simulators DLL.
+ ///
+ internal static class NativeInterface
+ {
+ public static event Action? OnVerbose = null;
+ private static void LogCall(string callName) =>
+ OnVerbose?.Invoke($"[VERBOSE] NativeInterface: calling {callName}.");
+
+ private static void CheckCall(Int64 errorCode)
+ {
+ if (errorCode != 0)
+ {
+ var error = _LastError();
+ throw new SimulationException($"Exception in native open systems simulator runtime: {error}", DLL_NAME);
+ }
+ }
+
+
+ public const string DLL_NAME = "Microsoft.Quantum.Experimental.Simulators.Runtime.dll";
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="lasterr")]
+ private static extern string _LastError();
+
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="get_simulator_info")]
+ private static extern string _GetSimulatorInfo();
+
+ internal static readonly JToken SimulatorInfo;
+
+ static NativeInterface()
+ {
+ SimulatorInfo = JToken.Parse(_GetSimulatorInfo());
+ }
+
+ public static string Name
+ {
+ get
+ {
+ return SimulatorInfo.Value("name");
+ }
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="init")]
+ private static extern Int64 _Init(uint initialCapacity, string representation, out uint simulatorId);
+
+ public static ulong Init(uint initialCapacity, string representation = "mixed")
+ {
+ LogCall("init");
+ CheckCall(_Init(initialCapacity, representation, out var simulatorId));
+ return simulatorId;
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="destroy")]
+ private static extern Int64 _Destroy(ulong simId);
+
+ public static void Destroy(ulong simId)
+ {
+ LogCall("destroy");
+ CheckCall(_Destroy(simId));
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="dump_to_console")]
+ private static extern void _DumpToConsole(ulong simId);
+
+ public static void DumpToConsole(ulong simId)
+ {
+ LogCall("dump_to_console");
+ _DumpToConsole(simId);
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="get_current_state")]
+ private static extern string _GetCurrentState(ulong simId);
+
+ public static State GetCurrentState(ulong simId)
+ {
+ LogCall("get_current_state");
+ return JsonSerializer.Deserialize(_GetCurrentState(simId));
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling =true, CallingConvention =CallingConvention.Cdecl, EntryPoint="get_noise_model_by_name")]
+ private static extern Int64 _GetNoiseModelByName(string name, out string noiseModel);
+
+ public static NoiseModel GetNoiseModelByName(string name)
+ {
+ LogCall("get_noise_model_by_name");
+ CheckCall(_GetNoiseModelByName(name, out var noiseModelJson));
+ return JsonSerializer.Deserialize(noiseModelJson);
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="get_noise_model")]
+ private static extern Int64 _GetNoiseModel(ulong simId, out string noiseModel);
+
+ public static NoiseModel GetNoiseModel(ulong simId)
+ {
+ LogCall("get_noise_model");
+ CheckCall(_GetNoiseModel(simId, out var noiseModelJson));
+ return JsonSerializer.Deserialize(noiseModelJson);
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="set_noise_model")]
+ private static extern Int64 _SetNoiseModel(ulong simId, string noiseModel);
+
+ public static void SetNoiseModel(ulong simId, NoiseModel noiseModel)
+ {
+ LogCall("set_noise_model");
+ string? jsonData = null;
+ try
+ {
+ jsonData = JsonSerializer.Serialize(noiseModel);
+ CheckCall(_SetNoiseModel(simId, jsonData));
+ }
+ catch (NotSupportedException ex)
+ {
+ throw new ArgumentException("Could not serialize noise model to JSON, as no suitable serializer was found.", ex);
+ }
+ catch (JsonException ex)
+ {
+ throw new Exception($"Could not serialize noise model: {ex.Message}", ex);
+ }
+ catch (SimulationException ex)
+ {
+ throw new Exception($"Could not set noise model from JSON: {ex.Message}\nJSON data:\n{jsonData}", ex);
+ }
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="set_noise_model_by_name")]
+ private static extern Int64 _SetNoiseModelByName(ulong simId, string name);
+
+ public static void SetNoiseModelByName(ulong simId, string name)
+ {
+ LogCall("set_noise_model_by_name");
+ CheckCall(_SetNoiseModelByName(simId, name));
+ }
+
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="h")]
+ private static extern Int64 _H(ulong simId, uint idx);
+
+ public static void H(ulong simId, Qubit q)
+ {
+ LogCall("h");
+ CheckCall(_H(simId, (uint)q.Id));
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="x")]
+ private static extern Int64 _X(ulong simId, uint idx);
+
+ public static void X(ulong simId, Qubit q)
+ {
+ LogCall("x");
+ CheckCall(_X(simId, (uint)q.Id));
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="y")]
+ private static extern Int64 _Y(ulong simId, uint idx);
+
+ public static void Y(ulong simId, Qubit q)
+ {
+ LogCall("y");
+ CheckCall(_Y(simId, (uint)q.Id));
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="z")]
+ private static extern Int64 _Z(ulong simId, uint idx);
+
+ public static void Z(ulong simId, Qubit q)
+ {
+ LogCall("z");
+ CheckCall(_Z(simId, (uint)q.Id));
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="s")]
+ private static extern Int64 _S(ulong simId, uint idx);
+
+ public static void S(ulong simId, Qubit q)
+ {
+ LogCall("s");
+ CheckCall(_S(simId, (uint)q.Id));
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="s_adj")]
+ private static extern Int64 _SAdj(ulong simId, uint idx);
+
+ public static void SAdj(ulong simId, Qubit q)
+ {
+ LogCall("s");
+ CheckCall(_SAdj(simId, (uint)q.Id));
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="t")]
+ private static extern Int64 _T(ulong simId, uint idx);
+
+ public static void T(ulong simId, Qubit q)
+ {
+ LogCall("t");
+ CheckCall(_T(simId, (uint)q.Id));
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="t_adj")]
+ private static extern Int64 _TAdj(ulong simId, uint idx);
+
+ public static void TAdj(ulong simId, Qubit q)
+ {
+ LogCall("t_adj");
+ CheckCall(_TAdj(simId, (uint)q.Id));
+ }
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="cnot")]
+ private static extern Int64 _CNOT(ulong simId, uint idxControl, uint idxTarget);
+
+ public static void CNOT(ulong simId, Qubit control, Qubit target)
+ {
+ LogCall("cnot");
+ CheckCall(_CNOT(simId, (uint)control.Id, (uint)target.Id));
+ }
+
+
+ [DllImport(DLL_NAME, ExactSpelling=true, CallingConvention=CallingConvention.Cdecl, EntryPoint="m")]
+ private static extern Int64 _M(ulong simId, uint idx, out uint result);
+
+ public static Result M(ulong simId, Qubit q)
+ {
+ LogCall("m");
+ CheckCall(_M(simId, (uint)q.Id, out var result));
+ return result == 1 ? Result.One : Result.Zero;
+ }
+
+ }
+}
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/OpenSystemsSimulator.cs b/src/Simulation/Simulators/OpenSystemsSimulator/OpenSystemsSimulator.cs
new file mode 100644
index 00000000000..dfe8e965f94
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/OpenSystemsSimulator.cs
@@ -0,0 +1,289 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Linq;
+using Microsoft.Quantum.Simulation.Core;
+using Microsoft.Quantum.Simulation.Common;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Microsoft.Quantum.Simulation.Simulators.Exceptions;
+using Microsoft.Quantum.Intrinsic.Interfaces;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.Quantum.Experimental
+{
+ // NB: This class should not implement IQSharpCore, but does so temporarily
+ // to make the simulator available to IQ# (note that the I in IQSharpCore
+ // refers to interfaces, and not to IQ# itself...)
+ public partial class OpenSystemsSimulator : SimulatorBase, IQSharpCore
+ {
+ public static JToken BuildInfo => NativeInterface.SimulatorInfo;
+
+ private readonly ulong Id;
+
+ public override string Name => NativeInterface.Name;
+
+ public NoiseModel NoiseModel
+ {
+ get
+ {
+ return NativeInterface.GetNoiseModel(Id);
+ }
+
+ set
+ {
+ NativeInterface.SetNoiseModel(Id, value);
+ }
+ }
+
+ public State CurrentState => NativeInterface.GetCurrentState(this.Id);
+
+ public class OpenSystemsQubitManager : QubitManager
+ {
+ private readonly OpenSystemsSimulator Parent;
+ public OpenSystemsQubitManager(OpenSystemsSimulator parent, uint capacity)
+ : base(capacity)
+ {
+ this.Parent = parent;
+ }
+
+ protected override void Release(Qubit qubit, bool wasUsedOnlyForBorrowing)
+ {
+ if (qubit != null && qubit.IsMeasured)
+ {
+ // Try to reset measured qubits.
+ // TODO: There are better ways to do this; increment on the
+ // design and make it customizable.
+ // FIXME: In particular, this implementation uses a lot of
+ // extraneous measurements.
+ if ((this.Parent as IIntrinsicMeasure).Body(new QArray(Pauli.PauliZ), new QArray(qubit)) == Result.One)
+ {
+ (this.Parent as IIntrinsicX).Body(qubit);
+ }
+ }
+ base.Release(qubit, wasUsedOnlyForBorrowing);
+ }
+ }
+
+ public OpenSystemsSimulator(uint capacity = 3, string representation = "mixed") : base(new QubitManager((long)capacity))
+ {
+ this.Id = NativeInterface.Init(capacity, representation);
+ }
+
+ void IIntrinsicExp.Body(IQArray paulis, double angle, IQArray targets)
+ {
+ throw new NotImplementedException();
+ }
+
+ void IIntrinsicExp.AdjointBody(IQArray paulis, double angle, IQArray targets)
+ {
+ throw new NotImplementedException();
+ }
+
+ void IIntrinsicExp.ControlledBody(IQArray controls, IQArray paulis, double angle, IQArray targets)
+ {
+ throw new NotImplementedException();
+ }
+
+ void IIntrinsicExp.ControlledAdjointBody(IQArray controls, IQArray paulis, double angle, IQArray targets)
+ {
+ throw new NotImplementedException();
+ }
+
+ void IIntrinsicH.Body(Qubit target)
+ {
+ NativeInterface.H(this.Id, target);
+ }
+
+ void IIntrinsicH.ControlledBody(IQArray controls, Qubit target)
+ {
+ throw new NotImplementedException();
+ }
+
+ Result IIntrinsicMeasure.Body(IQArray paulis, IQArray targets)
+ {
+ if (targets is { Count: 1 } && paulis is { Count: 1 } && paulis.Single() == Pauli.PauliZ)
+ {
+ return NativeInterface.M(this.Id, targets.Single());
+ }
+ else
+ {
+ // FIXME: Pass multiqubit and non-Z cases to decompositions.
+ throw new NotImplementedException();
+ }
+ }
+
+ void IIntrinsicR.Body(Pauli pauli, double angle, Qubit target)
+ {
+ if (pauli == Pauli.PauliI)
+ {
+ // Don't apply global phases on uncontrolled operations.
+ return;
+ }
+ throw new NotImplementedException("Arbitrary rotation with noise is not yet supported.");
+ }
+
+ void IIntrinsicR.AdjointBody(Pauli pauli, double angle, Qubit target)
+ {
+ (this as IIntrinsicR).Body(pauli, -angle, target);
+ }
+
+ void IIntrinsicR.ControlledBody(IQArray controls, Pauli pauli, double angle, Qubit target)
+ {
+ if (controls is { Count: 0 })
+ {
+ (this as IIntrinsicR).Body(pauli, angle, target);
+ }
+ else
+ {
+ throw new NotImplementedException("Arbitrary controlled rotation with noise is not yet supported.");
+ }
+ }
+
+ void IIntrinsicR.ControlledAdjointBody(IQArray controls, Pauli pauli, double angle, Qubit target)
+ {
+ (this as IIntrinsicR).ControlledBody(controls, pauli, -angle, target);
+ }
+
+ void IIntrinsicS.Body(Qubit target)
+ {
+ NativeInterface.S(this.Id, target);
+ }
+
+ void IIntrinsicS.AdjointBody(Qubit target)
+ {
+ NativeInterface.SAdj(this.Id, target);
+ }
+
+ void IIntrinsicS.ControlledBody(IQArray controls, Qubit target)
+ {
+ throw new NotImplementedException();
+ }
+
+ void IIntrinsicS.ControlledAdjointBody(IQArray controls, Qubit target)
+ {
+ throw new NotImplementedException();
+ }
+
+ void IIntrinsicT.Body(Qubit target)
+ {
+ NativeInterface.T(this.Id, target);
+ }
+
+ void IIntrinsicT.AdjointBody(Qubit target)
+ {
+ NativeInterface.TAdj(this.Id, target);
+ }
+
+ void IIntrinsicT.ControlledBody(IQArray controls, Qubit target)
+ {
+ throw new NotImplementedException();
+ }
+
+ void IIntrinsicT.ControlledAdjointBody(IQArray controls, Qubit target)
+ {
+ throw new NotImplementedException();
+ }
+
+ void IIntrinsicX.Body(Qubit target)
+ {
+ NativeInterface.X(this.Id, target);
+ }
+
+ void IIntrinsicX.ControlledBody(IQArray controls, Qubit target)
+ {
+ // TODO: pass off to decompositions for more than one control.
+ if (controls is { Count: 0 })
+ {
+ (this as IIntrinsicX).Body(target);
+ }
+ else if (controls is { Count: 1 })
+ {
+ NativeInterface.CNOT(this.Id, controls[0], target);
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ void IIntrinsicY.Body(Qubit target)
+ {
+ NativeInterface.Y(this.Id, target);
+ }
+
+ void IIntrinsicY.ControlledBody(IQArray controls, Qubit target)
+ {
+ throw new NotImplementedException();
+ }
+
+ void IIntrinsicZ.Body(Qubit target)
+ {
+ NativeInterface.Z(this.Id, target);
+ }
+
+ void IIntrinsicZ.ControlledBody(IQArray controls, Qubit target)
+ {
+ // TODO: pass off to decompositions for more than one control.
+ if (controls is { Count: 0 })
+ {
+ (this as IIntrinsicZ).Body(target);
+ }
+ else if (controls is { Count: 1 })
+ {
+ Get().__Body__((controls[0], target));
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public void Dispose()
+ {
+ NativeInterface.Destroy(this.Id);
+ }
+
+ public class OpenSystemsDumpMachine : Quantum.Diagnostics.DumpMachine
+ {
+ private OpenSystemsSimulator Simulator { get; }
+
+ public OpenSystemsDumpMachine(OpenSystemsSimulator m) : base(m)
+ {
+ this.Simulator = m;
+ }
+
+ public override Func __Body__ => (location) =>
+ {
+ if (location == null) { throw new ArgumentNullException(nameof(location)); }
+ Simulator.MaybeDisplayDiagnostic(Simulator.CurrentState);
+ return QVoid.Instance;
+ };
+ }
+
+ // TODO: implement this by adding a new PartialTrace trait to the
+ // Rust side, and then exposing it through the C API.
+ // Until we have that, there's not a sensible way to interpret
+ // states on subregisters in general.
+ public class OpenSystemsDumpRegister : Quantum.Diagnostics.DumpRegister
+ {
+ private OpenSystemsSimulator Simulator { get; }
+
+ public OpenSystemsDumpRegister(OpenSystemsSimulator m) : base(m)
+ {
+ this.Simulator = m;
+ }
+
+ public override Func<(T, IQArray), QVoid> __Body__ => (args) =>
+ {
+ var (location, register) = args;
+ if (location == null) { throw new ArgumentNullException(nameof(location)); }
+ this.Simulator.Get().__Body__?.Invoke("DumpRegister not yet supported on OpenSystemsSimulator; skipping.");
+ return QVoid.Instance;
+ };
+ }
+ }
+}
diff --git a/src/Simulation/Simulators/OpenSystemsSimulator/README.md b/src/Simulation/Simulators/OpenSystemsSimulator/README.md
new file mode 100644
index 00000000000..59d2d1a1f4e
--- /dev/null
+++ b/src/Simulation/Simulators/OpenSystemsSimulator/README.md
@@ -0,0 +1,65 @@
+# .NET Bindings for Quantum Development Kit Experimental Simulators
+
+The .NET classes defined in this folder utilize the C library built from the `qdk_sim` Rust crate to provide bindings to the experimental simulators that can be used from Q# programs.
+
+
+For more details on the `qdk_sim` crate, and its APIs for Rust, C, and Python, please see the documentation provided with that crate. From the root of this repository:
+
+```bash
+pwsh ./bootstrap.ps1
+cd src/Simulation/qdk_sim_rs
+cargo +nightly doc --features python --open
+```
+
+For more details on using these simulators from Q# programs called from Python hosts, please see .
+
+## Native interface
+
+The core interoperability boundary between the C# runtime for the Quantum Development Kit and the `qdk_sim` native library is defined in the [`NativeInterface` static class](./NativeInterface.cs). This class provides P/Invoke declarations for each function exposed by the C API for the `qdk_sim` crate, as well as methods that call into these C API methods. The managed methods that correspond to each C API function check error codes reuturned by C API functions and convert them into .NET exceptions, allowing for C API errors to be easily caught by managed code.
+
+For example, to use the native API to create a new simulator with a mixed-state representation:
+
+```csharp
+using static Microsoft.Quantum.Experimental.NativeInterface;
+
+var simId = Init(initialCapacity: 3, representation: "mixed");
+try
+{
+ H(simId, 0);
+ var result = M(simId, 0);
+ System.Console.Out.WriteLine($"Got {result}!");
+}
+finally
+{
+ Destroy(simId);
+}
+```
+
+## Q# simulator interface
+
+For most applications, the native interface is not useful on its own, but as called as a Q# simulator. The [`OpenSystemsSimulator` class](./OpenSystemsSimulator.cs) provides bindings between the native interface described above and the `SimulatorBase` class in the C# runtime for Q#.
+
+This class implements each of the intrinsic Q# operations either directly as a call into the `qdk_sim` library, or by using [decompositions](./Decompositions) to reduce Q# intrinsics into those intrinsics supported by the experimental simulators.
+
+To create a new simulator, use the constructor for the `OpenSystemsSimulator` class:
+
+```csharp
+var qsim = new OpenSystemsSimulator(3, "mixed");
+```
+
+The noise model for the simulator can be accessed or set using the `OpenSystemsSimulator.NoiseModel` property (see [noise modeling API](#noise-modeling-api), below)):
+
+```csharp
+qsim.NoiseModel =
+ NoiseModel.TryGetByName("ideal_stabilizer", out var noiseModel)
+ ? noiseModel
+ : throw new Exception("Noise model with name 'ideal_stabilizer' not found.");
+```
+
+This simulator can then be used as any other Q# simulator.
+
+## Noise modeling API
+
+To get and set noise models, each of the data structures in the `qdk_sim` crate has an analogous C# class that can be used to serialize and deserialize `qdk_sim` data structures as JSON objects.
+
+In particular, the [`NoiseModel` class](./DataModel/NoiseModel.cs) class parallels the `NoiseModel` struct in the `qdk_sim` crate, and allows accessing or modifying the noise model used by experimental simulators to run each Q# program.
diff --git a/src/Simulation/Simulators/QuantumSimulator/SimulatorBase.cs b/src/Simulation/Simulators/QuantumSimulator/SimulatorBase.cs
index 2797a2b13ab..8bb52e118e4 100644
--- a/src/Simulation/Simulators/QuantumSimulator/SimulatorBase.cs
+++ b/src/Simulation/Simulators/QuantumSimulator/SimulatorBase.cs
@@ -248,15 +248,15 @@ public void CheckQubit(Qubit q, string qubitName)
{
if (q == null) throw new ArgumentNullException(qubitName, "Trying to perform an intrinsic operation on a null Qubit");
- if (!QubitManager.IsValid(q))
+ if (!(QubitManager?.IsValid(q) ?? true))
{
throw new ArgumentException($"Cannot use qubit {q.Id}. Qubit is invalid.", qubitName);
}
- if (QubitManager.IsFree(q))
+ if (QubitManager?.IsFree(q) ?? false)
{
throw new ArgumentException($"Cannot use qubit {q.Id}. Qubit has already been released.", qubitName);
}
- if (QubitManager.IsDisabled(q))
+ if (QubitManager?.IsDisabled(q) ?? false)
{
throw new ArgumentException($"Cannot use qubit {q.Id}. Qubit is disabled.", qubitName);
}
@@ -291,6 +291,10 @@ public class Allocate : Intrinsic.Allocate
public Allocate(SimulatorBase m) : base(m)
{
this.sim = m;
+ if (m.QubitManager == null)
+ {
+ throw new NullReferenceException($"SimulatorBase {m} has a null-valued qubit manager, but a qubit manager is required to implement the Allocate operation.");
+ }
this.manager = m.QubitManager;
}
@@ -323,6 +327,10 @@ public class Release : Intrinsic.Release
public Release(SimulatorBase m) : base(m)
{
this.sim = m;
+ if (m.QubitManager == null)
+ {
+ throw new NullReferenceException($"SimulatorBase {m} has a null-valued qubit manager, but a qubit manager is required to implement the Release operation.");
+ }
this.manager = m.QubitManager;
}
@@ -353,6 +361,10 @@ public class Borrow : Intrinsic.Borrow
public Borrow(SimulatorBase m) : base(m)
{
this.sim = m;
+ if (m.QubitManager == null)
+ {
+ throw new NullReferenceException($"SimulatorBase {m} has a null-valued qubit manager, but a qubit manager is required to implement the Borrow operation.");
+ }
this.manager = m.QubitManager;
}
@@ -385,6 +397,10 @@ public class Return : Intrinsic.Return
public Return(SimulatorBase m) : base(m)
{
this.sim = m;
+ if (m.QubitManager == null)
+ {
+ throw new NullReferenceException($"SimulatorBase {m} has a null-valued qubit manager, but a qubit manager is required to implement the Return operation.");
+ }
this.manager = m.QubitManager;
}
@@ -414,6 +430,10 @@ public class GetQubitsAvailableToUse : Environment.GetQubitsAvailableToUse
public GetQubitsAvailableToUse(SimulatorBase m) : base(m)
{
this.sim = m;
+ if (m.QubitManager == null)
+ {
+ throw new NullReferenceException($"SimulatorBase {m} has a null-valued qubit manager, but a qubit manager is required to implement the GetQubitsAvailableToUse operation.");
+ }
this.manager = m.QubitManager;
}
@@ -432,6 +452,11 @@ public class GetQubitsAvailableToBorrow : Environment.GetQubitsAvailableToBorrow
public GetQubitsAvailableToBorrow(SimulatorBase m) : base(m)
{
this.sim = m;
+
+ if (m.QubitManager == null)
+ {
+ throw new NullReferenceException($"SimulatorBase {m} has a null-valued qubit manager, but a qubit manager is required to implement the GetQubitsAvailableToBorrow operation.");
+ }
this.manager = m.QubitManager;
}
diff --git a/src/Simulation/qdk_sim_rs/.config/dotnet-tools.json b/src/Simulation/qdk_sim_rs/.config/dotnet-tools.json
new file mode 100644
index 00000000000..8e180ea3016
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "dotnet-script": {
+ "version": "1.1.0",
+ "commands": [
+ "dotnet-script"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Simulation/qdk_sim_rs/.gitignore b/src/Simulation/qdk_sim_rs/.gitignore
new file mode 100644
index 00000000000..a740f3ce698
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/.gitignore
@@ -0,0 +1,22 @@
+linux
+osx
+win10
+target
+drop
+
+# We inject version numbers into Cargo.toml, so don't want them stored in repo.
+Cargo.toml
+# In the future, it would be good to enable reproducible builds by committing
+# the lockfile and using --locked in calls to cargo.
+Cargo.lock
+
+# Ignore Python temporaries and build artifacts.
+*.pyd
+*.whl
+*.egg-info
+
+# Ignore generated C/C++ headers.
+include/
+
+# Don't ignore documentation here.
+!docs
diff --git a/src/Simulation/qdk_sim_rs/CONTRIBUTING.md b/src/Simulation/qdk_sim_rs/CONTRIBUTING.md
new file mode 100644
index 00000000000..894a188cb9b
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/CONTRIBUTING.md
@@ -0,0 +1,30 @@
+# Contributing to the QDK Experimental Simulators
+
+## Build prerequisites
+
+The build for the experimental simulators requires the nightly Rust toolchain to be installed, along with support for clippy and rustfmt. These prerequisites can be installed by using the `prerequisites.ps1` script in this folder:
+
+```pwsh
+PS> ./prerequistes.ps1
+```
+
+## Code quality checks
+
+The build for this crate enforces the following mandatory code quality checks:
+
+- `rustfmt`: Check code style and formatting rules.
+- `clippy`: Check for common programming errors and linter violations.
+- `#[deny(missing_docs)]`: Require API documentation for all public crates, modules, traits, functions, and types.
+
+## Testing strategy
+
+Tests for the open systems simulator consist of five distinct parts:
+
+- Rust-language unit tests for the Rust library.
+ These tests are defined in `#[cfg(test)]` submodules of each module in `./src/`.
+- Rust-language integration tests for the Rust library.
+ These tests are defined in modules under the `./tests/` folder.
+- Q#-language unit tests in the C#-based simulation runtime.
+ These tests ensure that the binding of the Rust library works as expected when included into the C#-based runtime, and are defined in operations marked with `@Test("Microsoft.Quantum.Experimental.OpenSystemsSimulator")` under the `qsharp-runtime/src/Simulation/Simulators.Tests/QuantumTestSuite` folder.
+- C#-language unit tests in the IQ# kernel.
+ These tests ensure that noise models and noisy simulation can be correctly exposed to Python and Q# notebook users; please see the `microsoft/iqsharp` repo for more details.
diff --git a/src/Simulation/qdk_sim_rs/Cargo.toml.template b/src/Simulation/qdk_sim_rs/Cargo.toml.template
new file mode 100644
index 00000000000..64e8483c780
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/Cargo.toml.template
@@ -0,0 +1,88 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+[package]
+name = "qdk_sim_experimental"
+version = "0.1.0"
+authors = ["Christopher Granade "]
+edition = "2018"
+license = "MIT"
+description = "Experimental simulators for use with the Quantum Development Kit."
+homepage = "https://github.com/microsoft/qsharp-runtime"
+repository = "https://github.com/microsoft/qsharp-runtime"
+
+# Exclude files specific to QDK build pipelines.
+exclude = [
+ "*.template", "*.csx", "*.ps1", "NuGet.Config", "drops"
+]
+
+[lib]
+name = "qdk_sim"
+path = "src/lib.rs"
+crate-type = ["rlib", "staticlib", "cdylib"]
+
+# Optional build-time features: we use this to create Python and WASM bindings.
+[features]
+default = []
+wasm = ["web-sys"]
+# When Python bindings are enabled, we also need the pyo3 dependency.
+python = ["pyo3", "numpy"]
+
+# Enable LaTeX on docs.rs.
+# See https://stackoverflow.com/a/54573800/267841 and
+# https://github.com/rust-num/num/pull/226/files for why this works.
+[package.metadata.docs.rs]
+rustdoc-args = [ "--html-in-header", "docs-includes/header.html", "--html-after-content", "docs-includes/after.html" ]
+
+
+[profile.release]
+opt-level = 3
+codegen-units = 1 # Reduce number of codegen units to increase optimizations.
+panic = 'unwind'
+
+[dependencies]
+ndarray = { version = "0.15.2", features = ["serde"] }
+num-complex = { version = "0.4", features = ["serde"] }
+num-traits = "0.2"
+derive_more = "0.99.10"
+itertools = "0.9.0"
+rand = { version = "0.7.3", features = ["alloc"] }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+lazy_static = "1.4.0"
+cfg-if = "1.0.0"
+num_enum = "0.5.1"
+# See https://github.com/rust-random/rand/issues/990
+# and https://docs.rs/getrandom/0.1.15/getrandom/index.html#support-for-webassembly-and-asmjs
+# for why this is needed.
+# NB: We depend on 0.1.15, since that's what gets brought in transitively
+# by rand and rand_core.
+getrandom = { version = "0.1.15", features = ["wasm-bindgen"] }
+
+# We only need web-sys when compiling with the wasm feature.
+web-sys = { version = "0.3.4", features = ['console'], optional = true }
+
+# We only need PyO3 when generating Python bindings.
+pyo3 = { version = "0.13.2", features = ["extension-module"], optional = true }
+numpy = {version = "0.13.1", optional = true}
+
+# We use built to expose compile-time metadata about how this crate
+# was built to C and Rust callers.
+built = "0.5.0"
+
+[build-dependencies]
+built = "0.5.0"
+cbindgen = "0.19.0"
+
+
+[dev-dependencies]
+assert-json-diff = "2.0.1"
+criterion = { version = "0.3", features = ['html_reports', 'csv_output'] }
+
+[[bench]]
+name = "c_api_benchmark"
+harness = false
+
+[[bench]]
+name = "microbenchmark"
+harness = false
diff --git a/src/Simulation/qdk_sim_rs/NuGet.Config b/src/Simulation/qdk_sim_rs/NuGet.Config
new file mode 100644
index 00000000000..8b638f09a31
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/NuGet.Config
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/src/Simulation/qdk_sim_rs/README.md b/src/Simulation/qdk_sim_rs/README.md
new file mode 100644
index 00000000000..f48c36c0bd5
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/README.md
@@ -0,0 +1,150 @@
+
+
+# Quantum Development Kit Experimental Simulators
+
+> ## **⚠** WARNING **⚠**
+>
+> This crate is **experimental**, and may undergo breaking API changes with no notice, and may not be maintained in future versions of the Quantum Development Kit.
+>
+> As an experimental feature of the Quantum Development Kit, this crate may be buggy or incomplete. Please check the tracking issue at [microsoft/qsharp-runtime#714](https://github.com/microsoft/qsharp-runtime/issues/714) for more information.
+
+> ## **ⓘ** TIP
+>
+> This crate provides low-level APIs for interacting with experimental simulators. If you're interested in using the experimental simulators to run your Q# programs, please see the installation instructions at .
+
+This **experimental** crate implements simulation functionality for the Quantum Development Kit, including:
+
+- Open systems simulation
+- Stabilizer simulation
+
+The [`c_api`] module allows for using the simulation functionality in this crate from C, or from other languages with a C FFI (e.g.: C++ or C#), while Rust callers can take advantage of the structs and methods in this crate directly.
+
+Similarly, the [`python`] module allows exposing data structures in this crate to Python programs.
+
+## Cargo Features
+
+- **`python`**: Enables Python bindings for this crate.
+- **`wasm`**: Ensures that the crate is compatible with usage from WebAssembly.
+
+## Representing quantum systems
+
+This crate provides several different data structures for representing quantum systems in a variety of different conventions:
+
+- [`State`]\: Represents stabilizer, pure, or mixed states of a register of qubits.
+- [`Process`]\: Represents processes that map states to states.
+- [`Instrument`]\: Represents quantum instruments, the most general form of measurement.
+
+## Noise model serialization
+
+Noise models can be serialized to JSON for interoperability across languages. In particular, each noise model is represented by a JSON object with properties for each operation, for the initial state, and for the instrument used to implement $Z$-basis measurement.
+
+For example:
+
+```json
+{
+ "initial_state": {
+ "n_qubits": 1,
+ "data": {
+ "Mixed": {
+ "v": 1, "dim":[2 ,2],
+ "data": [[1.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]]
+ }
+ }
+ },
+ "i": {
+ "n_qubits": 1,
+ "data": {
+ "Unitary": {
+ "v": 1,"dim": [2, 2],
+ "data": [[1.0, 0.0], [0.0, 0.0], [0.0, 0.0], [1.0, 0.0]]
+ }
+ }
+ },
+ ...
+ "z_meas": {
+ "Effects": [
+ {
+ "n_qubits": 1,
+ "data": {
+ "KrausDecomposition": {
+ "v":1, "dim": [1, 2, 2],
+ "data": [[1.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]]
+ }
+ }
+ },
+ {
+ "n_qubits": 1,
+ "data": {
+ "KrausDecomposition": {
+ "v": 1,"dim": [1, 2, 2],
+ "data":[[0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [1.0, 0.0]]
+ }
+ }
+ }
+ ]
+ }
+}
+```
+
+The value of the `initial_state` property is a serialized [`State`], the value of each operation property (i.e.: `i`, `x`, `y`, `z`, `h`, `s`, `s_adj`, `t`, `t_adj`, and `cnot`) is a serialized [`Process`], and the value of `z_meas` is a serialized [`Instrument`].
+
+### Representing arrays of complex numbers
+
+Throughout noise model serialization, JSON objects representing $n$-dimensional arrays of complex numbers are used to store various vectors, matrices, and tensors. Such arrays are serialized as JSON objects with three properties:
+
+- `v`: The version number of the JSON schema; must be `"1"`.
+- `dims`: A list of the dimensions of the array being represented.
+- `data`: A list of the elements of the flattened array, each of which is represented as a list with two entries representing the real and complex parts of each element.
+
+For example, consider the serialization of the ideal `y` operation:
+
+```json
+"y": {
+ "n_qubits": 1,
+ "data": {
+ "Unitary": {
+ "v": 1, "dim": [2, 2],
+ "data": [[0.0, 0.0], [0.0, 1.0], [0.0, -1.0], [0.0, 0.0]]
+ }
+ }
+}
+```
+
+### Representing states and processes
+
+Each state and process is represented in JSON by an object with two properties, `n_qubits` and `data`. The value of `data` is itself a JSON object with one property indicating which variant of the [`StateData`] or [`ProcessData`] enum is used to represent that state or process, respectively.
+
+For example, the following JSON object represents the mixed state $\ket{0}\bra{0}$:
+
+```json
+{
+ "n_qubits": 1,
+ "data": {
+ "Mixed": {
+ "v": 1, "dim":[2 ,2],
+ "data": [[1.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]]
+ }
+ }
+}
+```
+
+### Representing instruments
+
+TODO
+
+## Known issues
+
+- Performance of open systems simulation still needs additional work for larger registers.
+- Some gaps in different conversion functions and methods.
+- Stabilizer states cannot yet be measured through [`Instrument`] struct, only through underlying [`Tableau`].
+- Many parts of the crate do not yet have Python bindings.
+- Stabilizer simulation not yet exposed via C API.
+- Test and microbenchmark coverage still incomplete.
+- Too many APIs `panic!` or `unwrap`, and need replaced with `Result` returns instead.
diff --git a/src/Simulation/qdk_sim_rs/benches/c_api_benchmark.rs b/src/Simulation/qdk_sim_rs/benches/c_api_benchmark.rs
new file mode 100644
index 00000000000..d4ab3fbdc19
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/benches/c_api_benchmark.rs
@@ -0,0 +1,76 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+//! This set of benchmarks exercises the open systems simulator exclusively via
+//! its C API, so as to gauge any potential issues for C-based consumers of the
+//! simulator.
+
+use criterion::{criterion_group, criterion_main, Criterion};
+use qdk_sim::c_api;
+use std::ffi::CString;
+
+// Use include_str! to store test case JSON as a string into the compiled
+// test executable.
+static BENCHMARK_NOISE_MODEL_JSON: &str = include_str!("data/benchmark-noise-model.json");
+
+fn with_test_suite(
+ sim_id: usize,
+ group: &mut criterion::BenchmarkGroup,
+) {
+ group.bench_function("apply x", |b| {
+ b.iter(|| {
+ c_api::x(sim_id, 0);
+ })
+ });
+ group.bench_function("apply z", |b| {
+ b.iter(|| {
+ c_api::z(sim_id, 0);
+ })
+ });
+ group.bench_function("apply cnot", |b| {
+ b.iter(|| {
+ c_api::cnot(sim_id, 0, 1);
+ })
+ });
+ group.bench_function("measure", |b| {
+ b.iter(|| {
+ let mut result: usize = 0;
+ // NB: The C API is not in general safe.
+ unsafe {
+ c_api::m(sim_id, 0, &mut result);
+ }
+ })
+ });
+}
+
+fn ideal(c: &mut Criterion) {
+ let mut sim_id: usize = 0;
+ unsafe {
+ let _err = c_api::init(3, CString::new("mixed").unwrap().as_ptr(), &mut sim_id);
+ }
+ let mut group = c.benchmark_group("ideal");
+ with_test_suite(sim_id, &mut group);
+ group.finish();
+ c_api::destroy(sim_id);
+}
+
+fn noisy(c: &mut Criterion) {
+ let mut sim_id: usize = 0;
+ unsafe {
+ let _err = c_api::init(3, CString::new("mixed").unwrap().as_ptr(), &mut sim_id);
+ }
+ // NB: The C API is not in general safe.
+ unsafe {
+ c_api::set_noise_model(
+ sim_id,
+ CString::new(BENCHMARK_NOISE_MODEL_JSON).unwrap().as_ptr(),
+ );
+ }
+ let mut group = c.benchmark_group("noisy");
+ with_test_suite(sim_id, &mut group);
+ group.finish();
+ c_api::destroy(sim_id);
+}
+
+criterion_group!(benches, ideal, noisy);
+criterion_main!(benches);
diff --git a/src/Simulation/qdk_sim_rs/benches/data/benchmark-noise-model.json b/src/Simulation/qdk_sim_rs/benches/data/benchmark-noise-model.json
new file mode 100644
index 00000000000..e9d7f107f7c
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/benches/data/benchmark-noise-model.json
@@ -0,0 +1 @@
+{"initial_state":{"n_qubits":1,"data":{"Mixed":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[0,0]]}}},"cnot":{"n_qubits":2,"data":{"Unitary":{"v":1,"dim":[4,4],"data":[[1,0],[0,0],[0,0],[0,0],[0,0],[1,0],[0,0],[0,0],[0,0],[0,0],[0,0],[1,0],[0,0],[0,0],[1,0],[0,0]]}}},"i":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[1,0]]}}},"s":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[0,1]]}}},"s_adj":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[0,-1]]}}},"t":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[0.7071067811865476,0.7071067811865476]]}}},"t_adj":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1,0],[0,0],[0,0],[0.7071067811865476,-0.7071067811865476]]}}},"h":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0.7071067811865476,0],[0.7071067811865476,0],[0.7071067811865476,0],[-0.7071067811865476,0]]}}},"x":{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[4,2,2],"data":[[0,0],[0.961769203083567,0],[0.9617692030835675,0],[0,0],[0,0],[0.15811388300841897,0],[-0.1581138830084189,0],[0,0],[0.22360679774997896,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0.22360679774997896,0]]}}},"y":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0,0],[0,1],[0,-1],[0,0]]}}},"z":{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[2,2,2],"data":[[0.9746794344808962,0],[0,0],[0,0],[-0.9746794344808962,0],[0.2236067977499789,0],[0,0],[0,0],[0.22360679774997896,0]]}}},"z_meas":{"effects":[{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[1,2,2],"data":[[0.975,0],[0,0],[0,0],[0.025000000000000022,0]]}}},{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[1,2,2],"data":[[0.025000000000000022,0],[0,0],[0,0],[0.9749999999999999,0]]}}}]}}
\ No newline at end of file
diff --git a/src/Simulation/qdk_sim_rs/benches/data/noise-model-export.ipynb b/src/Simulation/qdk_sim_rs/benches/data/noise-model-export.ipynb
new file mode 100644
index 00000000000..804cd28ccd4
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/benches/data/noise-model-export.ipynb
@@ -0,0 +1,212 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "logical-diving",
+ "metadata": {},
+ "source": [
+ "This notebook is used to define a noise model for benchmarking purposes."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "understanding-business",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import qutip as qt"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "fallen-office",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Preparing Q# environment...\n"
+ ]
+ }
+ ],
+ "source": [
+ "import qsharp\n",
+ "from qsharp.experimental import enable_noisy_simulation, get_noise_model, set_noise_model\n",
+ "enable_noisy_simulation()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "pursuant-plenty",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "I, X, Y, Z = qt.qeye(2), qt.sigmax(), qt.sigmay(), qt.sigmaz()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "labeled-strike",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "sI, sX, sY, sZ = map(qt.to_super, [I, X, Y, Z])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "answering-europe",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def total_dephasing_channel():\n",
+ " return (1 / 2) * sI + (1 / 2) * sZ"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "attached-juice",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def dephasing_channel(p):\n",
+ " return (1 - p) * sI + p * total_dephasing_channel()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "serious-warner",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/latex": [
+ "Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True\\begin{equation*}\\left(\\begin{array}{*{11}c}1.0 & 0.0 & 0.0 & 0.0\\\\0.0 & 0.900 & 0.0 & 0.0\\\\0.0 & 0.0 & 0.900 & 0.0\\\\0.0 & 0.0 & 0.0 & 1.0\\\\\\end{array}\\right)\\end{equation*}"
+ ],
+ "text/plain": [
+ "Quantum object: dims = [[[2], [2]], [[2], [2]]], shape = (4, 4), type = super, isherm = True\n",
+ "Qobj data =\n",
+ "[[1. 0. 0. 0. ]\n",
+ " [0. 0.9 0. 0. ]\n",
+ " [0. 0. 0.9 0. ]\n",
+ " [0. 0. 0. 1. ]]"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "dephasing_channel(0.1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "requested-instruction",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def total_depolarizing_channel():\n",
+ " return (sI + sX + sY + sZ) / 4"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "exact-argument",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def depolarizing_channel(p):\n",
+ " return (1 - p) * sI + p * total_depolarizing_channel()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "academic-focus",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def finite_visibility_meas(visibility):\n",
+ " return [\n",
+ " qt.to_super(visibility * (I + sign * Z) / 2 + (1 - visibility) * I / 2)\n",
+ " for sign in (+1, -1)\n",
+ " ]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "inclusive-active",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "noise_model = get_noise_model()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "instructional-mortality",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "noise_model['x'] = depolarizing_channel(0.1) * sX\n",
+ "noise_model['z'] = dephasing_channel(0.1) * sZ\n",
+ "noise_model['z_meas']['effects'] = finite_visibility_meas(0.95)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "voluntary-parallel",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "set_noise_model(noise_model)\n",
+ "qsharp.client._execute('%experimental.noise_model --save benchmark-noise-model.json')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "functional-concentrate",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/src/Simulation/qdk_sim_rs/benches/microbenchmark.rs b/src/Simulation/qdk_sim_rs/benches/microbenchmark.rs
new file mode 100644
index 00000000000..89ccd07b809
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/benches/microbenchmark.rs
@@ -0,0 +1,86 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+//! The benchmarks in this module exercise the internals of the simulator at
+//! a low level, and thus are not indicative of user-facing performance.
+//! Rather, these microbenchmarks are intended to help diagnose what the root
+//! cause may be when user-facing performance is degraded.
+//! In particular, optimizing these benchmarks may not translate into improved
+//! performance in user code.
+
+use criterion::{criterion_group, criterion_main, Criterion};
+use qdk_sim::{
+ common_matrices,
+ common_matrices::nq_eye,
+ linalg::{extend_one_to_n, extend_two_to_n, Tensor},
+};
+
+fn linalg(c: &mut Criterion) {
+ let mut group = c.benchmark_group("linalg");
+ for n_qubits in [1usize, 2, 3, 4].iter() {
+ group.bench_with_input(format!("nq_eye({})", n_qubits), n_qubits, |b, nq| {
+ b.iter(|| {
+ let _eye = nq_eye(*nq);
+ })
+ });
+ }
+ for idx_qubit in [0usize, 1, 2].iter() {
+ group.bench_with_input(
+ format!(
+ "extend_one_to_n(n_left: {}, n_right: {})",
+ idx_qubit,
+ 2 - idx_qubit
+ ),
+ idx_qubit,
+ |b, i| {
+ // Create some test data.
+ let data = nq_eye(1);
+ b.iter(|| {
+ let _extended = extend_one_to_n(data.view(), *i, 3);
+ })
+ },
+ );
+ }
+ for idx_qubit in [0usize, 1, 2].iter() {
+ group.bench_with_input(
+ format!(
+ "extend_two_to_n(n_left: {}, n_right: {})",
+ idx_qubit,
+ 2 - idx_qubit
+ ),
+ idx_qubit,
+ |b, i| {
+ // Create some test data.
+ let data = common_matrices::cnot();
+ b.iter(|| {
+ let _extended = extend_two_to_n(data.view(), *i, 3, 4);
+ })
+ },
+ );
+ }
+ group.bench_function("tensor 2x2 with 2x2", |b| {
+ let x = common_matrices::x();
+ let y = common_matrices::y();
+ b.iter(|| {
+ let _result = x.tensor(&y);
+ })
+ });
+ group.bench_function("tensor 2x2 with 4x4", |b| {
+ let x = common_matrices::x();
+ let cnot = common_matrices::cnot();
+ b.iter(|| {
+ let _result = x.tensor(&cnot);
+ })
+ });
+ group.bench_function("tensor 4x4 with 2x2", |b| {
+ let x = common_matrices::x();
+ let cnot = common_matrices::cnot();
+ b.iter(|| {
+ let _result = cnot.tensor(&x);
+ })
+ });
+ group.finish();
+}
+
+criterion_group!(benches, linalg);
+criterion_main!(benches);
diff --git a/src/Simulation/qdk_sim_rs/build-qdk-sim-rs.ps1 b/src/Simulation/qdk_sim_rs/build-qdk-sim-rs.ps1
new file mode 100644
index 00000000000..4ab192620d6
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/build-qdk-sim-rs.ps1
@@ -0,0 +1,41 @@
+& (Join-Path $PSScriptRoot ".." ".." ".." "build" "set-env.ps1");
+$IsCI = "$Env:TF_BUILD" -ne "" -or "$Env:CI" -eq "true";
+
+Push-Location $PSScriptRoot
+ # Start with the quick check first and make sure that Rust sources
+ # meet formatting and style guide rules.
+ cargo fmt -- --check
+ $script:allOk = $script:allOk -and $LASTEXITCODE -eq 0;
+
+ # Check linting rules defined by clippy, a linting tool provided with the
+ # Rust toolchain. Please see https://github.com/rust-lang/rust-clippy
+ # and https://rust-lang.github.io/rust-clippy/master/index.html
+ # for more information.
+ # If there's a false positive, that check should be explicitly disabled
+ # at the point where the false positive occurs with an explanation as to
+ # why it's OK.
+ cargo clippy -- -D warnings
+ $script:allOk = $script:allOk -and $LASTEXITCODE -eq 0;
+
+ $releaseFlag = "$Env:BUILD_CONFIGURATION" -eq "Release" ? @("--release") : @();
+
+ # Enable control flow guard (see https://github.com/microsoft/qsharp-runtime/pull/647)
+ # for interoperating Rust and C.
+ # NB: CFG is only supported on Windows, but the Rust flag is supported on
+ # all platforms; it's ignored on platforms without CFG functionality.
+ $Env:RUSTFLAGS = "-C control-flow-guard";
+
+ # Actually run the build.
+ cargo +nightly build -Z unstable-options @releaseFlag --out-dir "drop";
+
+ # Make sure docs are complete.
+ $Env:RUSTDOCFLAGS = "--html-in-header $(Resolve-Path docs-includes/header.html) " + `
+ "--html-after-content $(Resolve-Path docs-includes/after.html)"
+ cargo +nightly doc;
+
+ # When building in CI, free disk space by cleaning up.
+ # Note that this takes longer, but saves ~1 GB of space.
+ if ($IsCI) {
+ cargo clean;
+ }
+Pop-Location
diff --git a/src/Simulation/qdk_sim_rs/build.rs b/src/Simulation/qdk_sim_rs/build.rs
new file mode 100644
index 00000000000..aae76e31ea3
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/build.rs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+extern crate cbindgen;
+
+use std::env;
+
+fn main() -> Result<(), String> {
+ built::write_built_file().expect("Failed to acquire build-time information");
+
+ let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
+
+ cbindgen::generate_with_config(
+ &crate_dir,
+ cbindgen::Config {
+ language: cbindgen::Language::C,
+ ..Default::default()
+ },
+ )
+ .map_err(|e| e.to_string())?
+ .write_to_file("include/qdk_sim.h");
+
+ cbindgen::generate_with_config(
+ &crate_dir,
+ cbindgen::Config {
+ ..Default::default()
+ },
+ )
+ .map_err(|e| e.to_string())?
+ .write_to_file("include/qdk_sim.hpp");
+
+ Ok(())
+}
diff --git a/src/Simulation/qdk_sim_rs/cbindgen.toml b/src/Simulation/qdk_sim_rs/cbindgen.toml
new file mode 100644
index 00000000000..08094f28fc2
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/cbindgen.toml
@@ -0,0 +1 @@
+language = "C"
diff --git a/src/Simulation/qdk_sim_rs/docs-includes/after.html b/src/Simulation/qdk_sim_rs/docs-includes/after.html
new file mode 100644
index 00000000000..d855ca7e178
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/docs-includes/after.html
@@ -0,0 +1,12 @@
+
diff --git a/src/Simulation/qdk_sim_rs/docs-includes/header.html b/src/Simulation/qdk_sim_rs/docs-includes/header.html
new file mode 100644
index 00000000000..affe693fa82
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/docs-includes/header.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/Simulation/qdk_sim_rs/docs/c-api.md b/src/Simulation/qdk_sim_rs/docs/c-api.md
new file mode 100644
index 00000000000..11d152bafe3
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/docs/c-api.md
@@ -0,0 +1,145 @@
+# Using Experimental Simulators from C
+
+This module exposes a C API for this crate, useful for embedding into simulation
+runtimes.
+
+## Safety
+
+As this is a foreign-function interface, many of the functions exposed here
+are **unsafe**, representing that the caller is required to ensure that safety
+conditions in the host language are upheld.
+
+Please pay attention to any listed safety notes when calling into this C
+API.
+
+## Generating and Using C API Headers
+
+The [`qdk_sim`](..) crate has enabled the use of [`cbindgen`](https://crates.io/crates/cbindgen), such that C-language header files are generated automatically as part of the build for this crate.
+
+```bash
+cargo build
+```
+
+This will generate `include/qdk_sim.h`, which can then be used from C callers:
+
+```c
+#include
+#include "qdk_sim.h"
+
+int main() {
+ uintptr_t sim_id;
+ uintptr_t result0, result1;
+
+ init(2, "mixed", &sim_id);
+ h(sim_id, 0);
+ h(sim_id, 1);
+
+ m(sim_id, 0, &result0);
+ m(sim_id, 1, &result1);
+
+ printf("got %llu %llu", result0, result1);
+ destroy(sim_id);
+}
+```
+
+To build and run the above example using Clang on Windows:
+
+```bash
+$ clang docs/example.c -Iinclude -Ltarget/debug -lqdk_sim -lws2_32 -lAdvapi32 -lUserenv
+got 1 1
+```
+
+## Error Handling and Return Values
+
+Most C API functions for this crate return an integer, with `0` indicating success and any other value indicating failure. In the case that a non-zero value is returned, API functions will also set the last error message, accessible by calling [`lasterr`]:
+
+```c
+#include
+#include "qdk_sim.h"
+
+int main() {
+ uintptr_t sim_id;
+ uintptr_t result0, result1;
+
+ if (init(2, "invalid", &sim_id) != 0) {
+ printf("Got an error message: %s", lasterr());
+ }
+
+ destroy(sim_id);
+}
+```
+
+C API functions that need to return data to the caller, such as [`m`], do so by accepting pointers to memory where results should be stored.
+
+> **⚠ WARNING**: It is the caller's responsibility to ensure that pointers used to hold results are valid (that is, point to memory that can be safely written into).
+
+For example:
+
+```c
+uintptr_t result;
+if (m(sim_id, 0, &result) != 0) {
+ printf("Got an error message: %s", lasterr());
+}
+printf("Got a measurement result: %llu", result);
+```
+
+## Initializing, Using, and Destroying Simulators
+
+To create a new simulator from C, use the [`init`] function. This function accepts a pointer to an unsigned integer that will be set to an ID for the new simulator:
+
+```c
+uintptr_t sim_id;
+// Initialize a new simulator with two qubits and using a mixed-state
+// representation.
+if (init(2, "mixed", &sim_id) != 0) {
+ printf("Error initializing simulator: %s", lasterr());
+}
+```
+
+The ID for the newly created simulator can then be used to call into functions that apply different quantum operations, such as [`x`], [`h`], or [`cnot`]:
+
+```c
+// Apply an 𝑋 operation to qubit #0.
+if (x(sim_id, 0) != 0) {
+ printf("Error applying X: %s", lasterr());
+}
+```
+
+To free the memory associated with a given simulator, use the [`destroy`] function:
+
+```c
+if (destroy(sim_id) != 0) {
+ printf("Error destroying simulator: %s", lasterr());
+}
+```
+
+## Getting and Setting Noise Models
+
+Noise models for each simulator can be accessed or set by using [`get_noise_model`], [`get_noise_model_by_name`], [`set_noise_model`], and [`set_noise_model_by_name`]. Each of these functions accepts either a name of a built-in noise model (see [`crate::NoiseModel::get_by_name`] for details).
+
+Noise models in the C API are represented by strings containing JSON serializations of the [`crate::NoiseModel`] data model. For example:
+
+```c
+#include
+#include "qdk_sim.h"
+
+int main() {
+ const char *noise_model;
+
+ if (get_noise_model_by_name("ideal", &noise_model) != 0) {
+ printf("Error getting noise model: %s", lasterr());
+ }
+
+ printf("Noise model:\n%s", noise_model);
+}
+
+```
+
+Running the above results in the JSON representation of the ideal noise model being written to the console:
+
+```text
+Noise model:
+{"initial_state":{"n_qubits":1,"data":{"Mixed":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0]]}}},"i":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0]]}}},"x":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0.0,0.0],[1.0,0.0],[1.0,0.0],[0.0,0.0]]}}},"y":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0.0,0.0],[0.0,1.0],[-0.0,-1.0],[0.0,0.0]]}}},"z":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[-1.0,-0.0]]}}},"h":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[0.7071067811865476,0.0],[0.7071067811865476,0.0],[0.7071067811865476,0.0],[-0.7071067811865476,-0.0]]}}},"s":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,1.0]]}}},"s_adj":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,-0.0],[0.0,-0.0],[0.0,-0.0],[0.0,-1.0]]}}},"t":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.7071067811865476,0.7071067811865476]]}}},"t_adj":{"n_qubits":1,"data":{"Unitary":{"v":1,"dim":[2,2],"data":[[1.0,-0.0],[0.0,-0.0],[0.0,-0.0],[0.7071067811865476,-0.7071067811865476]]}}},"cnot":{"n_qubits":2,"data":{"Unitary":{"v":1,"dim":[4,4],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0],[0.0,0.0]]}}},"z_meas":{"Effects":[{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[1,2,2],"data":[[1.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0]]}}},{"n_qubits":1,"data":{"KrausDecomposition":{"v":1,"dim":[1,2,2],"data":[[0.0,0.0],[0.0,0.0],[0.0,0.0],[1.0,0.0]]}}}]}}
+```
+
+See [noise model serialization](crate#noise-model-serialization) for more details.
diff --git a/src/Simulation/qdk_sim_rs/docs/python-api.md b/src/Simulation/qdk_sim_rs/docs/python-api.md
new file mode 100644
index 00000000000..1950730920c
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/docs/python-api.md
@@ -0,0 +1,45 @@
+# Using Experimental Simulators from Python
+
+This module exposes the various data structures from this crate as Python objects, useful for embedding in Python programs.
+
+Note that this module contains Python-specific functionality, and cannot be used directly from Rust.
+
+> **ⓘ NOTE**: The Python API for this crate allows direct and low-level access to simulation data structures. This is distinct from using Python to run Q# programs on the experimental simulators implemented by this library. For details on how to use Python and Q# together with experimental simulators, please see documentation on the repository.
+
+## Building the Python API
+
+This crate automatically builds a Python extension module when compiled using the `python` Cargo feature. This module can be built into an installable Python package by using the `pip` command:
+
+```bash
+# Install the qdk_sim crate as a Python package.
+pip install .
+
+# Build this crate as a redistributable Python wheel, targeting the current
+# host architecture.
+pip wheel .
+```
+
+In both cases, `pip` will automatically discover and use your Rust toolchain to call `cargo build` and include its output in the Python package built from this crate.
+
+## Importing and working with the Python API
+
+Once installed using the above steps, the Python interface into the experimental simulators can be accessed as `import qdk_sim`. Doing so gives access to Python representations of different data structures in this crate. For example:
+
+```python
+>>> import qdk_sim
+>>> noise_model = qdk_sim.NoiseModel.get_by_name("ideal_stabilizer")
+>>> noise_model
+
+```
+
+Many Python objects implemented by this crate offer `as_json` methods that can be used to convert experimental simulator data structures to built-in Python objects:
+
+```python
+>>> import json
+>>> json.loads(qdk_sim.NoiseModel.get_by_name("ideal_stabilizer").as_json())
+{'initial_state': {'n_qubits': 1, 'data': {'Stabilizer': {'n_qubits': 1, 'table': {'v': 1, 'dim': [2, 3], 'data': [True, False, False, False, True, False]}}}}, 'i': {'n_qubits': 1, 'data': {'Sequence': []}}, 'x': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'Hadamard': 0}, {'Phase': 0}, {'Phase': 0}, {'Hadamard': 0}]}}, 'y': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'AdjointPhase': 0}, {'Hadamard': 0}, {'Phase': 0}, {'Phase': 0}, {'Hadamard': 0}, {'Phase': 0}]}}, 'z': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'Phase': 0}, {'Phase': 0}]}}, 'h': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'Hadamard':
+0}]}}, 's': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'Phase': 0}]}}, 's_adj': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'AdjointPhase': 0}]}}, 't': {'n_qubits': 1, 'data': 'Unsupported'}, 't_adj': {'n_qubits': 1, 'data': 'Unsupported'}, 'cnot': {'n_qubits': 1, 'data': {'ChpDecomposition': [{'Cnot': [0, 1]}]}}, 'z_meas': {'ZMeasurement': {'pr_readout_error': 0.0}}}
+```
diff --git a/src/Simulation/qdk_sim_rs/inject-version.csx b/src/Simulation/qdk_sim_rs/inject-version.csx
new file mode 100644
index 00000000000..ec71338f066
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/inject-version.csx
@@ -0,0 +1,39 @@
+#r "nuget: System.CommandLine, 2.0.0-beta1.21216.1"
+#r "nuget: Tommy, 2.0.0"
+
+using System.CommandLine;
+using System.Linq;
+using System.CommandLine.Invocation;
+using Tommy;
+
+// Create a root command with some options
+var rootCommand = new RootCommand
+{
+ new Option(
+ "--template",
+ description: "A file to use as the template for cargo manifest."),
+ new Option(
+ "--out-path",
+ description: "Path to write the generated manifest to."),
+ new Option(
+ "--version",
+ description: "The version number to inject.")
+};
+
+// Note that the parameters of the handler method are matched according to the names of the options
+rootCommand.Handler = CommandHandler.Create((template, outPath, version) =>
+{
+ Console.Out.WriteLine($"Injecting version {version} into {template} and writing to {outPath}.");
+ using var reader = new StreamReader(File.OpenRead(template.FullName));
+ var table = TOML.Parse(reader);
+
+ // Set the version number in the table.
+ table["package"]["version"] = version;
+
+ using var writer = new StreamWriter(File.OpenWrite(outPath));
+ table.WriteTo(writer);
+ // Remember to flush the data if needed!
+ writer.Flush();
+});
+
+await rootCommand.InvokeAsync(Args.ToArray());
diff --git a/src/Simulation/qdk_sim_rs/prerequisites.ps1 b/src/Simulation/qdk_sim_rs/prerequisites.ps1
new file mode 100644
index 00000000000..494635b723e
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/prerequisites.ps1
@@ -0,0 +1,25 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+if (-not (Get-Command rustup -ErrorAction SilentlyContinue)) {
+ if ($IsWindows -or $PSVersionTable.PSEdition -eq "Desktop") {
+ Invoke-WebRequest "https://win.rustup.rs" -OutFile rustup-init.exe
+ Unblock-File rustup-init.exe;
+ ./rustup-init.exe -y
+ } elseif ($IsLinux -or $IsMacOS) {
+ Invoke-WebRequest "https://sh.rustup.rs" | Select-Object -ExpandProperty Content | sh -s -- -y;
+ } else {
+ Write-Error "Host operating system not recognized as being Windows, Linux, or macOS; please download Rust manually from https://rustup.rs/."
+ }
+
+ if (-not (Get-Command rustup -ErrorAction SilentlyContinue)) {
+ Write-Error "After running rustup-init, rustup was not available. Please check logs above to see if something went wrong.";
+ exit -1;
+ }
+}
+
+# Now that rustup is available, go on and make sure that nightly support for
+# rustfmt and clippy is available.
+rustup install nightly
+rustup component add rustfmt clippy
+rustup component add rustfmt clippy --toolchain nightly
diff --git a/src/Simulation/qdk_sim_rs/pyproject.toml b/src/Simulation/qdk_sim_rs/pyproject.toml
new file mode 100644
index 00000000000..8619d8784c5
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools", "wheel", "setuptools-rust"]
+build-backend = "setuptools.build_meta"
diff --git a/src/Simulation/qdk_sim_rs/qdk_sim_experimental/__init__.py b/src/Simulation/qdk_sim_rs/qdk_sim_experimental/__init__.py
new file mode 100644
index 00000000000..53262d7bb3a
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/qdk_sim_experimental/__init__.py
@@ -0,0 +1,53 @@
+#!/bin/env python
+# -*- coding: utf-8 -*-
+##
+# __init__.py: Root for the qdk_sim_experimental package.
+##
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+##
+
+## IMPORTS ##
+
+from typing import Dict, List, Union
+from enum import Enum
+import enum
+import json
+
+import qdk_sim_experimental._qdk_sim_rs as _native
+try:
+ import qdk_sim_experimental.version as _version
+except ImportError:
+ # This could happen if setup.py was not run.
+ _version = None
+
+## EXPORTS ##
+
+__all__ = [
+ "Tableau", "NoiseModel", "Instrument", "State", "Process",
+ "Pauli"
+]
+
+# Re-export native classes.
+from qdk_sim_experimental._qdk_sim_rs import (
+ Tableau, NoiseModel, Instrument, State, Process
+)
+
+class Pauli(enum.Enum):
+ I = 0
+ X = 1
+ Y = 3
+ Z = 2
+
+## BUILD METADATA ##
+
+# Re-export the autogenerated version.
+__version__ = getattr(_version, "__version__", "")
+_is_conda = getattr(_version, "_is_conda", False)
+
+def build_info() -> Dict[str, Union[List[str], str]]:
+ """
+ Returns information about the environment in which this
+ module was built.
+ """
+ return json.loads(_native.build_info_json())
diff --git a/src/Simulation/qdk_sim_rs/qdk_sim_experimental/version.py b/src/Simulation/qdk_sim_rs/qdk_sim_experimental/version.py
new file mode 100644
index 00000000000..d15c19d5933
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/qdk_sim_experimental/version.py
@@ -0,0 +1,9 @@
+# Auto-generated file, do not edit.
+##
+# version.py: Specifies the version of the qsharp package.
+##
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+##
+__version__ = "0.0.0.1"
+_is_conda = False
diff --git a/src/Simulation/qdk_sim_rs/setup.py b/src/Simulation/qdk_sim_rs/setup.py
new file mode 100644
index 00000000000..6d3fad30592
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/setup.py
@@ -0,0 +1,47 @@
+#!/bin/env python
+# -*- coding: utf-8 -*-
+##
+# setup.py: Installs Python integration for qdk_sim_experimental.
+##
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+##
+
+## IMPORTS ##
+
+import setuptools
+from setuptools_rust import Binding, RustExtension
+import os
+
+## VERSION INFORMATION ##
+# Our build process sets the PYTHON_VERSION environment variable to a version
+# string that is compatible with PEP 440, and so we inherit that version number
+# here and propagate that to qsharp/version.py.
+#
+# To make sure that local builds still work without the same environment
+# variables, we'll default to 0.0.0.1 as a development version.
+
+version = os.environ.get('PYTHON_VERSION', '0.0.0.1')
+is_conda = bool(os.environ.get('CONDA_BUILD', False))
+
+with open('./qdk_sim_experimental/version.py', 'w') as f:
+ f.write(f'''# Auto-generated file, do not edit.
+##
+# version.py: Specifies the version of the qsharp package.
+##
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+##
+__version__ = "{version}"
+_is_conda = {is_conda}
+''')
+
+setuptools.setup(
+ name="qdk_sim_experimental",
+ version=version,
+ rust_extensions=[RustExtension("qdk_sim_experimental._qdk_sim_rs", binding=Binding.PyO3, features=["python"])],
+ packages=["qdk_sim_experimental"],
+ # rust extensions are not zip safe, just like C-extensions.
+ zip_safe=False,
+ include_package_data=True,
+)
diff --git a/src/Simulation/qdk_sim_rs/src/c_api.rs b/src/Simulation/qdk_sim_rs/src/c_api.rs
new file mode 100644
index 00000000000..bf88c09065e
--- /dev/null
+++ b/src/Simulation/qdk_sim_rs/src/c_api.rs
@@ -0,0 +1,443 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+// The following two attributes include the README.md for this module when
+// building docs (requires +nightly).
+// See https://github.com/rust-lang/rust/issues/82768#issuecomment-803935643
+// for discussion.
+#![cfg_attr(doc, feature(extended_key_value_attributes))]
+#![cfg_attr(doc, cfg_attr(doc, doc = include_str!("../docs/c-api.md")))]
+
+use crate::{built_info, NoiseModel, Process, State};
+use lazy_static::lazy_static;
+use serde_json::json;
+use std::collections::HashMap;
+use std::ffi::CStr;
+use std::ffi::CString;
+use std::os::raw::c_char;
+use std::ptr;
+use std::sync::Mutex;
+
+struct CApiState {
+ register_state: State,
+ noise_model: NoiseModel,
+}
+
+lazy_static! {
+ static ref STATE: Mutex> = Mutex::new(HashMap::new());
+ static ref LAST_ERROR: Mutex