diff --git a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp index 74fd47ef266..61797ceb832 100644 --- a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp +++ b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp @@ -24,6 +24,7 @@ #include "FloatUtils.hpp" #include "QirTypes.hpp" // TODO: Consider removing dependency on this file. +#include "QirRuntime.hpp" #include "QirRuntimeApi_I.hpp" #include "QSharpSimApi_I.hpp" #include "SimFactory.hpp" @@ -164,6 +165,19 @@ namespace Quantum return proc; } + void UnmarkAsMeasuredSingleQubit(Qubit q) + { + isMeasured[GetQubitId(q)] = false; + } + + void UnmarkAsMeasuredQubitList(long num, Qubit* qubit) + { + for (const auto& id : GetQubitIds(num, qubit)) + { + isMeasured[id] = false; + } + } + public: CFullstateSimulator(uint32_t userProvidedSeed = 0) : handle(LoadQuantumSimulator()) { @@ -220,16 +234,28 @@ namespace Quantum 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. + if (isMeasured.size() < id + 1) + { + isMeasured.resize(id + 1, false); + } return q; } void ReleaseQubit(Qubit q) override { - typedef void (*TReleaseQubit)(unsigned, unsigned); + typedef bool (*TReleaseQubit)(unsigned, unsigned); static TReleaseQubit releaseQubit = reinterpret_cast(this->GetProc("release")); - releaseQubit(this->simulatorId, GetQubitId(q)); // Release qubit in the simulator. - qubitManager->Release(q); // Release it in the qubit manager. + // Release qubit in the simulator, checking to make sure that release was valid. + auto id = GetQubitId(q); + if (!releaseQubit(this->simulatorId, id) && !isMeasured[id]) + { + // We reject the release of a qubit that is not in the ground state (releaseQubit returns false), + // and was not recently measured (ie: the last operation was not measurement). This means the + // state is not well known, and therefore the safety of release is not guaranteed. + quantum__rt__fail_cstr("Released qubit neither measured nor in ground state."); + } + qubitManager->Release(q); // Release it in the qubit manager. } Result Measure(long numBases, PauliId bases[], long numTargets, Qubit targets[]) override @@ -238,6 +264,11 @@ namespace Quantum typedef unsigned (*TMeasure)(unsigned, unsigned, unsigned*, unsigned*); static TMeasure m = reinterpret_cast(this->GetProc("Measure")); std::vector ids = GetQubitIds(numTargets, targets); + if (ids.size() == 1) + { + // If measuring exactly one qubit, mark it as measured for tracking. + isMeasured[ids[0]] = true; + } return reinterpret_cast( m(this->simulatorId, (unsigned)numBases, reinterpret_cast(bases), ids.data())); } @@ -272,6 +303,7 @@ namespace Quantum { static TSingleQubitGate op = reinterpret_cast(this->GetProc("X")); op(this->simulatorId, GetQubitId(q)); + UnmarkAsMeasuredSingleQubit(q); } void ControlledX(long numControls, Qubit controls[], Qubit target) override @@ -279,12 +311,15 @@ namespace Quantum static TSingleQubitControlledGate op = reinterpret_cast(this->GetProc("MCX")); std::vector ids = GetQubitIds(numControls, controls); op(this->simulatorId, (unsigned)numControls, ids.data(), GetQubitId(target)); + UnmarkAsMeasuredSingleQubit(target); + UnmarkAsMeasuredQubitList(numControls, controls); } void Y(Qubit q) override { static TSingleQubitGate op = reinterpret_cast(this->GetProc("Y")); op(this->simulatorId, GetQubitId(q)); + UnmarkAsMeasuredSingleQubit(q); } void ControlledY(long numControls, Qubit controls[], Qubit target) override @@ -292,12 +327,15 @@ namespace Quantum static TSingleQubitControlledGate op = reinterpret_cast(this->GetProc("MCY")); std::vector ids = GetQubitIds(numControls, controls); op(this->simulatorId, (unsigned)numControls, ids.data(), GetQubitId(target)); + UnmarkAsMeasuredSingleQubit(target); + UnmarkAsMeasuredQubitList(numControls, controls); } void Z(Qubit q) override { static TSingleQubitGate op = reinterpret_cast(this->GetProc("Z")); op(this->simulatorId, GetQubitId(q)); + UnmarkAsMeasuredSingleQubit(q); } void ControlledZ(long numControls, Qubit controls[], Qubit target) override @@ -305,12 +343,15 @@ namespace Quantum static TSingleQubitControlledGate op = reinterpret_cast(this->GetProc("MCZ")); std::vector ids = GetQubitIds(numControls, controls); op(this->simulatorId, (unsigned)numControls, ids.data(), GetQubitId(target)); + UnmarkAsMeasuredSingleQubit(target); + UnmarkAsMeasuredQubitList(numControls, controls); } void H(Qubit q) override { static TSingleQubitGate op = reinterpret_cast(this->GetProc("H")); op(this->simulatorId, GetQubitId(q)); + UnmarkAsMeasuredSingleQubit(q); } void ControlledH(long numControls, Qubit controls[], Qubit target) override @@ -318,12 +359,15 @@ namespace Quantum static TSingleQubitControlledGate op = reinterpret_cast(this->GetProc("MCH")); std::vector ids = GetQubitIds(numControls, controls); op(this->simulatorId, (unsigned)numControls, ids.data(), GetQubitId(target)); + UnmarkAsMeasuredSingleQubit(target); + UnmarkAsMeasuredQubitList(numControls, controls); } void S(Qubit q) override { static TSingleQubitGate op = reinterpret_cast(this->GetProc("S")); op(this->simulatorId, GetQubitId(q)); + UnmarkAsMeasuredSingleQubit(q); } void ControlledS(long numControls, Qubit controls[], Qubit target) override @@ -331,12 +375,15 @@ namespace Quantum static TSingleQubitControlledGate op = reinterpret_cast(this->GetProc("MCS")); std::vector ids = GetQubitIds(numControls, controls); op(this->simulatorId, (unsigned)numControls, ids.data(), GetQubitId(target)); + UnmarkAsMeasuredSingleQubit(target); + UnmarkAsMeasuredQubitList(numControls, controls); } void AdjointS(Qubit q) override { static TSingleQubitGate op = reinterpret_cast(this->GetProc("AdjS")); op(this->simulatorId, GetQubitId(q)); + UnmarkAsMeasuredSingleQubit(q); } void ControlledAdjointS(long numControls, Qubit controls[], Qubit target) override @@ -345,12 +392,15 @@ namespace Quantum reinterpret_cast(this->GetProc("MCAdjS")); std::vector ids = GetQubitIds(numControls, controls); op(this->simulatorId, (unsigned)numControls, ids.data(), GetQubitId(target)); + UnmarkAsMeasuredSingleQubit(target); + UnmarkAsMeasuredQubitList(numControls, controls); } void T(Qubit q) override { static TSingleQubitGate op = reinterpret_cast(this->GetProc("T")); op(this->simulatorId, GetQubitId(q)); + UnmarkAsMeasuredSingleQubit(q); } void ControlledT(long numControls, Qubit controls[], Qubit target) override @@ -358,12 +408,15 @@ namespace Quantum static TSingleQubitControlledGate op = reinterpret_cast(this->GetProc("MCT")); std::vector ids = GetQubitIds(numControls, controls); op(this->simulatorId, (unsigned)numControls, ids.data(), GetQubitId(target)); + UnmarkAsMeasuredSingleQubit(target); + UnmarkAsMeasuredQubitList(numControls, controls); } void AdjointT(Qubit q) override { static TSingleQubitGate op = reinterpret_cast(this->GetProc("AdjT")); op(this->simulatorId, GetQubitId(q)); + UnmarkAsMeasuredSingleQubit(q); } void ControlledAdjointT(long numControls, Qubit controls[], Qubit target) override @@ -372,6 +425,8 @@ namespace Quantum reinterpret_cast(this->GetProc("MCAdjT")); std::vector ids = GetQubitIds(numControls, controls); op(this->simulatorId, (unsigned)numControls, ids.data(), GetQubitId(target)); + UnmarkAsMeasuredSingleQubit(target); + UnmarkAsMeasuredQubitList(numControls, controls); } void R(PauliId axis, Qubit target, double theta) override @@ -380,6 +435,7 @@ namespace Quantum static TR r = reinterpret_cast(this->GetProc("R")); r(this->simulatorId, GetBasis(axis), theta, GetQubitId(target)); + UnmarkAsMeasuredSingleQubit(target); } void ControlledR(long numControls, Qubit controls[], PauliId axis, Qubit target, double theta) override @@ -389,6 +445,8 @@ namespace Quantum std::vector ids = GetQubitIds(numControls, controls); cr(this->simulatorId, GetBasis(axis), theta, (unsigned)numControls, ids.data(), GetQubitId(target)); + UnmarkAsMeasuredSingleQubit(target); + UnmarkAsMeasuredQubitList(numControls, controls); } void Exp(long numTargets, PauliId paulis[], Qubit targets[], double theta) override @@ -397,6 +455,7 @@ namespace Quantum static TExp exp = reinterpret_cast(this->GetProc("Exp")); std::vector ids = GetQubitIds(numTargets, targets); exp(this->simulatorId, (unsigned)numTargets, reinterpret_cast(paulis), theta, ids.data()); + UnmarkAsMeasuredQubitList(numTargets, targets); } void ControlledExp(long numControls, Qubit controls[], long numTargets, PauliId paulis[], Qubit targets[], @@ -408,6 +467,8 @@ namespace Quantum std::vector idsControls = GetQubitIds(numControls, controls); cexp(this->simulatorId, (unsigned)numTargets, reinterpret_cast(paulis), theta, (unsigned)numControls, idsControls.data(), idsTargets.data()); + UnmarkAsMeasuredQubitList(numTargets, targets); + UnmarkAsMeasuredQubitList(numControls, controls); } bool Assert(long numTargets, PauliId* bases, Qubit* targets, Result result, const char* failureMessage) override @@ -436,6 +497,11 @@ namespace Quantum void GetStateTo(TDumpLocation location, TDumpToLocationCallback callback); bool GetRegisterTo(TDumpLocation location, TDumpToLocationCallback callback, const QirArray* qubits); + // This bit std::vector tracks whether the last operation on a given qubit was Measure. + // Note that `std::vector` is already specialized to use an underlying bitfied to save space. + // See: https://www.cplusplus.com/reference/vector/vector-bool/ + std::vector isMeasured; + private: TDumpToLocationCallback const dumpToLocationCallback = [](size_t idx, double re, double im, TDumpLocation location) -> bool { diff --git a/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp b/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp index a7b87737f8c..45b05c750a1 100644 --- a/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp +++ b/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp @@ -145,6 +145,10 @@ TEST_CASE("Fullstate simulator: ZZ measure", "[fullstate_simulator]") Result rOne = iqa->Measure(2, paulis, 2, q); REQUIRE(Result_One == sim->GetResultValue(rOne)); + iqa->X(q[1]); + iqa->ControlledX(1, &q[0], q[1]); + iqa->H(q[0]); + sim->ReleaseQubit(q[0]); sim->ReleaseQubit(q[1]); } @@ -173,6 +177,8 @@ TEST_CASE("Fullstate simulator: assert probability", "[fullstate_simulator]") REQUIRE(!idig->Assert(2, xi, qs, sim->UseZero(), "")); REQUIRE(!idig->Assert(2, xi, qs, sim->UseOne(), "")); + iqa->X(qs[0]); + sim->ReleaseQubit(qs[0]); sim->ReleaseQubit(qs[1]); } @@ -196,6 +202,9 @@ TEST_CASE("Fullstate simulator: toffoli", "[fullstate_simulator]") iqa->ControlledX(2, qs, qs[2]); REQUIRE(Result_One == sim->GetResultValue(MZ(iqa, qs[2]))); + iqa->X(qs[1]); + iqa->X(qs[0]); + for (int i = 0; i < 3; i++) { sim->ReleaseQubit(qs[i]); @@ -307,6 +316,8 @@ TEST_CASE("Fullstate simulator: exponents", "[fullstate_simulator]") PauliId paulis[3] = {PauliId_X, PauliId_Y, PauliId_Z}; iqa->Exp(2, paulis, qs, 0.42); iqa->ControlledExp(2, qs, 3, paulis, &qs[2], 0.17); + iqa->ControlledExp(2, qs, 3, paulis, &qs[2], -0.17); + iqa->Exp(2, paulis, qs, -0.42); // not crashes? consider it passing REQUIRE(true); @@ -377,12 +388,32 @@ TEST_CASE("Fullstate simulator: get qubit state of Bell state", "[fullstate_simu REQUIRE(1.0 == Approx(norm).epsilon(0.0001)); norm = 0.0; + iqa->Y(qs[2]); + iqa->ControlledX(1, &qs[0], qs[1]); + iqa->H(qs[0]); + for (int i = 0; i < n; i++) { sim->ReleaseQubit(qs[i]); } } +extern "C" int Microsoft__Quantum__Testing__QIR__InvalidRelease__Interop(); // NOLINT +TEST_CASE("QIR: Simulator rejects unmeasured, non-zero release", "[fullstate_simulator]") +{ + std::unique_ptr sim = CreateFullstateSimulator(); + QirExecutionContext::Scoped qirctx(sim.get(), true /*trackAllocatedObjects*/); + REQUIRE_THROWS(Microsoft__Quantum__Testing__QIR__InvalidRelease__Interop()); +} + +extern "C" int Microsoft__Quantum__Testing__QIR__MeasureRelease__Interop(); // NOLINT +TEST_CASE("QIR: Simulator accepts measured release", "[fullstate_simulator]") +{ + std::unique_ptr sim = CreateFullstateSimulator(); + QirExecutionContext::Scoped qirctx(sim.get(), true /*trackAllocatedObjects*/); + REQUIRE_NOTHROW(Microsoft__Quantum__Testing__QIR__MeasureRelease__Interop()); +} + extern "C" int Microsoft__Quantum__Testing__QIR__Test_Simulator_QIS__Interop(); // NOLINT TEST_CASE("QIR: invoke all standard Q# gates against the fullstate simulator", "[fullstate_simulator]") { diff --git a/src/Qir/Tests/FullstateSimulator/qsharp/qir-test-simulator.qs b/src/Qir/Tests/FullstateSimulator/qsharp/qir-test-simulator.qs index bb97857e50c..95060828d74 100644 --- a/src/Qir/Tests/FullstateSimulator/qsharp/qir-test-simulator.qs +++ b/src/Qir/Tests/FullstateSimulator/qsharp/qir-test-simulator.qs @@ -71,6 +71,7 @@ namespace Microsoft.Quantum.Testing.QIR H(ctls[0]); H(ctls[1]); if (M(targets[0]) != Zero) { set res = 2; } + ResetAll(targets + ctls); } if (res != 0) { return 70 + res; } @@ -79,7 +80,23 @@ namespace Microsoft.Quantum.Testing.QIR H(qs[0]); H(qs[2]); if (Measure([PauliX, PauliZ, PauliX], qs) != Zero) { set res = 80; } + ResetAll(qs); } return res; } + + @EntryPoint() + operation InvalidRelease() : Unit { + use q = Qubit(); + let _ = M(q); + X(q); + } + + @EntryPoint() + operation MeasureRelease() : Unit { + use qs = Qubit[2]; + X(qs[0]); + let _ = Measure([PauliX], [qs[1]]); + let _ = M(qs[0]); + } } diff --git a/src/Qir/Tests/QIR-dynamic/qsharp/qir-test-assert.qs b/src/Qir/Tests/QIR-dynamic/qsharp/qir-test-assert.qs index aa9a84ff95f..81ecafe51a0 100644 --- a/src/Qir/Tests/QIR-dynamic/qsharp/qir-test-assert.qs +++ b/src/Qir/Tests/QIR-dynamic/qsharp/qir-test-assert.qs @@ -82,10 +82,13 @@ namespace Microsoft.Quantum.Testing.QIR { AssertMeasurement( [PauliZ], [qubit], Zero, "0: Newly allocated qubit must be in the |0> state."); AssertMeasurementProbability([PauliZ], [qubit], Zero, 1.0, "1: Newly allocated qubit must be in the |0> state.", 1e-10); - X(qubit); // |0> -> |1> - - AssertMeasurement( [PauliZ], [qubit], One, "2: Newly allocated qubit after X() must be in the |1> state."); - AssertMeasurementProbability([PauliZ], [qubit], One, 1.0, "3: Newly allocated qubit after X() must be in the |1> state.", 1e-10); + within { + X(qubit); // |0> -> |1> + } + apply { + AssertMeasurement( [PauliZ], [qubit], One, "2: Newly allocated qubit after X() must be in the |1> state."); + AssertMeasurementProbability([PauliZ], [qubit], One, 1.0, "3: Newly allocated qubit after X() must be in the |1> state.", 1e-10); + } } @EntryPoint() @@ -119,14 +122,18 @@ namespace Microsoft.Quantum.Testing.QIR { } //H(qubit); // Back to |0> let str2 = "Newly allocated qubit after x() followed by H() must be in the |-> state"; - X(qubit); // |1> - H(qubit); // |-> - AssertMeasurement( [PauliX], [qubit], One, str2); - // 50% probability in other Pauli bases: - AssertMeasurementProbability([PauliZ], [qubit], Zero, 0.5, str2, 1e-10); - AssertMeasurementProbability([PauliZ], [qubit], One, 0.5, str2, 1e-10); - AssertMeasurementProbability([PauliY], [qubit], Zero, 0.5, str2, 1e-10); - AssertMeasurementProbability([PauliY], [qubit], One, 0.5, str2, 1e-10); + within { + X(qubit); // |1> + H(qubit); // |-> + } + apply { + AssertMeasurement( [PauliX], [qubit], One, str2); + // 50% probability in other Pauli bases: + AssertMeasurementProbability([PauliZ], [qubit], Zero, 0.5, str2, 1e-10); + AssertMeasurementProbability([PauliZ], [qubit], One, 0.5, str2, 1e-10); + AssertMeasurementProbability([PauliY], [qubit], Zero, 0.5, str2, 1e-10); + AssertMeasurementProbability([PauliY], [qubit], One, 0.5, str2, 1e-10); + } } // (|0> + i|1>) / SQRT(2) = SH|0> = S|+> @@ -146,15 +153,19 @@ namespace Microsoft.Quantum.Testing.QIR { AssertMeasurementProbability([PauliX], [qubit], One, 0.5, "4: Call failed", 1e-10); } // Adjoint S(qubit); // Back to |+> // H(qubit); // Back to |0> - X(qubit); // |1> - H(qubit); // |-> - S(qubit); // (|0> - i|1>) / SQRT(2) - AssertMeasurement( [PauliY], [qubit], One, "5: Call failed"); - // 50% probability in other Pauli bases: - AssertMeasurementProbability([PauliZ], [qubit], Zero, 0.5, "6: Call failed", 1e-10); - AssertMeasurementProbability([PauliZ], [qubit], One, 0.5, "7: Call failed", 1e-10); - AssertMeasurementProbability([PauliX], [qubit], Zero, 0.5, "8: Call failed", 1e-10); - AssertMeasurementProbability([PauliX], [qubit], One, 0.5, "9: Call failed", 1e-10); + within { + X(qubit); // |1> + H(qubit); // |-> + S(qubit); // (|0> - i|1>) / SQRT(2) + } + apply { + AssertMeasurement( [PauliY], [qubit], One, "5: Call failed"); + // 50% probability in other Pauli bases: + AssertMeasurementProbability([PauliZ], [qubit], Zero, 0.5, "6: Call failed", 1e-10); + AssertMeasurementProbability([PauliZ], [qubit], One, 0.5, "7: Call failed", 1e-10); + AssertMeasurementProbability([PauliX], [qubit], Zero, 0.5, "8: Call failed", 1e-10); + AssertMeasurementProbability([PauliX], [qubit], One, 0.5, "9: Call failed", 1e-10); + } } @@ -212,6 +223,9 @@ namespace Microsoft.Quantum.Testing.QIR { AssertMeasurementProbability([PauliZ, PauliZ], [left, right], One, 0.5, "I: Call failed", 1E-05); AssertMeasurementProbability([PauliY, PauliY], [left, right], Zero, 0.5, "J: Call failed", 1E-05); AssertMeasurementProbability([PauliY, PauliY], [left, right], One, 0.5, "K: Call failed", 1E-05); + + Reset(right); + Reset(left); } // Task 3. |0000>+|1111> or |0011>+|1100> ? @@ -236,6 +250,8 @@ namespace Microsoft.Quantum.Testing.QIR { AssertMeasurementProbability([PauliZ, PauliZ], [qubitIds[0], qubitIds[1]], Zero, 1.0, "3: Call failed", 1E-05); // |00> or |11> AssertMeasurementProbability([PauliZ, PauliZ], [qubitIds[1], qubitIds[2]], One, 1.0, "4: Call failed", 1E-05); // |01> or |10> AssertMeasurementProbability([PauliZ, PauliZ], [qubitIds[2], qubitIds[3]], Zero, 1.0, "5: Call failed", 1E-05); // |00> or |11> + + ResetAll(qubitIds); } // Bell states (superposition of |00> and |11>, `(|10> + |01>) / SQRT(2)`): @@ -305,6 +321,9 @@ namespace Microsoft.Quantum.Testing.QIR { AssertMeasurement( [PauliX, PauliZ], [left, right], Zero, "Error: Measuring (|0+> + |1->)/SQRT(2) must return Zero in 𝑋𝑍-basis" ); AssertMeasurementProbability([PauliX, PauliZ], [left, right], Zero, 1.0, "Error: Measuring (|0+> + |1->)/SQRT(2) must return Zero always in 𝑋𝑍-basis", 1E-05); AssertMeasurementProbability([PauliX, PauliZ], [left, right], One, 0.0, "Error: Measuring (|0+> + |1->)/SQRT(2) must not return One in 𝑋𝑍-basis" , 1E-05); + + Reset(right); + Reset(left); } diff --git a/src/Simulation/Native/src/simulator/capi.cpp b/src/Simulation/Native/src/simulator/capi.cpp index da95a1754d2..827351f2ee1 100644 --- a/src/Simulation/Native/src/simulator/capi.cpp +++ b/src/Simulation/Native/src/simulator/capi.cpp @@ -69,9 +69,11 @@ extern "C" Microsoft::Quantum::Simulator::get(id)->allocateQubit(q); } - MICROSOFT_QUANTUM_DECL void release(_In_ unsigned id, _In_ unsigned q) + MICROSOFT_QUANTUM_DECL bool release(_In_ unsigned id, _In_ unsigned q) { - Microsoft::Quantum::Simulator::get(id)->release(q); + // The underlying simulator function will return True if and only if the qubit being released + // was in the ground state prior to release. + return Microsoft::Quantum::Simulator::get(id)->release(q); } MICROSOFT_QUANTUM_DECL unsigned num_qubits(_In_ unsigned id) diff --git a/src/Simulation/Native/src/simulator/capi.hpp b/src/Simulation/Native/src/simulator/capi.hpp index e0d4b25d0c6..2c33478b56b 100644 --- a/src/Simulation/Native/src/simulator/capi.hpp +++ b/src/Simulation/Native/src/simulator/capi.hpp @@ -70,7 +70,7 @@ extern "C" // allocate and release MICROSOFT_QUANTUM_DECL void allocateQubit(_In_ unsigned sid, _In_ unsigned qid); // NOLINT - MICROSOFT_QUANTUM_DECL void release(_In_ unsigned sid, _In_ unsigned q); // NOLINT + MICROSOFT_QUANTUM_DECL bool release(_In_ unsigned sid, _In_ unsigned q); // NOLINT MICROSOFT_QUANTUM_DECL unsigned num_qubits(_In_ unsigned sid); // NOLINT // single-qubit gates