def test_gkp_logical(self, eps): r"""Checks that logically equivalent GKP states have Wigner functions that agree.""" x = np.linspace(-3 * np.sqrt(np.pi), 3 * np.sqrt(np.pi), 40) p = np.copy(x) # Prepare GKP 0 and apply Hadamard prog_0H = sf.Program(1) with prog_0H.context as q0: sf.ops.GKP(epsilon=eps) | q0[0] sf.ops.Rgate(np.pi / 2) | q0[0] backend_0H = bosonic.BosonicBackend() backend_0H.run_prog(prog_0H) state_0H = backend_0H.state() wigner_0H = state_0H.wigner(0, x, p) # Prepare GKP + prog_plus = sf.Program(1) with prog_plus.context as qp: sf.ops.GKP(state=[np.pi / 2, 0], epsilon=eps) | qp[0] backend_plus = bosonic.BosonicBackend() backend_plus.run_prog(prog_plus) state_plus = backend_plus.state() wigner_plus = state_plus.wigner(0, x, p) assert np.allclose(wigner_0H, wigner_plus)
def test_cat_real_cutoff_and_D(self, alpha, phi): r"""Checks that for the real cat state representation a low cutoff and low D produce fewer weights when alpha !=0.""" prog = sf.Program(1) with prog.context as q: sf.ops.Catstate(alpha, phi, representation="real", cutoff=1e-6, D=1) | q[0] backend = bosonic.BosonicBackend() backend.run_prog(prog) state = backend.state() num_weights_low = state.num_weights prog = sf.Program(1) with prog.context as q: sf.ops.Catstate(alpha, phi, representation="real", cutoff=1e-6, D=10) | q[0] backend = bosonic.BosonicBackend() backend.run_prog(prog) state = backend.state() num_weights_high = state.num_weights assert np.allclose(sum(state.weights()), 1) assert state.means().shape == (num_weights_high, 2) assert state.covs().shape == (num_weights_high, 2, 2) if alpha != 0: assert num_weights_low < num_weights_high else: assert num_weights_low == num_weights_high
def test_complex_weight(self, alpha): r"""Checks that Bosonic creates a state with user-specifed weights, means and covariances.""" dummy_backend = bosonic.BosonicBackend() dummy_backend.begin_circuit(1) # Get weights, means and covs for a cat state weights, means, covs = dummy_backend.prepare_cat(alpha, 0, "complex", 0, 0) # Initiate the state, but use Bosonic method backend = bosonic.BosonicBackend() prog = sf.Program(1) with prog.context as q1: sf.ops.Bosonic(weights, means, covs) | q1[0] backend.run_prog(prog) state = backend.state() # Prepare using the Catstate method backend2 = bosonic.BosonicBackend() prog2 = sf.Program(1) with prog2.context as q2: sf.ops.Catstate(alpha) | q2[0] backend2.run_prog(prog2) state2 = backend2.state() # Check the two states are equal assert state.__eq__(state2)
def test_gkp(self, eps): r"""Checks the GKP state weights, means and covariances are correct.""" prog = sf.Program(1) with prog.context as q: sf.ops.GKP(epsilon=eps) | q[0] backend = bosonic.BosonicBackend() backend.run_prog(prog) state = backend.state() num_weights = state.num_weights assert state.weights().shape == (num_weights,) assert np.allclose(sum(state.weights()), 1) assert state.means().shape == (num_weights, 2) assert state.covs().shape == (num_weights, 2, 2) # Weights, means and covs should be real assert np.allclose(state.weights().real, state.weights()) assert np.allclose(state.means().real, state.means()) assert np.allclose(state.covs().real, state.covs()) # Covariance should be diagonal with these entries cov_val = (1 - np.exp(-2 * eps)) / (1 + np.exp(-2 * eps)) * sf.hbar / 2 covs_compare = np.tile(cov_val * np.eye(2), (num_weights, 1, 1)) assert np.allclose(state.covs(), covs_compare) # Means should be integer multiples of sqrt(pi*hbar)/2 times a damping damping = 2 * np.exp(-eps) / (1 + np.exp(-2 * eps)) mean_ints = state.means() / (damping * np.sqrt(np.pi * sf.hbar) / 2) # Round to make sure numerical errors are washed out mean_ints = np.round(np.real_if_close(mean_ints), 10) assert np.allclose(mean_ints % 1, np.zeros(mean_ints.shape))
def test_cat_state_wigners(self, alpha, phi, representation): r"""Checks that the real and complex cat state representations have the same Wigner functions as the cat state from the Fock backend.""" x = np.linspace(-2 * alpha, 2 * alpha, 100) p = np.linspace(-2 * alpha, 2 * alpha, 100) prog_complex_cat = sf.Program(1) with prog_complex_cat.context as qb: sf.ops.Catstate(alpha, phi, representation=representation) | qb[0] backend_bosonic = bosonic.BosonicBackend() backend_bosonic.run_prog(prog_complex_cat) wigner_bosonic = backend_bosonic.state().wigner(0, x, p) prog_cat_fock = sf.Program(1) with prog_cat_fock.context as qf: if alpha != 0: sf.ops.Catstate(alpha, phi) | qf[0] else: sf.ops.Vacuum() | qf[0] eng = sf.Engine("fock", backend_options={"cutoff_dim": 20}) results = eng.run(prog_cat_fock) wigner_fock = results.state.wigner(0, x, p) assert np.allclose(wigner_bosonic, wigner_fock, rtol=1e-3, atol=1e-6)
def test_fock(self, n): r"""Checks fock states in the bosonic representation.""" prog = sf.Program(1) with prog.context as q: sf.ops.Fock(n) | q[0] backend = bosonic.BosonicBackend() backend.run_prog(prog) state = backend.state() # Check shapes assert state.num_weights == n + 1 assert state.weights().shape == (n + 1,) assert np.allclose(sum(state.weights()), 1) assert state.means().shape == (n + 1, 2) assert state.covs().shape == (n + 1, 2, 2) # Check mean photon is close to n mean, var = state.mean_photon(0) assert np.allclose(mean, n, atol=r_fock) assert np.allclose(var, 0, atol=r_fock) # Weights, means and covs should be real assert np.allclose(state.weights().real, state.weights()) assert np.allclose(state.means().real, state.means()) assert np.allclose(state.covs().real, state.covs()) if n == 0: covs_compare = np.tile(np.eye(2) * sf.hbar / 2, (1, 1, 1)) assert np.allclose(state.covs(), covs_compare) assert np.allclose(state.fidelity_vacuum(), 1)
def test_different_preparations(self, alpha, r): """Runs a program with non-Gaussian and Gaussian preparations.""" prog = sf.Program(3) with prog.context as q: sf.ops.Catstate(alpha) | q[0] sf.ops.Squeezed(r) | q[1] backend = bosonic.BosonicBackend() backend.run_prog(prog) state = backend.state() # Check output shapes if alpha != 0: assert state.weights().shape == (4,) assert np.allclose(sum(state.weights()), 1) assert state.means().shape == (4, 6) assert state.covs().shape == (4, 6, 6) else: assert state.weights().shape == (1,) assert np.allclose(sum(state.weights()), 1) assert state.means().shape == (1, 6) assert state.covs().shape == (1, 6, 6) # Check covariance is correct cov_compare = np.diag([1, 1, np.exp(-2 * r), np.exp(2 * r), 1, 1]) covs_compare = np.tile(cov_compare * sf.hbar / 2, (4, 1, 1)) assert np.allclose(state.covs(), covs_compare)
def test_cat_state_parity(self, alpha): r"""Checks that the real and complex cat state representations yield the correct parity.""" # for phi = 0, should yield parity of 1 prog_complex_cat = sf.Program(1) with prog_complex_cat.context as qc: sf.ops.Catstate(alpha) | qc[0] prog_real_cat = sf.Program(1) with prog_real_cat.context as qr: sf.ops.Catstate(alpha, representation="real") | qr[0] backend_complex = bosonic.BosonicBackend() backend_complex.run_prog(prog_complex_cat) state_complex = backend_complex.state() parity_complex = state_complex.parity_expectation([0]) backend_real = bosonic.BosonicBackend() backend_real.run_prog(prog_real_cat) state_real = backend_real.state() parity_real = state_real.parity_expectation([0]) assert np.allclose(parity_complex, 1) assert np.allclose(parity_real, 1) # for phi = 1, should yield parity of -1 unless alpha == 0 if alpha != 0: prog_complex_cat = sf.Program(1) with prog_complex_cat.context as qc: sf.ops.Catstate(alpha, 1) | qc[0] prog_real_cat = sf.Program(1) with prog_real_cat.context as qr: sf.ops.Catstate(alpha, 1, representation="real") | qr[0] backend_complex = bosonic.BosonicBackend() backend_complex.run_prog(prog_complex_cat) state_complex = backend_complex.state() parity_complex = state_complex.parity_expectation([0]) backend_real = bosonic.BosonicBackend() backend_real.run_prog(prog_real_cat) state_real = backend_real.state() parity_real = state_real.parity_expectation([0]) assert np.allclose(parity_complex, -1) assert np.allclose(parity_real, -1)
def test_raised_errors(self): """Runs programs with operations that are not applicable or have not been implemented.""" from strawberryfields.backends.base import NotApplicableError prog = sf.Program(1) with prog.context as q: sf.ops.Kgate(1) | q[0] backend = bosonic.BosonicBackend() with pytest.raises(NotApplicableError): backend.run_prog(prog) prog = sf.Program(1) with prog.context as q: sf.ops.MeasureThreshold() | q[0] backend = bosonic.BosonicBackend() with pytest.raises(NotImplementedError): backend.run_prog(prog)
def test_fock_state_parity(self, n): r"""Checks that fock states have the right parity.""" prog = sf.Program(1) with prog.context as q: sf.ops.Fock(n) | q[0] backend = bosonic.BosonicBackend() backend.run_prog(prog) state = backend.state() assert np.allclose(state.parity_expectation([0]), (-1.0) ** n, atol=r_fock)
def test_non_initial_prep_error(self): """Tests that more than one non-Gaussian state preparations in the same mode raises an error.""" prog = sf.Program(1) with prog.context as q: sf.ops.Catstate() | q[0] sf.ops.GKP() | q[0] backend = bosonic.BosonicBackend() with pytest.raises(NotImplementedError): backend.run_prog(prog)
def test_gkp_state_parity(self, eps): r"""Checks that GKP states yield the right parity.""" prog = sf.Program(1) with prog.context as q: sf.ops.GKP(epsilon=eps) | q[0] backend = bosonic.BosonicBackend() backend.run_prog(prog) state = backend.state() assert np.allclose(state.parity_expectation([0]), 1, atol=r_fock)
def test_gkp_complex(self): r"""Checks that trying to call a complex representation raises an error. This test can be updated once the complex representation is implemented.""" prog = sf.Program(1) with prog.context as q: sf.ops.GKP(representation="complex") | q[0] backend = bosonic.BosonicBackend() with pytest.raises(NotImplementedError): backend.run_prog(prog)
def test_g2_threshold(self): r"""Tests that the g^2 of a single photon is zero""" num_repeats = 50 for _ in range(num_repeats): prog = sf.Program(2) with prog.context as q: sf.ops.Fock(1) | q[0] sf.ops.BSgate() | (q[0], q[1]) sf.ops.MeasureThreshold() | (q[0], q[1]) backend = bosonic.BosonicBackend() results = backend.run_prog(prog) _, results = backend.run_prog(prog) res0, res1 = results[0][0][0], results[1][0][0] assert ([res0, res1] == [0, 1]) or ([res0, res1] == [1, 0])
def test_fock_threshold(self, n): r"""Tests that Fock states n > 0 always yield a click.""" num_repeats = 50 for _ in range(num_repeats): prog = sf.Program(1) with prog.context as q: sf.ops.Fock(n) | q[0] sf.ops.MeasureThreshold() | q[0] backend = bosonic.BosonicBackend() _, results = backend.run_prog(prog) res0 = results[0][0][0] if n == 0: assert res0 == 0 else: assert res0 == 1
def test_hong_ou_mandel_threshold(self): r"""Tests Hong-Ou-Mandel interference""" num_repeats = 50 for _ in range(num_repeats): prog = sf.Program(2) with prog.context as q: sf.ops.Fock(1) | q[0] sf.ops.Fock(1) | q[1] sf.ops.BSgate() | (q[0], q[1]) sf.ops.MeasureThreshold() | (q[0], q[1]) backend = bosonic.BosonicBackend() results = backend.run_prog(prog) _, results = backend.run_prog(prog) res0, res1 = results[0][0][0], results[1][0][0] assert ([res0, res1] == [0, 1]) or ([res0, res1] == [1, 0])
def test_measurement_many_shots(self, alpha, shots): """Runs a program with measurements.""" prog = sf.Program(1) with prog.context as q: sf.ops.Catstate(alpha) | q[0] sf.ops.MeasureHomodyne(0) | q[0] backend = bosonic.BosonicBackend() applied, samples, all_samples = backend.run_prog(prog, shots=shots) state = backend.state() # Check output is vacuum since everything was measured assert np.allclose(state.fidelity_vacuum(), 1) # Check samples assert 0 in samples.keys() assert samples[0].shape == (int(shots),)
def test_cat_state_parity(self, alpha, phi, representation, p, expected_parity): r"""Checks that the real and complex cat state representations yield the correct parity.""" prog_cat = sf.Program(1) with prog_cat.context as q: sf.ops.Catstate(alpha, phi, p, representation) | q[0] backend = bosonic.BosonicBackend() backend.run_prog(prog_cat) state = backend.state() parity = state.parity_expectation([0]) # for p = 0, should yield parity of 1 # for p = 1, should yield parity of -1 unless alpha == 0 if not (p == 1 and alpha == 0): assert np.allclose(parity, expected_parity)
def test_measurement(self, alpha, r): """Runs a program with measurements.""" prog = sf.Program(2) with prog.context as q: sf.ops.Catstate(alpha) | q[0] sf.ops.Squeezed(r) | q[1] sf.ops.MeasureX | q[0] sf.ops.MeasureHD | q[1] backend = bosonic.BosonicBackend() applied, samples, all_samples = backend.run_prog(prog) state = backend.state() # Check output is vacuum since everything was measured assert np.allclose(state.fidelity_vacuum(), 1) # Check samples for i in range(2): assert i in samples.keys() assert samples[i].shape == (1,)
def test_fock_state_wigners(self, n): r"""Checks that fock state Wigner functions in the bosonic and Fock backends match.""" x = np.linspace(-n, n, 100) p = np.linspace(-n, n, 100) prog = sf.Program(1) with prog as q: sf.ops.Fock(n) | q[0] backend = bosonic.BosonicBackend() backend.run_prog(prog) wigner_bosonic = backend.state().wigner(0, x, p) eng = sf.Engine("fock", backend_options={"cutoff_dim": int(n + 1)}) results = eng.run(prog) wigner_fock = results.state.wigner(0, x, p) assert np.allclose(wigner_fock, wigner_bosonic, atol=r_fock)
def test_cat_real(self, alpha, phi): r"""Checks the real cat state representation produces real weights, means and covariances.""" prog = sf.Program(1) with prog.context as q: sf.ops.Catstate(alpha, phi, representation="real", cutoff=1e-6, D=1) | q[0] backend = bosonic.BosonicBackend() backend.run_prog(prog) state = backend.state() num_weights = state.num_weights assert np.allclose(sum(state.weights()), 1) assert state.means().shape == (num_weights, 2) assert state.covs().shape == (num_weights, 2, 2) # Weights, means and covs should be real assert np.allclose(state.weights().real, state.weights()) assert np.allclose(state.means().real, state.means()) assert np.allclose(state.covs().real, state.covs())
def test_mb_gates(self, alpha, r): """Runs a program with measurement-based gates.""" prog = sf.Program(2) with prog.context as q: sf.ops.MSgate(1, 0, 1, 1, avg=True) | q[0] sf.ops.MSgate(1, 0, 1, 1, avg=False) | q[1] sf.ops.MSgate(1, 0, 1, 1, avg=False) | q[1] backend = bosonic.BosonicBackend() applied, samples, all_samples = backend.run_prog(prog) # Check number of active modes did not change assert backend.get_modes() == [0, 1] # Check samples for the data modes are empty assert samples == {} # Check ancilla samples exist for the second mode ancilla_samples = backend.ancillae_samples_dict assert 1 in ancilla_samples.keys() assert len(ancilla_samples[1]) == 2
def test_add_new_mode(self, alpha): """Tests adding a new mode in a program context.""" prog = sf.Program(1) with prog.context as q: sf.ops.Vacuum() | q[0] q = q + (sf.ops.New(n=1),) sf.ops.Catstate(alpha) | q[1] backend = bosonic.BosonicBackend() backend.run_prog(prog) state = backend.state() # Check output shapes if alpha != 0: assert state.weights().shape == (4,) assert np.allclose(sum(state.weights()), 1) assert state.means().shape == (4, 4) assert state.covs().shape == (4, 4, 4) else: assert state.weights().shape == (1,) assert np.allclose(sum(state.weights()), 1) assert state.means().shape == (1, 4) assert state.covs().shape == (1, 4, 4)
def test_cat_threshold(self): r"""Tests threshold measurement applied to a cat Bell state.""" # For large alpha, a beamsplitter effectively creates a cat Bell pair. # The Bell pair is such that when there is vacuum in one mode, there is a # cat state with large alpha (i.e. non-vacuum) in the other mode. # This means the threshold outcomes should always be anti-correlated. alpha = 4 phi = 0 num_repeats = 50 for _ in range(num_repeats): prog = sf.Program(2) with prog.context as q: sf.ops.Catstate(alpha, phi) | q[0] sf.ops.Catstate(alpha, phi) | q[1] sf.ops.BSgate() | (q[0], q[1]) sf.ops.MeasureThreshold() | q[0] sf.ops.MeasureThreshold() | q[1] backend = bosonic.BosonicBackend() _, results = backend.run_prog(prog) res0, res1 = results[0][0][0], results[1][0][0] assert ([res0, res1] == [0, 1]) or ([res0, res1] == [1, 0])
def test_init_circuit(self, alpha, r): """Checks init_circuit only prepares non-Gaussian states and vacuum.""" prog = sf.Program(3) with prog.context as q: sf.ops.Catstate(alpha) | q[0] # this line should have no effect since it is a Gaussian prep # that would be picked up in run_prog, but not init_circuit sf.ops.Squeezed(r) | q[1] backend = bosonic.BosonicBackend() backend.init_circuit(prog) state = backend.state() # Check that second and third mode are still in vacuum weights, means, covs = state.reduced_bosonic([1, 2]) mean_compare = np.zeros(4) means_compare = np.tile(mean_compare, (4, 1)) assert np.allclose(means, means_compare) cov_compare = np.diag([1, 1, 1, 1]) covs_compare = np.tile(cov_compare * sf.hbar / 2, (4, 1, 1)) assert np.allclose(covs, covs_compare)
def test_cat_marginal(self, alpha): """Tests marginal method in BaseBosonicState.""" prog = sf.Program(1) with prog.context as q: sf.ops.Catstate(alpha) | q backend = bosonic.BosonicBackend() backend.run_prog(prog) state = backend.state() scale = np.sqrt(sf.hbar) x = np.linspace(-6, 6, 1000) * scale marginal = state.marginal(0, x) # Calculate the wavefunction directly disp = np.sqrt(2 * sf.hbar) * alpha norm = 1 / np.sqrt(2 + 2 * np.exp(-2 * abs(alpha)**2)) psi = np.exp(-((x - disp)**2) / (2 * sf.hbar)) psi += np.exp(-((x + disp)**2) / (2 * sf.hbar)) psi *= norm / (np.pi * sf.hbar)**0.25 assert np.allclose(marginal, abs(psi)**2)
def test_cat_complex(self, alpha, phi): r"""Checks the complex cat state representation.""" prog = sf.Program(1) with prog.context as q: sf.ops.Catstate(alpha, phi) | q[0] backend = bosonic.BosonicBackend() backend.run_prog(prog) state = backend.state() if alpha != 0: # Check shapes assert state.num_weights == 4 assert state.weights().shape == (4,) assert np.allclose(sum(state.weights()), 1) assert state.means().shape == (4, 2) assert state.covs().shape == (4, 2, 2) covs_compare = np.tile(np.eye(2) * sf.hbar / 2, (4, 1, 1)) assert np.allclose(state.covs(), covs_compare) # Weights should be real if phi is an integer if phi % 1 == 0: assert np.allclose(state.weights().real, state.weights()) else: assert not np.allclose(state.weights().real, state.weights()) # Covs should be real, means complex assert not np.allclose(state.means().real, state.means()) assert np.allclose(state.covs().real, state.covs()) else: assert state.num_weights == 1 assert state.weights().shape == (1,) assert np.allclose(sum(state.weights()), 1) assert state.means().shape == (1, 2) assert state.covs().shape == (1, 2, 2) covs_compare = np.tile(np.eye(2) * sf.hbar / 2, (1, 1, 1)) assert np.allclose(state.covs(), covs_compare)