def runJob(self, eng): num_subsystem = 8 prog = sf.Program(num_subsystem, name="remote_job") U = random_interferometer(4) with prog.context as q: # Initial squeezed states # Allowed values are r=1.0 or r=0.0 ops.S2gate(1.0) | (q[0], q[4]) ops.S2gate(1.0) | (q[1], q[5]) ops.S2gate(1.0) | (q[3], q[7]) # Interferometer on the signal modes (0-3) ops.Interferometer(U) | (q[0], q[1], q[2], q[3]) ops.BSgate(0.543, 0.123) | (q[2], q[0]) ops.Rgate(0.453) | q[1] ops.MZgate(0.65, -0.54) | (q[2], q[3]) # *Same* interferometer on the idler modes (4-7) ops.Interferometer(U) | (q[4], q[5], q[6], q[7]) ops.BSgate(0.543, 0.123) | (q[6], q[4]) ops.Rgate(0.453) | q[5] ops.MZgate(0.65, -0.54) | (q[6], q[7]) ops.MeasureFock() | q eng = eng results =eng.run(prog, shots=10) # state = results.state # measurements = results.samples return results.samples
def test_equivalence_different_params(self, compare_params): """Programs with different parameters differ.""" prog_1 = sf.Program(2) prog_2 = sf.Program(2) with prog_1.context as q: ops.Fock(2) | q[0] ops.BSgate() | (q[0], q[1]) ops.MeasureFock() | q[1] with prog_2.context as q: ops.Fock(1) | q[0] # different parameter ops.BSgate() | (q[0], q[1]) ops.MeasureFock() | q[1] if compare_params: # should NOT be equivalent assert not prog_1.equivalence(prog_2, compare_params=compare_params) assert not prog_2.equivalence(prog_1, compare_params=compare_params) else: # should be equivalent assert prog_1.equivalence(prog_2, compare_params=compare_params) assert prog_2.equivalence(prog_1, compare_params=compare_params)
def test_generate_code_with_engine(self, engine_kwargs): """Test generating code for a regular program with an engine""" prog = sf.Program(3) eng = sf.Engine(**engine_kwargs) with prog.context as q: ops.Sgate(0.54, 0) | q[0] ops.BSgate(0.45, np.pi / 2) | (q[0], q[2]) ops.Sgate(3 * np.pi / 2, 0) | q[1] ops.BSgate(2 * np.pi, 0.62) | (q[0], q[1]) ops.MeasureFock() | q[0] results = eng.run(prog) code = io.generate_code(prog, eng) code_list = code.split("\n") formatting_str = f"\"{engine_kwargs['backend']}\"" if "backend_options" in engine_kwargs: formatting_str += ( ", backend_options=" f'{{"cutoff_dim": {engine_kwargs["backend_options"]["cutoff_dim"]}}}' ) expected = prog_txt.format(engine_args=formatting_str).split("\n") for i, row in enumerate(code_list): assert row == expected[i]
def test_two_dimensional_cluster_denmark(): """ Two-dimensional temporal-mode cluster state as demonstrated in https://arxiv.org/pdf/1906.08709 """ np.random.seed(42) sq_r = 3 delay1 = 1 # number of timebins in the short delay line delay2 = 12 # number of timebins in the long delay line n = 200 # number of timebins shots = 10 # first half of cluster state measured in X, second half in P theta_A = [0] * int(n / 2) + [np.pi / 2] * int( n / 2) # measurement angles for detector A theta_B = theta_A # measurement angles for detector B # 2D cluster prog = tdmprogram.TDMProgram([1, delay2 + delay1 + 1]) with prog.context(theta_A, theta_B, shift="default") as (p, q): ops.Sgate(sq_r, 0) | q[0] ops.Sgate(sq_r, 0) | q[delay2 + delay1 + 1] ops.Rgate(np.pi / 2) | q[delay2 + delay1 + 1] ops.BSgate(np.pi / 4, np.pi) | (q[delay2 + delay1 + 1], q[0]) ops.BSgate(np.pi / 4, np.pi) | (q[delay2 + delay1], q[0]) ops.BSgate(np.pi / 4, np.pi) | (q[delay1], q[0]) ops.MeasureHomodyne(p[1]) | q[0] ops.MeasureHomodyne(p[0]) | q[delay1] eng = sf.Engine("gaussian") result = eng.run(prog, shots=shots) reshaped_samples = result.samples for sh in range(shots): X_A = reshaped_samples[sh][0][:n // 2] # X samples from detector A P_A = reshaped_samples[sh][0][n // 2:] # P samples from detector A X_B = reshaped_samples[sh][1][:n // 2] # X samples from detector B P_B = reshaped_samples[sh][1][n // 2:] # P samples from detector B # nullifiers defined in https://arxiv.org/pdf/1906.08709.pdf, Eqs. (1) and (2) N = delay2 ntot = len(X_A) - delay2 - 1 nX = np.array([ X_A[k] + X_B[k] - X_A[k + 1] - X_B[k + 1] - X_A[k + N] + X_B[k + N] - X_A[k + N + 1] + X_B[k + N + 1] for k in range(ntot) ]) nP = np.array([ P_A[k] + P_B[k] + P_A[k + 1] + P_B[k + 1] - P_A[k + N] + P_B[k + N] + P_A[k + N + 1] - P_B[k + N + 1] for k in range(ntot) ]) nXvar = np.var(nX) nPvar = np.var(nP) assert np.allclose(nXvar, 4 * sf.hbar * np.exp(-2 * sq_r), rtol=5 / np.sqrt(ntot)) assert np.allclose(nPvar, 4 * sf.hbar * np.exp(-2 * sq_r), rtol=5 / np.sqrt(ntot))
def test_all_passive_gates(hbar, tol): """test that all gates run and do not cause anything to crash""" eng = sf.LocalEngine(backend="gaussian") circuit = sf.Program(4) with circuit.context as q: for i in range(4): ops.Sgate(1, 0.3) | q[i] ops.Rgate(np.pi) | q[0] ops.PassiveChannel(np.ones((2, 2))) | (q[1], q[2]) ops.LossChannel(0.9) | q[1] ops.MZgate(0.25 * np.pi, 0) | (q[2], q[3]) ops.PassiveChannel(np.array([[0.83]])) | q[0] ops.sMZgate(0.11, -2.1) | (q[0], q[3]) ops.Interferometer(np.array([[np.exp(1j * 2)]])) | q[1] ops.BSgate(0.8, 0.4) | (q[1], q[3]) ops.Interferometer(0.5**0.5 * np.fft.fft(np.eye(2))) | (q[0], q[2]) ops.PassiveChannel(0.1 * np.ones((3, 3))) | (q[3], q[1], q[0]) cov = eng.run(circuit).state.cov() circuit = sf.Program(4) with circuit.context as q: ops.Rgate(np.pi) | q[0] ops.PassiveChannel(np.ones((2, 2))) | (q[1], q[2]) ops.LossChannel(0.9) | q[1] ops.MZgate(0.25 * np.pi, 0) | (q[2], q[3]) ops.PassiveChannel(np.array([[0.83]])) | q[0] ops.sMZgate(0.11, -2.1) | (q[0], q[3]) ops.Interferometer(np.array([[np.exp(1j * 2)]])) | q[1] ops.BSgate(0.8, 0.4) | (q[1], q[3]) ops.Interferometer(0.5**0.5 * np.fft.fft(np.eye(2))) | (q[0], q[2]) ops.PassiveChannel(0.1 * np.ones((3, 3))) | (q[3], q[1], q[0]) compiled_circuit = circuit.compile(compiler="passive") T = compiled_circuit.circuit[0].op.p[0] S_sq = np.eye(8, dtype=np.complex128) r = 1 phi = 0.3 for i in range(4): S_sq[i, i] = np.cosh(r) - np.sinh(r) * np.cos(phi) S_sq[i, i + 4] = -np.sinh(r) * np.sin(phi) S_sq[i + 4, i] = -np.sinh(r) * np.sin(phi) S_sq[i + 4, i + 4] = np.cosh(r) + np.sinh(r) * np.cos(phi) cov_sq = (hbar / 2) * S_sq @ S_sq.T mu = np.zeros(8) P = interferometer(T) L = (hbar / 2) * (np.eye(P.shape[0]) - P @ P.T) cov2 = P @ cov_sq @ P.T + L assert np.allclose(cov, cov2, atol=tol, rtol=0)
def test_one_dimensional_cluster_tokyo(): """ One-dimensional temporal-mode cluster state as demonstrated in https://aip.scitation.org/doi/pdf/10.1063/1.4962732 """ np.random.seed(42) sq_r = 5 n = 10 # for an n-mode cluster state shots = 3 # first half of cluster state measured in X, second half in P theta1 = [0] * int(n / 2) + [np.pi / 2] * int( n / 2) # measurement angles for detector A theta2 = theta1 # measurement angles for detector B prog = tdmprogram.TDMProgram(N=[1, 2]) with prog.context(theta1, theta2, shift="default") as (p, q): ops.Sgate(sq_r, 0) | q[0] ops.Sgate(sq_r, 0) | q[2] ops.Rgate(np.pi / 2) | q[0] ops.BSgate(np.pi / 4) | (q[0], q[2]) ops.BSgate(np.pi / 4) | (q[0], q[1]) ops.MeasureHomodyne(p[0]) | q[0] ops.MeasureHomodyne(p[1]) | q[1] eng = sf.Engine("gaussian") result = eng.run(prog, shots=shots) reshaped_samples = result.samples for sh in range(shots): X_A = reshaped_samples[sh][0][:n // 2] # X samples from detector A P_A = reshaped_samples[sh][0][n // 2:] # P samples from detector A X_B = reshaped_samples[sh][1][:n // 2] # X samples from detector B P_B = reshaped_samples[sh][1][n // 2:] # P samples from detector B # nullifiers defined in https://aip.scitation.org/doi/pdf/10.1063/1.4962732, Eqs. (1a) and (1b) ntot = len(X_A) - 1 nX = np.array( [X_A[i] + X_B[i] + X_A[i + 1] - X_B[i + 1] for i in range(ntot)]) nP = np.array( [P_A[i] + P_B[i] - P_A[i + 1] + P_B[i + 1] for i in range(ntot)]) nXvar = np.var(nX) nPvar = np.var(nP) assert np.allclose(nXvar, 2 * sf.hbar * np.exp(-2 * sq_r), rtol=5 / np.sqrt(n)) assert np.allclose(nPvar, 2 * sf.hbar * np.exp(-2 * sq_r), rtol=5 / np.sqrt(n))
def test_program_subroutine(self, setup_eng, tol): """Simple quantum program with a subroutine and references.""" eng, prog = setup_eng(2) # define some gates D = ops.Dgate(0.5) BS = ops.BSgate(0.7 * np.pi, np.pi / 2) R = ops.Rgate(np.pi / 3) def subroutine(a, b): """Subroutine for the quantum program""" R | a BS | (a, b) R.H | a # main program with prog.context as q: # get register references alice, bob = q ops.All(ops.Vacuum()) | (alice, bob) D | alice subroutine(alice, bob) BS | (alice, bob) subroutine(bob, alice) state = eng.run(prog).state # state norm must be invariant if isinstance(eng.backend, BaseFock): assert np.allclose(state.trace(), 1, atol=tol, rtol=0)
def get_unitary(gate_args, device, phi_loop=None, delays=[1, 6, 36]): """Computes the unitary corresponding to a given set of gates Args: gate_args (_type_): dictionary with the collected arguments for squeezing gate, phase gates and beamsplitter gates device (sf.Device): the Borealis device phi_loop (list, optional): list containing the three loop offsets. Defaults to None. delays (list, optional): the delay applied by each loop in time bins. Returns: np.ndarray: a unitary matrix """ args_list = to_args_list(gate_args, device) n, N = get_mode_indices(delays) prog = sf.TDMProgram(N) with prog.context(*args_list) as (p, q): for i in range(len(delays)): ops.Rgate(p[2 * i + 1]) | q[n[i]] ops.BSgate(p[2 * i + 2], np.pi / 2) | (q[n[i]], q[n[i + 1]]) if phi_loop is not None: ops.Rgate(phi_loop[i]) | q[n[i]] prog.space_unroll() prog = prog.compile(compiler="passive") # unitary matrix assert prog.circuit U = prog.circuit[0].op.p[0] return U
def setup_two_mode_circuit(self, setup_eng, cutoff): """Create the circuit for following tests""" eng_ref, q = setup_eng(2) S = ops.Sgate(2) B = ops.BSgate(2.234, -1.165) initial_state = np.complex64( np.random.rand(*[cutoff] * 4) + 1j * np.random.rand(*[cutoff] * 4)) with eng_ref: ops.DensityMatrix(initial_state) | q S | q[0] B | q S | q[1] B | q eng, q = sf.Engine(2) with eng: S | q[0] B | q S | q[1] B | q rho = eng_ref.run().dm() return eng, rho, initial_state
def test_unroll_shots(self): """Test unrolling program several times using different number of shots.""" n = 2 shots = 2 prog = sf.TDMProgram(N=2) with prog.context([0] * n, [0] * n, [0] * n) as (p, q): ops.Sgate(0.5643, 0) | q[1] ops.BSgate(p[0]) | (q[1], q[0]) ops.Rgate(p[1]) | q[1] ops.MeasureHomodyne(p[2]) | q[0] prog_length = len(prog.circuit) assert prog_length == 4 prog.unroll(shots=shots) assert len(prog.circuit) == n * shots * prog_length # unroll once more with the same number of shots to cover caching prog.unroll(shots=shots) assert len(prog.circuit) == n * shots * prog_length # unroll once more with a different number of shots shots = 3 prog.unroll(shots=shots) assert len(prog.circuit) == n * shots * prog_length prog.roll() assert len(prog.circuit) == prog_length
def test_not_drawable(self, tmpdir): eng, q = sf.Engine(3) with eng: ops.BSgate(0, 2) | (q[0], q[2]) with pytest.raises(NotDrawableException): eng.draw_circuit(print_queued_ops=True, tex_dir=tmpdir)
def test_not_drawable(self, tmpdir): prog = sf.Program(3) with prog.context as q: ops.BSgate(0, 2) | (q[0], q[2]) with pytest.raises(NotDrawableException): prog.draw_circuit(tex_dir=tmpdir)
def test_S2gate_decomp_equal(self, setup_eng, r, tol): """Tests that the S2gate gives the same transformation as its decomposition.""" eng, prog = setup_eng(2) r = 0.25 phi = np.pi / 5 with prog.context as q: ops.S2gate(r, phi) | q # run decomposition with reversed arguments ops.BSgate(np.pi / 4, 0) | q ops.Sgate(r, phi + np.pi) | q[0] ops.Sgate(r, phi) | q[1] ops.BSgate(-np.pi / 4, 0) | q eng.run(prog) assert np.all(eng.backend.is_vacuum(tol))
def test_checkpoints(self, setup_eng, tol): """Test history checkpoints work when creating and deleting modes.""" eng, q = setup_eng(2) alice, bob = q # define some gates D = ops.Dgate(0.5) BS = ops.BSgate(2 * np.pi, np.pi / 2) R = ops.Rgate(np.pi) with eng: D | alice BS | (alice, bob) ops.Del | alice R | bob charlie, = ops.New(1) BS | (bob, charlie) ops.MeasureX | bob ops.Del | bob D.H | charlie ops.MeasureX | charlie eng.optimize() state = eng.run() # state norm must be invariant if isinstance(eng.backend, BaseFock): assert np.allclose(state.trace(), 1, atol=tol, rtol=0) def check_reg(self, expected_n=None): """Compare Engine.register with the mode list returned by the backend. They should always be in agreement after Engine.run(), Engine.reset_queue() and Engine.reset(). """ rr = eng.register modes = eng.backend.get_modes() # number of elements assert len(rr) == len(modes) if expected_n is not None: assert len(rr) == expected_n # check indices match assert np.all([r.ind for r in rr] == modes) # activity assert np.all([r.active for r in rr]) # check that reset() works check_reg(1) eng.reset() new_reg = eng.register # original number of modes assert len(new_reg) == len(q) # the regrefs are reset as well assert np.all([r.val is None for r in new_reg]) check_reg(2)
def test_neq_operator(self): """Not-equal operator check.""" prog_1 = sf.Program(2) prog_2 = sf.Program(2) with prog_1.context as q: ops.Sgate(0.2) | q[0] ops.BSgate() | (q[0], q[1]) ops.MeasureFock() | q[1] with prog_2.context as q: ops.Sgate(4.2) | q[0] ops.BSgate() | (q[0], q[1]) ops.MeasureFock() | q[1] # should NOT be equal assert prog_1 != prog_2 assert prog_2 != prog_1
def test_delays_tdmprogram_with_nested_loops(self): """Test that error is raised when calculating the delays of a TDM program with nested loops""" gate_args = [random_bs(8), random_bs(8)] # a program with nested delays prog = TDMProgram(N=4) with prog.context(*gate_args) as (p, q): ops.Sgate(0.4, 0) | q[3] ops.BSgate(p[0]) | (q[0], q[3]) ops.BSgate(p[1]) | (q[2], q[1]) ops.MeasureX | q[0] with pytest.raises( NotImplementedError, match="Calculating delays for programs with nested loops is not implemented.", ): prog.get_delays()
def test_generate_code_no_engine(self): """Test generating code for a regular program with no engine""" prog = sf.Program(3) with prog.context as q: ops.Sgate(0.54, 0) | q[0] ops.BSgate(0.45, np.pi / 2) | (q[0], q[2]) ops.Sgate(3 * np.pi / 2, 0) | q[1] ops.BSgate(2 * np.pi, 0.62) | (q[0], q[1]) ops.MeasureFock() | q[0] code = io.generate_code(prog) code_list = code.split("\n") expected = prog_txt_no_engine.split("\n") for i, row in enumerate(code_list): assert row == expected[i]
def test_CXgate_decomp_equal(self, setup_eng, s, tol): """Tests that the CXgate gives the same transformation as its decomposition.""" eng, prog = setup_eng(2) r = np.arcsinh(-s / 2) y = -1 / np.cosh(r) x = -np.tanh(r) theta = np.arctan2(y, x) / 2 with prog.context as q: ops.CXgate(s) | q # run decomposition with reversed arguments ops.BSgate(-(np.pi / 2 + theta), 0) | q ops.Sgate(r, np.pi) | q[0] ops.Sgate(r, 0) | q[1] ops.BSgate(-theta, 0) | q eng.run(prog) assert np.all(eng.backend.is_vacuum(tol))
def test_operation_before_squeezing(self): """Test error is raised when an operation is passed before the S2gates""" prog = sf.Program(2) with prog.context as q: ops.BSgate() | (q[0], q[1]) ops.S2gate(SQ_AMPLITUDE) | (q[0], q[1]) ops.MeasureFock() | q with pytest.raises(CircuitError, match="There can be no operations before the S2gates."): prog.compile("Xunitary")
def test_print_commands(self, eng, prog): """Program.print and Engine.print_applied return correct strings.""" prog = sf.Program(2) # store the result of the print command in list res res = [] # use a print function that simply appends the operation # name to the results list print_fn = lambda x: res.append(x.__str__()) # prog should now be empty prog.print(print_fn) assert res == [] # define some gates D = ops.Dgate(0.5) BS = ops.BSgate(2 * np.pi, np.pi / 2) R = ops.Rgate(np.pi) with prog.context as q: alice, bob = q D | alice BS | (alice, bob) ops.Del | alice R | bob charlie, = ops.New(1) BS | (bob, charlie) ops.MeasureX | bob ops.Dgate(bob.par).H | charlie ops.Del | bob ops.MeasureX | charlie res = [] prog.print(print_fn) expected = [ "Dgate(0.5, 0) | (q[0])", "BSgate(6.283, 1.571) | (q[0], q[1])", "Del | (q[0])", "Rgate(3.142) | (q[1])", "New(1)", "BSgate(6.283, 1.571) | (q[1], q[2])", "MeasureX | (q[1])", "Dgate(q1, 0).H | (q[2])", "Del | (q[1])", "MeasureX | (q[2])", ] assert res == expected # NOTE optimization can change gate order result = eng.run(prog, compile_options={'optimize': False}) res = [] eng.print_applied(print_fn) assert res == ["Run 0:"] + expected
def test_eq_symmetric_bsgate(self, compare_params): """Mode order doesn't matter in programs with symmetric beamsplitter.""" prog_1 = sf.Program(2) prog_2 = sf.Program(2) with prog_1.context as q: ops.Fock(2) | q[0] ops.BSgate(np.pi / 4, np.pi / 2) | (q[0], q[1] ) # symmetric beamsplitter ops.MeasureFock() | q[1] with prog_2.context as q: ops.Fock(2) | q[0] ops.BSgate(np.pi / 4, np.pi / 2) | (q[1], q[0] ) # symmetric beamsplitter ops.MeasureFock() | q[1] # should be equivalent assert prog_1.equivalence(prog_2, compare_params=compare_params) assert prog_2.equivalence(prog_1, compare_params=compare_params)
def test_two_mode_gate(self): """Test two mode gate converts""" sf_prog = Program(4) with sf_prog.context as q: ops.BSgate(0.54, -0.324) | (q[3], q[0]) xir_prog = io.to_xir(sf_prog) expected = [("BSgate", [0.54, -0.324], (3, 0))] assert [(stmt.name, stmt.params, stmt.wires) for stmt in xir_prog.statements] == expected
def test_equivalence_same_prog(self, prog, compare_params): """Same program equivalence.""" assert prog.equivalence(prog) with prog.context as q: ops.Fock(2) | q[0] ops.BSgate() | (q[0], q[1]) ops.MeasureFock() | q[1] # should be equivalent assert prog.equivalence(prog, compare_params=compare_params)
def test_extract_arbitrary_unitary_two_modes_vectorized( self, setup_eng, cutoff, batch_size, tol): """Test that arbitrary unitary extraction works for 2 mode when vectorized""" bsize = 1 if batch_size is not None: bsize = batch_size S = ops.Sgate(0.4, -1.2) B = ops.BSgate(2.234, -1.165) # not a state but it doesn't matter initial_state = np.complex64( np.random.rand(cutoff, cutoff) + 1j * np.random.rand(cutoff, cutoff)) eng_ref, p0 = setup_eng(2) with p0.context as q: ops.Ket(initial_state) | q prog = sf.Program(p0) with prog.context as q: S | q[0] B | q S | q[1] B | q U = utils.extract_unitary( prog, cutoff_dim=cutoff, vectorize_modes=True, backend=eng_ref.backend_name, ) if isinstance(U, tf.Tensor): U = U.numpy() final_state = U @ initial_state.reshape([-1]) expected_state = eng_ref.run([p0, prog]).state.ket() if isinstance(expected_state, tf.Tensor): expected_state = expected_state.numpy() if expected_state.shape[ 0] == bsize: # the Fock backend does not support batching! for exp_state in expected_state: assert np.allclose(final_state, exp_state.reshape([-1]), atol=tol, rtol=0) else: assert np.allclose(final_state, expected_state.reshape([-1]), atol=tol, rtol=0)
def test_neq_operator_equivalent(self): """Not-equal operator check with equivalent circuit.""" prog_1 = sf.Program(3) prog_2 = sf.Program(3) with prog_1.context as q: ops.Sgate(0.2) | q[0] ops.BSgate() | (q[0], q[1]) ops.Sgate(0.2) | q[2] ops.MeasureFock() | q[1] with prog_2.context as q: ops.Sgate(0.2) | q[0] ops.Sgate(0.2) | q[2] # moved up one step ops.BSgate() | (q[0], q[1]) ops.MeasureFock() | q[1] # should NOT be equal assert prog_1 != prog_2 assert prog_2 != prog_1
def test_equivalence_different_circuits(self, compare_params): """Programs with different, but equivalent, circuits.""" prog_1 = sf.Program(3) prog_2 = sf.Program(3) with prog_1.context as q: ops.Sgate(0.2) | q[0] ops.BSgate() | (q[0], q[1]) ops.Sgate(0.2) | q[2] ops.MeasureFock() | q[1] with prog_2.context as q: ops.Sgate(0.2) | q[0] ops.Sgate(0.2) | q[2] # moved up one step, but still equivalent ops.BSgate() | (q[0], q[1]) ops.MeasureFock() | q[1] # should be equivalent assert prog_1.equivalence(prog_2, compare_params=compare_params) assert prog_2.equivalence(prog_1, compare_params=compare_params)
def test_GBS_compile_nonconsec_measurefock(self): """Tests that GBS compilation fails when Fock measurements are made with an intervening gate.""" prog = sf.Program(2) with prog.context as q: ops.Dgate(1.0) | q[0] ops.MeasureFock() | q[0] ops.Dgate(-1.0) | q[1] ops.BSgate(-0.5, 2.0) | q # intervening gate ops.MeasureFock() | q[1] with pytest.raises(program.CircuitError, match="The Fock measurements are not consecutive."): prog.compile('gbs')
def test_subsystems(self, setup_eng, tol): """Check that the backend keeps in sync with the program when creating and deleting modes.""" null = sf.Program(2) # empty program eng, prog = setup_eng(2) # define some gates D = ops.Dgate(0.5) BS = ops.BSgate(2 * np.pi, np.pi / 2) R = ops.Rgate(np.pi) with prog.context as q: alice, bob = q D | alice BS | (alice, bob) ops.Del | alice R | bob charlie, = ops.New(1) BS | (bob, charlie) ops.MeasureX | bob ops.Del | bob D.H | charlie ops.MeasureX | charlie def check_reg(p, expected_n=None): """Compare Program.register with the mode list returned by the backend. They should always be in agreement after Engine.run() and Engine.reset(). """ rr = p.register modes = eng.backend.get_modes() # number of elements assert len(rr) == len(modes) if expected_n is not None: assert len(rr) == expected_n # check indices match assert np.all([r.ind for r in rr] == modes) # activity assert np.all([r.active for r in rr]) state = eng.run(null) check_reg(null, 2) state = eng.run(prog).state check_reg(prog, 1) # state norm must be invariant if isinstance(eng.backend, BaseFock): assert np.allclose(state.trace(), 1, atol=tol, rtol=0) # check that reset() works eng.reset() # the regrefs are reset as well assert np.all([r.val is None for r in prog.register])
def test_checkpoints(self, backend): """Test history checkpoints work when creating and deleting modes.""" eng, q = sf.Engine(2) alice, bob = q # define some gates D = ops.Dgate(0.5) BS = ops.BSgate(2 * np.pi, np.pi / 2) R = ops.Rgate(np.pi) with eng: D | alice BS | (alice, bob) ops.Del | alice R | bob charlie, = ops.New(1) BS | (bob, charlie) ops.MeasureX | bob ops.Del | bob D.H | charlie ops.MeasureX | charlie eng.optimize() eng.run(backend) assert not alice.active assert charlie.active # check that reset_queue() restores the latest RegRef checkpoint # (created by eng.run() above) eng.reset_queue() assert not alice.active assert charlie.active with eng: diana, = ops.New(1) ops.Del | charlie assert not charlie.active assert diana.active eng.reset_queue() assert charlie.active assert not diana.active eng.reset() new_reg = eng.register # original number of modes assert len(new_reg) == len(q) # the regrefs are reset as well assert np.all([r.val is None for r in new_reg])
def test_print_commands(self, backend): """Test print_queue and print_applied returns correct strings.""" eng, q = sf.Engine(2) # define some gates D = ops.Dgate(0.5) BS = ops.BSgate(2 * np.pi, np.pi / 2) R = ops.Rgate(np.pi) # get register references alice, bob = q with eng: D | alice BS | (alice, bob) ops.Del | alice R | bob charlie, = ops.New(1) BS | (bob, charlie) ops.MeasureX | bob ops.Dgate(bob).H | charlie ops.Del | bob ops.MeasureX | charlie res = [] print_fn = lambda x: res.append(x.__str__()) eng.print_queue(print_fn) expected = [ "Dgate(0.5, 0) | (q[0])", "BSgate(6.283, 1.571) | (q[0], q[1])", "Del | (q[0])", "Rgate(3.142) | (q[1])", "New(1) ", "BSgate(6.283, 1.571) | (q[1], q[2])", "MeasureX | (q[1])", "Dgate(RR(q[1]), 0).H | (q[2])", "Del | (q[1])", "MeasureX | (q[2])", ] assert res == expected state = eng.run(backend) # queue should now be empty res = [] eng.print_queue(print_fn) assert res == [] # print_applied should now not be empty res = [] eng.print_applied(print_fn) assert res == ["Run 0:"] + expected