def test_decomposition(self, tol): """Test that an graph is correctly decomposed""" n = 3 prog = sf.Program(n) A = np.random.random([n, n]) + 1j * np.random.random([n, n]) A += A.T A -= np.trace(A) * np.identity(n) / n sq, U = dec.graph_embed(A) G = ops.GraphEmbed(A) cmds = G.decompose(prog.register) assert np.all(sq == G.sq) assert np.all(U == G.U) S = np.identity(2 * n) # calculating the resulting decomposed symplectic for cmd in cmds: # all operations should be BSgates, Rgates, or Sgates assert isinstance( cmd.op, (ops.Interferometer, ops.BSgate, ops.Rgate, ops.Sgate) ) # build up the symplectic transform modes = [i.ind for i in cmd.reg] if isinstance(cmd.op, ops.Sgate): S = _squeezing(cmd.op.p[0], cmd.op.p[1], modes, n) @ S if isinstance(cmd.op, ops.Rgate): S = _rotation(cmd.op.p[0], modes, n) @ S if isinstance(cmd.op, ops.BSgate): S = _beamsplitter(cmd.op.p[0], cmd.op.p[1], modes, n) @ S if isinstance(cmd.op, ops.Interferometer): U1 = cmd.op.p[0] S_U = np.vstack( [np.hstack([U1.real, -U1.imag]), np.hstack([U1.imag, U1.real])] ) S = S_U @ S # the resulting covariance state cov = S @ S.T # calculate Hamilton's A matrix: A = X.(I-Q^{-1})* A_res = np.real_if_close(Amat(cov)) # The bottom right corner of A_res should be identical to A, # up to some constant scaling factor. Check if the ratio # of all elements is one ratio = np.real_if_close(A_res[n:, n:] / A) ratio /= ratio[0, 0] assert np.allclose(ratio, np.ones([n, n]), atol=tol, rtol=0)
def test_Amat_TMS_using_cov(): """test Amat returns correct result for a two-mode squeezed state""" V = TMS_cov(np.arcsinh(1), 0) res = Amat(V) B = np.fliplr(np.diag([1 / np.sqrt(2)] * 2)) O = np.zeros_like(B) ex = np.block([[B, O], [O, B]]) assert np.allclose(res, ex)
def test_numba_tor(N): """Tests numba implementations of the torontonian against the default implementation""" cov = random_covariance(N) O = Xmat(N) @ Amat(cov) t1 = tor(O) t2 = numba_tor(O) t3 = rec_torontonian(O) assert np.isclose(t1, t2) assert np.isclose(t1, t3)
def test_Amat_TMS_using_Q(): """test Amat returns correct result for a two-mode squeezed state""" q = np.fliplr(np.diag([2.0] * 4)) np.fill_diagonal(q, np.sqrt(2)) Q = np.fliplr(q) res = Amat(Q, cov_is_qmat=True) B = np.fliplr(np.diag([1 / np.sqrt(2)] * 2)) O = np.zeros_like(B) ex = np.block([[B, O], [O, B]]) assert np.allclose(res, ex)
def test_numba_ltor(N): """Tests numba implementations of the loop torontonian against the default implementation""" alpha = np.random.random(N) + np.random.random(N) * 1j alpha = np.concatenate((alpha, alpha.conj())) cov = random_covariance(N) O = Xmat(N) @ Amat(cov) mu = O @ alpha t1 = ltor(O, mu) t2 = numba_ltor(O, mu) t3 = rec_ltorontonian(O, mu) assert np.isclose(t1, t2) assert np.isclose(t1, t3)
def reference_count_unnormalized(cov, only_modes=None): A = Amat(cov) modes = cov.shape[0] // 2 if only_modes is not None: mask = only_modes + tuple(x + modes for x in only_modes) A = A[mask, :][:, mask] xmat_size = len(only_modes) else: xmat_size = modes O = Xmat(xmat_size) @ A return tor(O).real
def test_tor_and_threshold_displacement_prob_agree(n_modes): """Tests that threshold_detection_prob, ltor and the usual tor expression all agree when displacements are zero""" cv = random_covariance(n_modes) mu = np.zeros([2 * n_modes]) Q = Qmat(cv) O = Xmat(n_modes) @ Amat(cv) expected = tor(O) / np.sqrt(np.linalg.det(Q)) prob = threshold_detection_prob(mu, cv, np.array([1] * n_modes)) prob2 = numba_ltor(O, mu) / np.sqrt(np.linalg.det(Q)) prob3 = rec_ltorontonian(O, mu) / np.sqrt(np.linalg.det(Q)) prob4 = numba_vac_prob(mu, Q) * ltor(O, mu) assert np.isclose(expected, prob) assert np.isclose(expected, prob2) assert np.isclose(expected, prob3) assert np.isclose(expected, prob4)
def compile(self, seq, registers): # the number of modes in the provided program n_modes = len(registers) # Number of modes must be even if n_modes % 2 != 0: raise CircuitError("The X series only supports programs with an even number of modes.") # Call the GBS compiler to do basic measurement validation. # The GBS compiler also merges multiple measurement commands # into a single MeasureFock command at the end of the circuit. seq = GBSSpecs().compile(seq, registers) # ensure that all modes are measured if len(seq[-1].reg) != n_modes: raise CircuitError("All modes must be measured.") # Use the GaussianUnitary compiler to compute the symplectic # matrix representing the Gaussian operations. # Note that the Gaussian unitary compiler does not accept measurements, # so we append the measurement separately. meas_seq = [seq[-1]] seq = GaussianUnitary().compile(seq[:-1], registers) + meas_seq # determine the modes that are acted on by the symplectic transformation used_modes = [x.ind for x in seq[0].reg] # extract the compiled symplectic matrix S = seq[0].op.p[0] if len(used_modes) != n_modes: # The symplectic transformation acts on a subset of # the programs registers. We must expand the symplectic # matrix to one that acts on all registers. # simply extract the computed symplectic matrix S = expand(seq[0].op.p[0], used_modes, n_modes) half_n_modes = n_modes // 2 # Construct the covariance matrix of the state. # Note that hbar is a global variable that is set by the user cov = (sf.hbar / 2) * S @ S.T # Construct the A matrix A = Amat(cov, hbar=sf.hbar) # Construct the adjacency matrix represented by the A matrix. # This must be an weighted, undirected bipartite graph. That is, # B00 = B11 = 0 (no edges between the two vertex sets 0 and 1), # and B01 == B10.T (undirected edges between the two vertex sets). B = A[:n_modes, :n_modes] B00 = B[:half_n_modes, :half_n_modes] B01 = B[:half_n_modes, half_n_modes:] B10 = B[half_n_modes:, :half_n_modes] B11 = B[half_n_modes:, half_n_modes:] # Perform unitary validation to ensure that the # applied unitary is valid. if not np.allclose(B00, 0) or not np.allclose(B11, 0): # Not a bipartite graph raise CircuitError( "The applied unitary cannot mix between the modes {}-{} and modes {}-{}.".format( 0, half_n_modes - 1, half_n_modes, n_modes - 1 ) ) if not np.allclose(B01, B10): # Not a symmetric bipartite graph raise CircuitError( "The applied unitary on modes {}-{} must be identical to the applied unitary on modes {}-{}.".format( 0, half_n_modes - 1, half_n_modes, n_modes - 1 ) ) # Now that the unitary has been validated, perform the Takagi decomposition # to determine the constituent two-mode squeezing and interferometer # parameters. sqs, U = takagi(B01) sqs = np.arctanh(sqs) # ensure provided S2gates all have the allowed squeezing values if not all(s in self.allowed_sq_ranges for s in sqs): wrong_sq_values = [np.round(s, 4) for s in sqs if s not in self.allowed_sq_ranges] raise CircuitError( "Incorrect squeezing value(s) r={}. Allowed squeezing " "value(s) are {}.".format(wrong_sq_values, self.allowed_sq_ranges) ) # Convert the squeezing values into a sequence of S2gate commands sq_seq = [ Command(ops.S2gate(sqs[i]), [registers[i], registers[i + half_n_modes]]) for i in range(half_n_modes) ] # NOTE: at some point, it might make sense to add a keyword argument to this method, # to allow the user to specify if they want the interferometers decomposed or not. # Convert the unitary into a sequence of MZgate and Rgate commands on the signal modes U1 = ops.Interferometer(U, mesh="rectangular_symmetric", drop_identity=False)._decompose( registers[:half_n_modes] ) U2 = copy.deepcopy(U1) for Ui in U2: Ui.reg = [registers[r.ind + half_n_modes] for r in Ui.reg] return sq_seq + U1 + U2 + meas_seq
def test_Amat_vacuum_using_Q(): """test Amat returns correct result for a vacuum state""" Q = np.identity(2) res = Amat(Q, cov_is_qmat=True) ex = np.zeros([2, 2]) assert np.allclose(res, ex)
def test_Amat_vacuum_using_cov(): """test Amat returns correct result for a vacuum state""" V = np.identity(2) res = Amat(V) ex = np.zeros([2, 2]) assert np.allclose(res, ex)
def test_decomposition(self, tol): """Test that a graph is correctly decomposed""" n = 3 prog = sf.Program(2*n) A = np.zeros([2*n, 2*n]) B = np.random.random([n, n]) A[:n, n:] = B A += A.T sq, U, V = dec.bipartite_graph_embed(B) G = ops.BipartiteGraphEmbed(A) cmds = G.decompose(prog.register) S = np.identity(4 * n) # calculating the resulting decomposed symplectic for cmd in cmds: # all operations should be BSgates, Rgates, or S2gates assert isinstance( cmd.op, (ops.Interferometer, ops.S2gate) ) # build up the symplectic transform modes = [i.ind for i in cmd.reg] if isinstance(cmd.op, ops.S2gate): # check that the registers are i, i+n assert len(modes) == 2 assert modes[1] == modes[0] + n r, phi = par_evaluate(cmd.op.p) assert -r in sq assert phi == 0 S = _two_mode_squeezing(r, phi, modes, 2*n) @ S if isinstance(cmd.op, ops.Interferometer): # check that each unitary only applies to half the modes assert len(modes) == n assert modes in ([0, 1, 2], [3, 4, 5]) # check matrix is unitary U1 = par_evaluate(cmd.op.p[0]) assert np.allclose(U1 @ U1.conj().T, np.identity(n), atol=tol, rtol=0) if modes[0] == 0: assert np.allclose(U1, U, atol=tol, rtol=0) else: assert modes[0] == 3 assert np.allclose(U1, V, atol=tol, rtol=0) S_U = np.vstack( [np.hstack([U1.real, -U1.imag]), np.hstack([U1.imag, U1.real])] ) S = expand(S_U, modes, 2*n) @ S # the resulting covariance state cov = S @ S.T A_res = Amat(cov)[:2*n, :2*n] # The bottom right corner of A_res should be identical to A, # up to some constant scaling factor. Check if the ratio # of all elements is one ratio = np.real_if_close(A_res[n:, :n] / B.T) ratio /= ratio[0, 0] assert np.allclose(ratio, np.ones([n, n]), atol=tol, rtol=0)