// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. namespace Microsoft.Quantum.Tests { open Microsoft.Quantum.Convert; open Microsoft.Quantum.Intrinsic; open Microsoft.Quantum.Canon; open Microsoft.Quantum.ErrorCorrection; open Microsoft.Quantum.Diagnostics; open Microsoft.Quantum.Arrays; open Microsoft.Quantum.Measurement; open Microsoft.Quantum.Math; // NB: These tests need to be generalized to allow for unit testing CSS // codes as well. Since the recovery functions look different for CSS // codes, we must test the Steane code more manually. operation QeccTestCaseImpl (code : QECC, nScratch : Int, fn : RecoveryFn, error : (Qubit[] => Unit), data : Qubit[]) : Unit { let (encode, decode, syndMeas) = code!; use scratch = Qubit[nScratch]; let logicalRegister = encode!(data, scratch); // Cause an error. error(logicalRegister!); Recover(code, fn, logicalRegister); let (decodedData, decodedScratch) = decode!(logicalRegister); ResetAll(decodedScratch); } function QeccTestCase (code : QECC, nScratch : Int, fn : RecoveryFn, error : (Qubit[] => Unit)) : (Qubit[] => Unit) { return QeccTestCaseImpl(code, nScratch, fn, error, _); } operation AssertCodeCorrectsErrorImpl (code : QECC, nLogical : Int, nScratch : Int, fn : RecoveryFn, error : (Qubit[] => Unit)) : Unit { AssertOperationsEqualReferenced(nLogical, QeccTestCase(code, nScratch, fn, error), NoOp); } /// # Remarks /// This is a function which curries over all but the error to be applied, /// and does not explicitly refer to qubits in any way. /// Thus, the result of evaluating this function is an operation that can /// be passed to ApplyToEach<(Qubit[] => ())> in order to test a *collection* of /// errors in a compact way. function AssertCodeCorrectsError (code : QECC, nLogical : Int, nScratch : Int, fn : RecoveryFn) : ((Qubit[] => Unit) => Unit) { return AssertCodeCorrectsErrorImpl(code, nLogical, nScratch, fn, _); } /// # Summary /// Ensures that the bit flip code can correct a single arbitrary /// bit-flip ($X$) error. @Test("QuantumSimulator") operation TestBitFlip() : Unit { let code = BitFlipCode(); let fn = BitFlipRecoveryFn(); let errors = Mapped(CurriedOp(ApplyPauli), [[PauliX, PauliI, PauliI], [PauliI, PauliX, PauliI], [PauliI, PauliI, PauliX]]); let assertionGenerator = AssertCodeCorrectsError(code, 1, 2, fn); assertionGenerator(NoOp); ApplyToEach(assertionGenerator, errors); } /// # Summary /// Ensures that the 5-qubit perfect code can correct an arbitrary /// single-qubit error. @Test("QuantumSimulator") operation TestFiveQubitCode() : Unit { let code = FiveQubitCode(); let fn = FiveQubitCodeRecoveryFn(); let assertionGenerator = AssertCodeCorrectsError(code, 1, 4, fn); let errors = Mapped(CurriedOp(ApplyPauli), WeightOnePaulis(5)); assertionGenerator(NoOp); ApplyToEach(assertionGenerator, errors); } // TODO: split this test up into several smaller tests. @Test("QuantumSimulator") operation TestFiveQubitTedious() : Unit { let s = SyndromeMeasOp(MeasureStabilizerGenerators([[PauliX, PauliZ, PauliZ, PauliX, PauliI], [PauliI, PauliX, PauliZ, PauliZ, PauliX], [PauliX, PauliI, PauliX, PauliZ, PauliZ], [PauliZ, PauliX, PauliI, PauliX, PauliZ]], _, MeasureWithScratch)); use aux = Qubit[6]; Ry(PI() / 2.5, aux[0]); FiveQubitCodeEncoderImpl([aux[0]], aux[1 .. 4]); let m = aux[5]; mutable n = 0; H(m); Controlled X([m], aux[0]); Controlled Z([m], aux[1]); Controlled Z([m], aux[2]); Controlled X([m], aux[3]); H(m); AssertQubit(Zero, m); if (M(m) == One) { set n = n + 1; X(m); } H(m); Controlled X([m], aux[1]); Controlled Z([m], aux[2]); Controlled Z([m], aux[3]); Controlled X([m], aux[4]); H(m); if (M(m) == One) { set n = n + 2; X(m); } H(m); Controlled X([m], aux[2]); Controlled Z([m], aux[3]); Controlled Z([m], aux[4]); Controlled X([m], aux[0]); H(m); if M(m) == One { set n += 4; X(m); } H(m); Controlled X([m], aux[3]); Controlled Z([m], aux[4]); Controlled Z([m], aux[0]); Controlled X([m], aux[1]); H(m); if M(m) == One { set n += 8; X(m); } EqualityFactI(n, 0, $"syndrome failure"); // Now testing MeasureWithScratch if (MeasureWithScratch([PauliX, PauliZ, PauliZ, PauliX, PauliI], aux[0 .. 4]) == One) { fail $"stabilizer 1 fail"; } if (MeasureWithScratch([PauliI, PauliX, PauliZ, PauliZ, PauliX], aux[0 .. 4]) == One) { fail $"stabilizer 2 fail"; } if (MeasureWithScratch([PauliX, PauliI, PauliX, PauliZ, PauliZ], aux[0 .. 4]) == One) { fail $"stabilizer 3 fail"; } if (MeasureWithScratch([PauliZ, PauliX, PauliI, PauliX, PauliZ], aux[0 .. 4]) == One) { fail $"stabilizer 4 fail"; } ResetAll(aux); } @Test("QuantumSimulator") operation TestFiveQubit() : Unit { let s = SyndromeMeasOp(MeasureStabilizerGenerators([[PauliX, PauliZ, PauliZ, PauliX, PauliI], [PauliI, PauliX, PauliZ, PauliZ, PauliX], [PauliX, PauliI, PauliX, PauliZ, PauliZ], [PauliZ, PauliX, PauliI, PauliX, PauliZ]], _, MeasureWithScratch)); // TODO: split this test up into several smaller tests. use aux = Qubit[5]; // let's start with an arbitrary logical state. Ry(PI() / 2.5, aux[0]); FiveQubitCodeEncoderImpl([aux[0]], aux[1 .. 4]); let syn = s!(LogicalRegister(aux)); let a = ResultArrayAsInt(syn!); EqualityFactI(a, 0, $"syndrome failure"); let (encode, decode, syndMeas) = (FiveQubitCode())!; let recovery = FiveQubitCodeRecoveryFn(); for idx in 0 .. 4 { X(aux[idx]); let syndrome = syndMeas!(LogicalRegister(aux)); let recoveryOp = recovery!(syndrome); ApplyPauli(recoveryOp, aux); let ans = ResultArrayAsInt((syndMeas!(LogicalRegister(aux)))!); EqualityFactI(ans, 0, $"Correction failure"); } for idx in 0 .. 4 { Y(aux[idx]); let syndrome = syndMeas!(LogicalRegister(aux)); let recoveryOp = recovery!(syndrome); ApplyPauli(recoveryOp, aux); let ans = ResultArrayAsInt((syndMeas!(LogicalRegister(aux)))!); EqualityFactI(ans, 0, $"Correction failure"); } for idx in 0 .. 4 { Z(aux[idx]); let syndrome = syndMeas!(LogicalRegister(aux)); let recoveryOp = recovery!(syndrome); ApplyPauli(recoveryOp, aux); let ans = ResultArrayAsInt((syndMeas!(LogicalRegister(aux)))!); EqualityFactI(ans, 0, $"Correction failure"); } ResetAll(aux); } @Test("QuantumSimulator") operation TestSteaneCodeEncoder() : Unit { use aux = Qubit[7]; SteaneCodeEncoderImpl(aux[0 .. 0], aux[1 .. 6]); if (MeasureWithScratch([PauliX, PauliI, PauliX, PauliI, PauliX, PauliI, PauliX], aux[0 .. 6]) == One) { fail $"Steane code first X stabilizer"; } if (MeasureWithScratch([PauliI, PauliX, PauliX, PauliI, PauliI, PauliX, PauliX], aux[0 .. 6]) == One) { fail $"Steane code second X stabilizer"; } if (MeasureWithScratch([PauliI, PauliI, PauliI, PauliX, PauliX, PauliX, PauliX], aux[0 .. 6]) == One) { fail $"Steane code third X stabilizer"; } if (MeasureWithScratch([PauliZ, PauliI, PauliZ, PauliI, PauliZ, PauliI, PauliZ], aux[0 .. 6]) == One) { fail $"Steane code first Z stabilizer"; } if (MeasureWithScratch([PauliI, PauliZ, PauliZ, PauliI, PauliI, PauliZ, PauliZ], aux[0 .. 6]) == One) { fail $"Steane code second Z stabilizer"; } if (MeasureWithScratch([PauliI, PauliI, PauliI, PauliZ, PauliZ, PauliZ, PauliZ], aux[0 .. 6]) == One) { fail $"Steane code third Z stabilizer"; } ResetAll(aux); } @Test("QuantumSimulator") operation TestPi4YInjection() : Unit { use aux = Qubit[2]; // magic state in aux[1] Ry(PI() / 4.0, aux[1]); let expected = ApplyToEachA(Ry(PI() / 4.0, _), _); let actual = ApplyToEach(InjectPi4YRotation(_, aux[1]), _); AssertOperationsEqualReferenced(1, actual, expected); // NB: we explicitly do not reset the // qubit containing the magic state, // so as to test whether the injection // correctly reset for us. AssertMeasurement([PauliZ], [aux[1]], Zero, $"Magic state was not reset to |0〉."); Reset(aux[0]); } @Test("QuantumSimulator") operation TestPi4YInjectionAdjoint() : Unit { use aux = Qubit[2]; // magic state in aux[1] Ry(PI() / 4.0, aux[1]); let expected = ApplyToEachA(Ry(-PI() / 4.0, _), _); let actual = ApplyToEach(Adjoint InjectPi4YRotation(_, aux[1]), _); AssertOperationsEqualReferenced(1, actual, expected); // NB: we explicitly do not reset the // qubit containing the magic state, // so as to test whether the injection // correctly reset for us. AssertMeasurement([PauliZ], [aux[1]], Zero, $"Magic state was not reset to |0〉."); Reset(aux[0]); } /// # Summary /// Applies logical operators before and after the encoding circuit, /// that as a whole acts as identity. @Test("QuantumSimulator") operation TestKDLogicalOperator() : Unit { use aux = Qubit[7]; X(aux[0]); SteaneCodeEncoderImpl(aux[0 .. 0], aux[1 .. 6]); // The logical qubit here is in One X(aux[0]); X(aux[1]); X(aux[2]); // The logical qubit here is in Zero Z(aux[1]); Z(aux[3]); Z(aux[5]); // Z logical operator does nothing. let (logicalQubit, xsyn, zsyn) = _ExtractLogicalQubitFromSteaneCode(LogicalRegister(aux)); // The logical qubit must be in Zero EqualityFactI(xsyn, -1, $"X syndrome detected!"); EqualityFactI(zsyn, -1, $"Z syndrome detected!"); AssertQubit(Zero, aux[0]); ResetAll(aux); } @Test("QuantumSimulator") operation TestKDSyndrome() : Unit { use aux = Qubit[7]; for idx in 0 .. 6 { ResetAll(aux); SteaneCodeEncoderImpl(aux[0 .. 0], aux[1 .. 6]); Z(aux[idx]); let (logiQ, xsyn, zsyn) = _ExtractLogicalQubitFromSteaneCode(LogicalRegister(aux)); EqualityFactI(idx, xsyn, $"wrong X syndrome"); ResetAll(aux); SteaneCodeEncoderImpl(aux[0 .. 0], aux[1 .. 6]); X(aux[idx]); let (logiQ2, xsyn2, zsyn2) = _ExtractLogicalQubitFromSteaneCode(LogicalRegister(aux)); EqualityFactI(idx, zsyn2, $"wrong Z syndrome"); } ResetAll(aux); } @Test("QuantumSimulator") operation TestKnillDistillationNoError() : Unit { use register = Qubit[15]; // Prepare the perfect magic states. ApplyToEach(Ry(PI() / 4.0, _), register); let accept = KnillDistill(register); Ry(-PI() / 4.0, register[0]); Fact(accept, $"Distillation failure"); ApplyToEach(AssertQubit(Zero, _), register); // NB: no need to reset, we just asserted everything // was returned to |0〉. } /// # Summary /// Tests if the distillation routine works as intended. /// This protocol is supposed to catch any weight 2 errors /// on the input magic states, assuming perfect Cliffords. /// Here we do not attempt to correct detected errors, /// since corrections would make the output magic state /// less accurate, compared to post-selection on zero syndrome. @Test("QuantumSimulator") operation TestKD() : Unit { use rm = Qubit[15]; ApplyToEach(Ry(PI() / 4.0, _), rm); let acc = KnillDistill(rm); // Check that the rough magic states were // successfully reset to |0〉. ApplyToEach(AssertQubit(Zero, _), Rest(rm)); Ry(-PI() / 4.0, rm[0]); EqualityFactB(true, acc, $"Distillation failure"); AssertQubit(Zero, rm[0]); // Cases where a single magic state is wrong for idx in [0, 8, 14] { ResetAll(rm); ApplyToEach(Ry(PI() / 4.0, _), rm); Y(rm[idx]); let acc1 = KnillDistill(rm); // Check that the rough magic states were // successfully reset to |0〉. ApplyToEach(AssertQubit(Zero, _), Rest(rm)); EqualityFactB(false, acc1, $"Distillation missed an error"); } // Cases where two magic states are wrong for (idxFirst, idxSecond) in [(0,1), (0, 3), (0,14), (4, 13), (13, 14)] { ResetAll(rm); ApplyToEach(Ry(PI() / 4.0, _), rm); Y(rm[idxFirst]); Y(rm[idxSecond]); let acc1 = KnillDistill(rm); // Check that the rough magic states were // successfully reset to |0〉. ApplyToEach(AssertQubit(Zero, _), Rest(rm)); EqualityFactB(false, acc1, $"Distillation missed a pair error"); } ResetAll(rm); } operation CSSTestCaseImpl (code : CSS, nScratch : Int, fnX : RecoveryFn, fnZ : RecoveryFn, error : (Qubit[] => Unit), data : Qubit[]) : Unit { let (encode, decode, syndMeasX, syndMeasZ) = code!; use scratch = Qubit[nScratch]; let logicalRegister = encode!(data, scratch); // Cause an error. Message($"Applying error {error}."); error(logicalRegister!); RecoverCSS(code, fnX, fnZ, logicalRegister); let (decodedData, decodedScratch) = decode!(logicalRegister); ApplyToEach(Reset, decodedScratch); } function CSSTestCase (code : CSS, nScratch : Int, fnX : RecoveryFn, fnZ : RecoveryFn, error : (Qubit[] => Unit)) : (Qubit[] => Unit) { return CSSTestCaseImpl(code, nScratch, fnX, fnZ, error, _); } operation AssertCSSCodeCorrectsErrorImpl (code : CSS, nLogical : Int, nScratch : Int, fnX : RecoveryFn, fnZ : RecoveryFn, error : (Qubit[] => Unit)) : Unit { AssertOperationsEqualReferenced(nLogical, CSSTestCase(code, nScratch, fnX, fnZ, error), NoOp); } function AssertCSSCodeCorrectsError (code : CSS, nLogical : Int, nScratch : Int, fnX : RecoveryFn, fnZ : RecoveryFn) : ((Qubit[] => Unit) => Unit) { return AssertCSSCodeCorrectsErrorImpl(code, nLogical, nScratch, fnX, fnZ, _); } /// # Summary /// Ensures that the 7-qubit Steane code can correct an arbitrary /// single-qubit error. @Test("QuantumSimulator") operation TestSteaneCode() : Unit { let code = SteaneCode(); let (fnX, fnZ) = SteaneCodeRecoveryFns(); let assertionGenerator = AssertCSSCodeCorrectsError(code, 1, 6, fnX, fnZ); let errors = Mapped(CurriedOp(ApplyPauli), WeightOnePaulis(7)); assertionGenerator(NoOp); ApplyToEach(assertionGenerator, errors); } }