Check qubit release/measurement status on release (#796)

* Check qubit release/measurement status on release

This mimics the same behavior in the QIR Runtime wrapper for the fullstate simulator as what we have for C#, namely that a qubit is valid for release if and only if it is either in the ground state or the last operation on that qubit was measure.

This fixes #552, and supersedes #710.

* CR feedback

* Revert mutex change
This commit is contained in:
Stefan J. Wernli 2021-08-17 14:17:19 -07:00 коммит произвёл GitHub
Родитель 292976c8b9
Коммит ec491fee57
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 162 добавлений и 27 удалений

Просмотреть файл

@ -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<TReleaseQubit>(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<TMeasure>(this->GetProc("Measure"));
std::vector<unsigned> 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<Result>(
m(this->simulatorId, (unsigned)numBases, reinterpret_cast<unsigned*>(bases), ids.data()));
}
@ -272,6 +303,7 @@ namespace Quantum
{
static TSingleQubitGate op = reinterpret_cast<TSingleQubitGate>(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<TSingleQubitControlledGate>(this->GetProc("MCX"));
std::vector<unsigned> 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<TSingleQubitGate>(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<TSingleQubitControlledGate>(this->GetProc("MCY"));
std::vector<unsigned> 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<TSingleQubitGate>(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<TSingleQubitControlledGate>(this->GetProc("MCZ"));
std::vector<unsigned> 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<TSingleQubitGate>(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<TSingleQubitControlledGate>(this->GetProc("MCH"));
std::vector<unsigned> 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<TSingleQubitGate>(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<TSingleQubitControlledGate>(this->GetProc("MCS"));
std::vector<unsigned> 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<TSingleQubitGate>(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<TSingleQubitControlledGate>(this->GetProc("MCAdjS"));
std::vector<unsigned> 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<TSingleQubitGate>(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<TSingleQubitControlledGate>(this->GetProc("MCT"));
std::vector<unsigned> 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<TSingleQubitGate>(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<TSingleQubitControlledGate>(this->GetProc("MCAdjT"));
std::vector<unsigned> 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<TR>(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<unsigned> 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<TExp>(this->GetProc("Exp"));
std::vector<unsigned> ids = GetQubitIds(numTargets, targets);
exp(this->simulatorId, (unsigned)numTargets, reinterpret_cast<unsigned*>(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<unsigned> idsControls = GetQubitIds(numControls, controls);
cexp(this->simulatorId, (unsigned)numTargets, reinterpret_cast<unsigned*>(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<bool>` is already specialized to use an underlying bitfied to save space.
// See: https://www.cplusplus.com/reference/vector/vector-bool/
std::vector<bool> isMeasured;
private:
TDumpToLocationCallback const dumpToLocationCallback = [](size_t idx, double re, double im,
TDumpLocation location) -> bool {

Просмотреть файл

@ -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<IRuntimeDriver> 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<IRuntimeDriver> 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]")
{

Просмотреть файл

@ -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]);
}
}

Просмотреть файл

@ -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);
}

Просмотреть файл

@ -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)

Просмотреть файл

@ -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