def vibronic( t: np.ndarray, U1: np.ndarray, r: np.ndarray, U2: np.ndarray, alpha: np.ndarray, n_samples: int, loss: float = 0.0, ) -> list: """Generate samples for computing vibronic spectra. The following gates are applied to input vacuum states: 1. Two-mode squeezing on all :math:`2N` modes with parameters ``t`` 2. Interferometer ``U1`` on the first :math:`N` modes 3. Squeezing on the first :math:`N` modes with parameters ``r`` 4. Interferometer ``U2`` on the first :math:`N` modes 5. Displacement on the first :math:`N` modes with parameters ``alpha`` A sample is generated by measuring the number of photons in each of the :math:`2N` modes. In the special case that all of the two-mode squeezing parameters ``t`` are zero, only :math:`N` modes are considered, which speeds up calculations. **Example usage:** >>> t = np.array([0., 0., 0., 0., 0., 0., 0.]) >>> U1 = np.array( >>> [[-0.07985219, 0.66041032, -0.19389188, 0.01340832, 0.70312675, -0.1208423, -0.10352726], >>> [0.19216669, -0.12470466, -0.81320519, 0.52045174, -0.1066017, -0.06300751, -0.00376173], >>> [0.60838109, 0.0835063, -0.14958816, -0.34291399, 0.06239828, 0.68753918, -0.07955415], >>> [0.63690134, -0.03047939, 0.46585565, 0.50545897, 0.21194805, -0.20422433, 0.18516987], >>> [0.34556293, 0.22562207, -0.1999159, -0.50280235, -0.25510781, -0.55793978, 0.40065893], >>> [-0.03377431, -0.66280536, -0.14740447, -0.25725325, 0.6145946, -0.07128058, 0.29804963], >>> [-0.24570365, 0.22402764, 0.003273, 0.19204683, -0.05125235, 0.3881131, 0.83623564]]) >>> r = np.array( >>> [0.09721339, 0.07017918, 0.02083469, -0.05974357, -0.07487845, -0.1119975, -0.1866708]) >>> U2 = np.array( >>> [[-0.07012006, 0.14489772, 0.17593463, 0.02431155, -0.63151781, 0.61230046, 0.41087368], >>> [0.5618538, -0.09931968, 0.04562272, 0.02158822, 0.35700706, 0.6614837, -0.326946], >>> [-0.16560687, -0.7608465, -0.25644606, -0.54317241, -0.12822903, 0.12809274, -0.00597384], >>> [0.01788782, 0.60430409, -0.19831443, -0.73270964, -0.06393682, 0.03376894, -0.23038293], >>> [0.78640978, -0.11133936, 0.03160537, -0.09188782, -0.43483738, -0.4018141, 0.09582698], >>> [-0.13664887, -0.11196486, 0.86353995, -0.19608061, -0.12313513, -0.08639263, -0.40251231], >>> [-0.12060103, -0.01169781, -0.33937036, 0.34662981, -0.49895371, 0.03257453, -0.70709135]]) >>> alpha = np.array( >>> [0.15938187, 0.10387399, 1.10301587, -0.26756921, 0.32194572, -0.24317402, 0.0436992]) >>> vibronic(t, U1, S, U2, alpha, 2, 0.0) [[0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] Args: t (array): two-mode squeezing parameters U1 (array): unitary matrix for the first interferometer r (array): squeezing parameters U2 (array): unitary matrix for the second interferometer alpha (array): displacement parameters n_samples (int): number of samples to be generated loss (float): loss parameter denoting the fraction of generated photons that are lost Returns: list[list[int]]: a list of samples from GBS """ if n_samples < 1: raise ValueError("Number of samples must be at least one") if not 0 <= loss <= 1: raise ValueError( "Loss parameter must take a value between zero and one") n_modes = len(t) eng = sf.LocalEngine(backend="gaussian") if np.any(t != 0): gbs = sf.Program(n_modes * 2) else: gbs = sf.Program(n_modes) # pylint: disable=expression-not-assigned,pointless-statement with gbs.context as q: if np.any(t != 0): for i in range(n_modes): sf.ops.S2gate(t[i]) | (q[i], q[i + n_modes]) sf.ops.Interferometer(U1) | q[:n_modes] for i in range(n_modes): sf.ops.Sgate(r[i]) | q[i] sf.ops.Interferometer(U2) | q[:n_modes] for i in range(n_modes): sf.ops.Dgate(alpha[i]) | q[i] if loss: for _q in q: sf.ops.LossChannel(1 - loss) | _q sf.ops.MeasureFock() | q with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="Cannot simulate non-") s = eng.run(gbs, run_options={"shots": n_samples}).samples s = np.array(s).tolist() # convert all generated samples to list if n_samples == 1: s = [s] if np.any(t == 0): s = np.pad(s, ((0, 0), (0, n_modes))).tolist() return s
def prog(): """Program fixture.""" return sf.Program(2)
tf.random.set_seed(137) np.random.seed(137) # define width and depth of CV quantum neural network modes = 1 layers = 8 cutoff_dim = 6 # defining desired state (single photon state) target_state = np.zeros(cutoff_dim) target_state[1] = 1 target_state = tf.constant(target_state, dtype=tf.complex64) # initialize engine and program eng = sf.Engine(backend="tf", backend_options={"cutoff_dim": cutoff_dim}) qnn = sf.Program(modes) # initialize QNN weights weights = init_weights(modes, layers) num_params = np.prod(weights.shape) # Create array of Strawberry Fields symbolic gate arguments, matching # the size of the weights Variable. sf_params = np.arange(num_params).reshape(weights.shape).astype(np.str) sf_params = np.array([qnn.params(*i) for i in sf_params]) # Construct the symbolic Strawberry Fields program by # looping and applying layers to the program. with qnn.context as q: for k in range(layers): layer(sf_params[k], q)
def sample_tmsv( r: list, t: float, Ul: np.ndarray, w: np.ndarray, n_samples: int, loss: float = 0.0, ) -> list: r"""Generate samples for simulating vibrational quantum dynamics with a two-mode squeezed vacuum input state. This function generates samples from a GBS device with two-mode squeezed vacuum input states. Given :math:`N` squeezing parameters and an :math:`N`-dimensional normal-to-local transformation matrix, a GBS device with :math:`2N` modes is simulated. The :func:`~.TimeEvolution` operator acts only on the first :math:`N` modes in the device. Samples are generated by measuring the number of photons in each of the :math:`2N` modes. **Example usage:** >>> r = [[0.2, 0.1], [0.8, 0.2]] >>> t = 10.0 >>> Ul = np.array([[0.707106781, -0.707106781], ... [0.707106781, 0.707106781]]) >>> w = np.array([3914.92, 3787.59]) >>> n_samples = 5 >>> sample_tmsv(r, t, Ul, w, n_samples) [[0, 0, 0, 0], [0, 0, 0, 0], [0, 1, 0, 1], [0, 1, 0, 1], [0, 2, 0, 2]] Args: r (list[list[float]]): list of two-mode squeezing gate parameters given as ``[amplitude, phase]`` for all modes t (float): time in femtoseconds Ul (array): normal-to-local transformation matrix w (array): normal mode frequencies :math:`\omega` in units of :math:`\mbox{cm}^{-1}` n_samples (int): number of samples to be generated loss (float): loss parameter denoting the fraction of lost photons Returns: list[list[int]]: a list of samples """ if np.any(np.iscomplex(Ul)): raise ValueError( "The normal mode to local mode transformation matrix must be real") if n_samples < 1: raise ValueError("Number of samples must be at least one") if not len(r) == len(Ul): raise ValueError( "Number of squeezing parameters and the number of modes in the normal-to-local" " transformation matrix must be equal") N = len(Ul) eng = sf.LocalEngine(backend="gaussian") prog = sf.Program(2 * N) # pylint: disable=expression-not-assigned with prog.context as q: for i in range(N): sf.ops.S2gate(r[i][0], r[i][1]) | (q[i], q[i + N]) sf.ops.Interferometer(Ul.T) | q[:N] TimeEvolution(w, t) | q[:N] sf.ops.Interferometer(Ul) | q[:N] if loss: for _q in q: sf.ops.LossChannel(1 - loss) | _q sf.ops.MeasureFock() | q with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="Cannot simulate non-") s = eng.run(prog, shots=n_samples).samples return s.tolist()
def prog(backend): """Program fixture.""" prog = sf.Program(2) with prog.context as q: ops.Dgate(0.5) | q[0] return prog
def sample(A: np.ndarray, n_mean: float, n_samples: int = 1, threshold: bool = True, loss: float = 0.0) -> list: r"""Generate simulated samples from GBS encoded with a symmetric matrix :math:`A`. **Example usage:** >>> g = nx.erdos_renyi_graph(5, 0.7) >>> a = nx.to_numpy_array(g) >>> sample(a, 3, 4) [[1, 1, 1, 1, 1], [1, 1, 0, 1, 1], [0, 0, 0, 0, 0], [1, 0, 0, 0, 1]] Args: A (array): the symmetric matrix to sample from n_mean (float): mean photon number n_samples (int): number of samples threshold (bool): perform GBS with threshold detectors if ``True`` or photon-number resolving detectors if ``False`` loss (float): fraction of generated photons that are lost while passing through device. Parameter should range from ``loss=0`` (ideal noise-free GBS) to ``loss=1``. Returns: list[list[int]]: a list of samples from GBS with respect to the input symmetric matrix """ if not np.allclose(A, A.T): raise ValueError( "Input must be a NumPy array corresponding to a symmetric matrix") if n_samples < 1: raise ValueError("Number of samples must be at least one") if n_mean < 0: raise ValueError("Mean photon number must be non-negative") if not 0 <= loss <= 1: raise ValueError( "Loss parameter must take a value between zero and one") nodes = len(A) p = sf.Program(nodes) eng = sf.LocalEngine(backend="gaussian") mean_photon_per_mode = n_mean / float(nodes) # pylint: disable=expression-not-assigned,pointless-statement with p.context as q: sf.ops.GraphEmbed(A, mean_photon_per_mode=mean_photon_per_mode) | q if loss: for _q in q: sf.ops.LossChannel(1 - loss) | _q if threshold: sf.ops.MeasureThreshold() | q else: sf.ops.MeasureFock() | q with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="Cannot simulate non-") s = eng.run(p, run_options={"shots": n_samples}).samples if n_samples == 1: return [s] return s.tolist()
def test_no_unitary(self, tol): """Test compilation works with no unitary provided""" prog = sf.Program(8) with prog.context as q: ops.S2gate(SQ_AMPLITUDE) | (q[0], q[4]) ops.S2gate(SQ_AMPLITUDE) | (q[1], q[5]) ops.S2gate(SQ_AMPLITUDE) | (q[2], q[6]) ops.S2gate(SQ_AMPLITUDE) | (q[3], q[7]) ops.MeasureFock() | q res = prog.compile("Xunitary") expected = sf.Program(8) with expected.context as q: ops.S2gate(SQ_AMPLITUDE, 0) | (q[0], q[4]) ops.S2gate(SQ_AMPLITUDE, 0) | (q[1], q[5]) ops.S2gate(SQ_AMPLITUDE, 0) | (q[2], q[6]) ops.S2gate(SQ_AMPLITUDE, 0) | (q[3], q[7]) # corresponds to an identity on modes [0, 1, 2, 3] # This can be easily seen from below by noting that: # MZ(pi, pi) = R(0) = I # MZ(pi, 0) @ MZ(pi, 0) = I # [R(pi) \otimes I] @ MZ(pi, 0) = I ops.MZgate(np.pi, 0) | (q[0], q[1]) ops.MZgate(np.pi, 0) | (q[2], q[3]) ops.MZgate(np.pi, np.pi) | (q[1], q[2]) ops.MZgate(np.pi, np.pi) | (q[0], q[1]) ops.MZgate(np.pi, 0) | (q[2], q[3]) ops.MZgate(np.pi, np.pi) | (q[1], q[2]) ops.Rgate(np.pi) | (q[0]) ops.Rgate(0) | (q[1]) ops.Rgate(0) | (q[2]) ops.Rgate(0) | (q[3]) # corresponds to an identity on modes [4, 5, 6, 7] ops.MZgate(np.pi, 0) | (q[4], q[5]) ops.MZgate(np.pi, 0) | (q[6], q[7]) ops.MZgate(np.pi, np.pi) | (q[5], q[6]) ops.MZgate(np.pi, np.pi) | (q[4], q[5]) ops.MZgate(np.pi, 0) | (q[6], q[7]) ops.MZgate(np.pi, np.pi) | (q[5], q[6]) ops.Rgate(np.pi) | (q[4]) ops.Rgate(0) | (q[5]) ops.Rgate(0) | (q[6]) ops.Rgate(0) | (q[7]) ops.MeasureFock() | q assert program_equivalence(res, expected, atol=tol, compare_params=False) # double check that the applied symplectic is correct # remove the Fock measurements res.circuit = res.circuit[:-1] # extract the Gaussian symplectic matrix O = res.compile("gaussian_unitary").circuit[0].op.p[0] # construct the expected symplectic matrix corresponding # to just the initial two mode squeeze gates S = two_mode_squeezing(SQ_AMPLITUDE, 0) num_modes = 8 expected = np.identity(2 * num_modes) for i in range(num_modes // 2): expected = expand(S, [i, i + num_modes // 2], num_modes) @ expected # Note that the comparison has to be made at the level of covariance matrices # Not at the level of symplectic matrices assert np.allclose(O @ O.T, expected @ expected.T, atol=tol)
#!/usr/bin/env python3 import strawberryfields as sf from strawberryfields.ops import * # initialize engine and program objects eng = sf.Engine(backend="gaussian") gate_teleportation = sf.Program(4) with gate_teleportation.context as q: # create initial states Squeezed(0.1) | q[0] Squeezed(-2) | q[1] Squeezed(-2) | q[2] # apply the gate to be teleported Pgate(0.5) | q[1] # conditional phase entanglement CZgate(1) | (q[0], q[1]) CZgate(1) | (q[1], q[2]) # projective measurement onto # the position quadrature Fourier.H | q[0] MeasureX | q[0] Fourier.H | q[1] MeasureX | q[1] # compare against the expected output # X(q1/sqrt(2)).F.P(0.5).X(q0/sqrt(0.5)).F.|z> # not including the corrections Squeezed(0.1) | q[3]
# y0ka1 # Made on July 14th, 2021 ''' This is a script to run test-circuits to help me learn quantum computing, it will not have a certian structured circuit, rather I will be testing many circuits so this will be constantly running while I run the code from a seperate terminal. ''' import qiskit import strawberryfields as sf import pennylane from strawberryfields import ops # Creating a 3-mode quantum program prog = sf.Program(3) with prog.context as q: ops.Sgate(0.54) | q[0] ops.Sgate(0.54) | q[1] ops.Sgate(0.54) | q[2] ops.BSgate(0.43, 0.1) | (q[0], q[2]) ops.BSgate(0.43, 0.1) | (q[1], q[2]) ops.MeasureFock() | q
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)
import strawberryfields as sf from strawberryfields.ops import * import tensorflow as tf # Define the variational circuit and its output. X = tf.placeholder(tf.float32, shape=[2]) y = tf.placeholder(tf.float32, shape=[2]) sdev = 0.05 depth = 50 phi = tf.Variable(tf.random_normal(shape=[depth], stddev=sdev)) # eng, q = sf.Engine(3) eng = sf.Engine(backend="tf", backend_options={"cutoff_dim": 10}) circuit = sf.Program(2) with circuit.context as q: #with eng: # Note that we are feeding 1-d tensors into gates, not scalars! Dgate(X[0], 0.) | q[0] Dgate(X[1], 0.) | q[1] # Dgate(X[0], X[1]) | q[2] Dgate(phi[0], phi[1]) | q[0] Dgate(phi[2], phi[3]) | q[1] # Dgate(phi[4], phi[5]) | q[2] Sgate(phi[6], phi[7]) | q[0] Sgate(phi[8], phi[9]) | q[1] # Sgate(phi[10], phi[11]) | q[2] Kgate(phi[30]) | q[0] Kgate(phi[31]) | q[1] # Kgate(phi[32]) | q[2] BSgate(phi[12]) | (q[0], q[1]) BSgate() | (q[0], q[1])
def test_gradients2to1(): ket_in = np.zeros((5, 5, 5, 5), dtype=np.complex128) ket_in[1, 2, 1, 0] = np.sqrt(1 / 3) ket_in[1, 0, 0, 3] = 1j * np.sqrt(2 / 3) test_prog = sf.Program(4) # 4 modes with test_prog.context as q: sf.ops.Ket(ket_in) | (q[0], q[1], q[2], q[3]) sf.ops.BSgate(0.1804, 0.0578) | (q[0], q[1]) sf.ops.BSgate(0.06406, 1.5165) | (q[2], q[3]) sf.ops.BSgate(1.473, 0.1176) | (q[1], q[2]) sf.ops.BSgate(0.263, -0.2517) | (q[0], q[1]) sf.ops.BSgate(0.3323, 1.9946) | (q[2], q[3]) sf.ops.BSgate(0.311, -0.3231) | (q[1], q[2]) sf.ops.BSgate(0.4348, 0.0798) | (q[0], q[1]) sf.ops.BSgate(0.0368, 0.6157) | (q[2], q[3]) # eng = sf.Engine(backend="fock", backend_options={"cutoff_dim": 5}) # ket_out_1 = eng.run(test_prog).state.ket() prog_unitary = sf.Program(4) prog_unitary.circuit = test_prog.circuit[1:] prog_compiled = prog_unitary.compile(compiler="gaussian_unitary") S = prog_compiled.circuit[0].op.p[0] U1 = S[:4, :4] + 1j * S[4:, :4] ket_in = np.zeros((5, 5, 5, 5), dtype=np.complex128) ket_in[1, 2, 1, 0] = np.sqrt(1 / 3) ket_in[1, 0, 0, 3] = 1j * np.sqrt(2 / 3) test_prog = sf.Program(4) # 4 modes with test_prog.context as q: sf.ops.Ket(ket_in) | (q[0], q[1], q[2], q[3]) sf.ops.BSgate(0.1804, 0.0568) | (q[0], q[1]) sf.ops.BSgate(0.06306, 1.5165) | (q[2], q[3]) sf.ops.BSgate(1.462, 0.1176) | (q[1], q[2]) sf.ops.BSgate(0.263, -0.2507) | (q[0], q[1]) sf.ops.BSgate(0.3333, 1.9946) | (q[2], q[3]) sf.ops.BSgate(0.312, -0.3231) | (q[1], q[2]) sf.ops.BSgate(0.4358, 0.0788) | (q[0], q[1]) sf.ops.BSgate(0.0358, 0.6157) | (q[2], q[3]) # eng = sf.Engine(backend="fock", backend_options={"cutoff_dim": 5}) # ket_out_2 = eng.run(test_prog).state.ket() prog_unitary = sf.Program(4) prog_unitary.circuit = test_prog.circuit[1:] prog_compiled = prog_unitary.compile(compiler="gaussian_unitary") S = prog_compiled.circuit[0].op.p[0] U2 = S[:4, :4] + 1j * S[4:, :4] dU = U2 - U1 s_in = State({ (1, 2, 1, 0): np.sqrt(1 / 3), (1, 0, 0, 3): 1j * np.sqrt(2 / 3) }) s_out = State({(4, 0, 0, 0): 1.0}) io = IOSpec(input_state=s_in, output_state=s_out) tree1 = Tree(io=io, covariance_matrix=U1) tree2 = Tree(io=io, covariance_matrix=U1 + dU) amp1, grad1 = tree1.amplitude() amp2, grad2 = tree2.amplitude() print(amp2, amp1 + np.sum(grad1 * dU)) print(np.sum(grad1 * dU), amp2 - amp1) assert np.isclose(amp2, amp1 + np.sum(grad1 * dU)) assert np.isclose(amp1, amp2 - np.sum(grad2 * dU))
import strawberryfields as sf from strawberryfields.ops import * prog = sf.Program(2) import tensorflow as tf input_ = tf.placeholder(tf.float32, shape=(2, 1)) weights = tf.Variable([[0.1, 0.1]]) bias = tf.Variable(0.0) NN = tf.sigmoid(tf.matmul(weights, input_) + bias) NNDgate = Dgate(NN) @sf.convert def sigmoid(x): return tf.sigmoid(x) prog = sf.Program(2) with prog.context as q: MeasureX | q[0] Dgate(sigmoid(q[0])) | q[1] batch_size = 3 prog = sf.Program(2) eng = sf.Engine('tf', backend_options={ "cutoff_dim": 7, "batch_size": batch_size
tf.clip_by_value(disp_magnitude_variables[layer_number, i], -disp_clip, disp_clip), disp_phase_variables[layer_number, i]) | q[i] for i in range(mode_number): Kgate( tf.clip_by_value(kerr_variables[layer_number, i], -kerr_clip, kerr_clip)) | q[i] # =================================================================================== # Defining QNN # =================================================================================== # construct the two-mode Strawberry Fields program prog = sf.Program(mode_number) # construct the circuit with prog.context as q: input_qnn_layer() for i in range(depth): qnn_layer(i) # create an engine eng = sf.Engine('tf', backend_options={ "cutoff_dim": cutoff, "batch_size": batch_size })
def vibronic( t: np.ndarray, U1: np.ndarray, r: np.ndarray, U2: np.ndarray, alpha: np.ndarray, n_samples: int, loss: float = 0.0, ) -> list: """Generate samples for computing vibronic spectra. The following gates are applied to input vacuum states: 1. Two-mode squeezing on all :math:`2N` modes with parameters ``t`` 2. Interferometer ``U1`` on the first :math:`N` modes 3. Squeezing on the first :math:`N` modes with parameters ``r`` 4. Interferometer ``U2`` on the first :math:`N` modes 5. Displacement on the first :math:`N` modes with parameters ``alpha`` A sample is generated by measuring the number of photons in each of the :math:`2N` modes. In the special case that all of the two-mode squeezing parameters ``t`` are zero, only :math:`N` modes are considered, which speeds up calculations. **Example usage:** >>> formic = data.Formic() >>> w = formic.w >>> wp = formic.wp >>> Ud = formic.Ud >>> delta = formic.delta >>> T = 0 >>> t, U1, r, U2, alpha = vibronic.gbs_params(w, wp, Ud, delta, T) >>> sample.vibronic(t, U1, r, U2, alpha, 2, 0.0) [[0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] Args: t (array): two-mode squeezing parameters U1 (array): unitary matrix for the first interferometer r (array): squeezing parameters U2 (array): unitary matrix for the second interferometer alpha (array): displacement parameters n_samples (int): number of samples to be generated loss (float): loss parameter denoting the fraction of generated photons that are lost Returns: list[list[int]]: a list of samples from GBS """ if n_samples < 1: raise ValueError("Number of samples must be at least one") if not 0 <= loss <= 1: raise ValueError( "Loss parameter must take a value between zero and one") n_modes = len(t) eng = sf.LocalEngine(backend="gaussian") if np.any(t != 0): gbs = sf.Program(n_modes * 2) else: gbs = sf.Program(n_modes) # pylint: disable=expression-not-assigned,pointless-statement with gbs.context as q: if np.any(t != 0): for i in range(n_modes): sf.ops.S2gate(t[i]) | (q[i], q[i + n_modes]) sf.ops.Interferometer(U1) | q[:n_modes] for i in range(n_modes): sf.ops.Sgate(r[i]) | q[i] sf.ops.Interferometer(U2) | q[:n_modes] for i in range(n_modes): sf.ops.Dgate(alpha[i]) | q[i] if loss: for _q in q: sf.ops.LossChannel(1 - loss) | _q sf.ops.MeasureFock() | q with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="Cannot simulate non-") s = eng.run(gbs, run_options={"shots": n_samples}).samples s = np.array(s).tolist() # convert all generated samples to list if n_samples == 1: s = [s] if np.any(t == 0): s = np.pad(s, ((0, 0), (0, n_modes))).tolist() return s
#!/usr/bin/env python3 import strawberryfields as sf from strawberryfields.ops import * from numpy import pi # initialize engine and program objects eng = sf.Engine(backend="fock", backend_options={"cutoff_dim": 5}) ham_simulation = sf.Program(2) # set the Hamiltonian parameters J = 1 # hopping transition U = 1.5 # on-site interaction k = 20 # Lie product decomposition terms t = 1.086 # timestep theta = -J * t / k r = -U * t / (2 * k) with ham_simulation.context as q: # prepare the initial state Fock(2) | q[0] # Two node tight-binding # Hamiltonian simulation for i in range(k): BSgate(theta, pi / 2) | (q[0], q[1]) Kgate(r) | q[0] Rgate(-r) | q[0] Kgate(r) | q[1] Rgate(-r) | q[1] # end circuit
def prog(self): """Dummy program context for each test""" prog = sf.Program(2) Program._current_context = prog yield prog Program._current_context = None
#!/usr/bin/env python3 import strawberryfields as sf from strawberryfields.ops import * from strawberryfields.utils import scale from numpy import pi, sqrt # initialize engine and program objects eng = sf.Engine(backend="gaussian") teleportation = sf.Program(3) with teleportation.context as q: psi, alice, bob = q[0], q[1], q[2] # state to be teleported: Coherent(1+0.5j) | psi # 50-50 beamsplitter BS = BSgate(pi/4, 0) # maximally entangled states Squeezed(-2) | alice Squeezed(2) | bob BS | (alice, bob) # Alice performs the joint measurement # in the maximally entangled basis BS | (psi, alice) MeasureX | psi MeasureP | alice # Bob conditionally displaces his mode
def gaussian( U1: np.ndarray, r: np.ndarray, U2: np.ndarray, alpha: np.ndarray, n_samples: int, loss: float = 0.0, ) -> list: r"""Generate simulated samples from pure Gaussian states. The pure Gaussian states are prepared by applying the following gates to the vacuum: #. Interferometer ``U1`` #. Squeezing on all modes with parameters ``r`` #. Interferometer ``U2`` #. Displacement on all modes with parameters ``alpha`` Note that, since the gates are applied to the vacuum, the first interferometer has no effect. It is nevertheless included so that the inputs to this function follow a standard decomposition of Gaussian unitaries shown :ref:`above <decomposition>`. Args: U1 (array): first interferometer unitary matrix r (array): squeezing parameters U2 (array): second interferometer unitary matrix alpha (array): displacement parameters n_samples (int): number of samples to be generated loss (float): fraction of generated photons that are lost while passing through device. Parameter should range from ``loss=0`` (ideal noise-free GBS) to ``loss=1``. Returns: list[list[int]]: a list of samples from GBS """ if n_samples < 1: raise ValueError("Number of samples must be at least one") if not 0 <= loss <= 1: raise ValueError( "Loss parameter must take a value between zero and one") n_modes = len(alpha) eng = sf.LocalEngine(backend="gaussian") gbs = sf.Program(n_modes) # pylint: disable=expression-not-assigned,pointless-statement with gbs.context as q: # this interferometer has no action, but is kept to follow the decomposition given in the # docstring sf.ops.Interferometer(U1) | q for i in range(n_modes): sf.ops.Sgate(r[i]) | q[i] sf.ops.Interferometer(U2) | q for i in range(n_modes): sf.ops.Dgate(alpha[i]) | q[i] if loss: for _q in q: sf.ops.LossChannel(1 - loss) | _q sf.ops.MeasureFock() | q with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="Cannot simulate non-") s = eng.run(gbs, run_options={"shots": n_samples}).samples if n_samples == 1: return [s] return s.tolist()
#!/usr/bin/env python3 import numpy as np import strawberryfields as sf from strawberryfields.ops import * # initialize engine and program objects eng = sf.Engine(backend="gaussian") gbs = sf.Program(4) # define the linear interferometer U = np.array( [[ 0.219546940711 - 0.256534554457j, 0.611076853957 + 0.524178937791j, -0.102700187435 + 0.474478834685j, -0.027250232925 + 0.03729094623j ], [ 0.451281863394 + 0.602582912475j, 0.456952590016 + 0.01230749109j, 0.131625867435 - 0.450417744715j, 0.035283194078 - 0.053244267184j ], [ 0.038710094355 + 0.492715562066j, -0.019212744068 - 0.321842852355j, -0.240776471286 + 0.524432833034j, -0.458388143039 + 0.329633367819j ], [ -0.156619083736 + 0.224568570065j, 0.109992223305 - 0.163750223027j, -0.421179844245 + 0.183644837982j, 0.818769184612 + 0.068015658737j ]]) with gbs.context as q: # prepare the input squeezed states S = Sgate(1)
def sample_fock( input_state: list, t: float, Ul: np.ndarray, w: np.ndarray, n_samples: int, cutoff: int, loss: float = 0.0, ) -> list: r"""Generate samples for simulating vibrational quantum dynamics with an input Fock state. **Example usage:** >>> input_state = [0, 2] >>> t = 10.0 >>> Ul = np.array([[0.707106781, -0.707106781], ... [0.707106781, 0.707106781]]) >>> w = np.array([3914.92, 3787.59]) >>> n_samples = 5 >>> cutoff = 5 >>> sample_fock(input_state, t, Ul, w, n_samples, cutoff) [[0, 2], [0, 2], [1, 1], [0, 2], [0, 2]] Args: input_state (list): input Fock state t (float): time in femtoseconds Ul (array): normal-to-local transformation matrix w (array): normal mode frequencies :math:`\omega` in units of :math:`\mbox{cm}^{-1}` n_samples (int): number of samples to be generated cutoff (int): cutoff dimension for each mode loss (float): loss parameter denoting the fraction of lost photons Returns: list[list[int]]: a list of samples """ if np.any(np.iscomplex(Ul)): raise ValueError( "The normal mode to local mode transformation matrix must be real") if n_samples < 1: raise ValueError("Number of samples must be at least one") if not len(input_state) == len(Ul): raise ValueError( "Number of modes in the input state and the normal-to-local transformation" " matrix must be equal") if np.any(np.array(input_state) < 0): raise ValueError("Input state must not contain negative values") if max(input_state) >= cutoff: raise ValueError( "Number of photons in each input mode must be smaller than cutoff") modes = len(Ul) s = [] eng = sf.Engine("fock", backend_options={"cutoff_dim": cutoff}) prog = sf.Program(modes) # pylint: disable=expression-not-assigned with prog.context as q: for i in range(modes): sf.ops.Fock(input_state[i]) | q[i] sf.ops.Interferometer(Ul.T) | q TimeEvolution(w, t) | q sf.ops.Interferometer(Ul) | q if loss: for _q in q: sf.ops.LossChannel(1 - loss) | _q sf.ops.MeasureFock() | q for _ in range(n_samples): s.append(eng.run(prog).samples[0].tolist()) return s
def test_decomposition(self, hbar, 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].x, cmd.op.p[1].x, modes, n) @ S if isinstance(cmd.op, ops.Rgate): S = _rotation(cmd.op.p[0].x, modes, n) @ S if isinstance(cmd.op, ops.BSgate): S = _beamsplitter(cmd.op.p[0].x, cmd.op.p[1].x, modes, n) @ S if isinstance(cmd.op, ops.Interferometer): U1 = cmd.op.p[0].x 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})* I = np.identity(n) O = np.zeros_like(I) X = np.block([[O, I], [I, O]]) x = cov[:n, :n] xp = cov[:n, n:] p = cov[n:, n:] aidaj = (x + p + 1j * (xp - xp.T) - 2 * I) / 4 aiaj = (x - p + 1j * (xp + xp.T)) / 4 Q = np.block([[aidaj, aiaj.conj()], [aiaj, aidaj.conj()] ]) + np.identity(2 * n) A_res = X @ (np.identity(2 * n) - np.linalg.inv(Q)).conj() # 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 sample_coherent( alpha: list, t: float, Ul: np.ndarray, w: np.ndarray, n_samples: int, loss: float = 0.0, ) -> list: r"""Generate samples for simulating vibrational quantum dynamics with an input coherent state. **Example usage:** >>> alpha = [[0.3, 0.5], [1.4, 0.1]] >>> t = 10.0 >>> Ul = np.array([[0.707106781, -0.707106781], ... [0.707106781, 0.707106781]]) >>> w = np.array([3914.92, 3787.59]) >>> n_samples = 5 >>> sample_coherent(alpha, t, Ul, w, n_samples) [[0, 2], [0, 1], [0, 3], [0, 2], [0, 1]] Args: alpha (list[list[float]]): list of displacement parameters given as ``[magnitudes, angles]`` for all modes t (float): time in femtoseconds Ul (array): normal-to-local transformation matrix w (array): normal mode frequencies :math:`\omega` in units of :math:`\mbox{cm}^{-1}` n_samples (int): number of samples to be generated loss (float): loss parameter denoting the fraction of lost photons Returns: list[list[int]]: a list of samples """ if np.any(np.iscomplex(Ul)): raise ValueError( "The normal mode to local mode transformation matrix must be real") if n_samples < 1: raise ValueError("Number of samples must be at least one") if not len(alpha) == len(Ul): raise ValueError( "Number of displacement parameters and the number of modes in the normal-to-local" " transformation matrix must be equal") modes = len(Ul) eng = sf.LocalEngine(backend="gaussian") prog = sf.Program(modes) # pylint: disable=expression-not-assigned with prog.context as q: for i in range(modes): sf.ops.Dgate(alpha[i][0], alpha[i][1]) | q[i] sf.ops.Interferometer(Ul.T) | q TimeEvolution(w, t) | q sf.ops.Interferometer(Ul) | q if loss: for _q in q: sf.ops.LossChannel(1 - loss) | _q sf.ops.MeasureFock() | q with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="Cannot simulate non-") s = eng.run(prog, shots=n_samples).samples return s.tolist()
def test_no_unitary(self, chip, tol): """Test compilation works with no unitary provided""" prog = sf.Program(12) with prog.context as q: ops.S2gate(SQ_AMPLITUDE) | (q[0], q[6]) ops.S2gate(SQ_AMPLITUDE) | (q[1], q[7]) ops.S2gate(SQ_AMPLITUDE) | (q[2], q[8]) ops.S2gate(SQ_AMPLITUDE) | (q[3], q[9]) ops.S2gate(SQ_AMPLITUDE) | (q[4], q[10]) ops.S2gate(SQ_AMPLITUDE) | (q[5], q[11]) ops.MeasureFock() | q res = prog.compile(chip.short_name) expected = sf.Program(12) with expected.context as q: ops.S2gate(SQ_AMPLITUDE, 0) | (q[0], q[6]) ops.S2gate(SQ_AMPLITUDE, 0) | (q[1], q[7]) ops.S2gate(SQ_AMPLITUDE, 0) | (q[2], q[8]) ops.S2gate(SQ_AMPLITUDE, 0) | (q[3], q[9]) ops.S2gate(SQ_AMPLITUDE, 0) | (q[4], q[10]) ops.S2gate(SQ_AMPLITUDE, 0) | (q[5], q[11]) # corresponds to an identity on modes [0, 1, 2, 3, 4, 5] # This can be easily seen from below by noting that: # MZ(pi, pi) = R(0) = I # MZ(pi, 0) @ MZ(pi, 0) = I # [R(pi) \otimes I] @ MZ(pi, 0) = I ops.MZgate(np.pi, 0) | (q[0], q[1]) ops.MZgate(np.pi, 0) | (q[2], q[3]) ops.MZgate(np.pi, 0) | (q[4], q[5]) ops.MZgate(np.pi, np.pi) | (q[1], q[2]) ops.MZgate(np.pi, np.pi) | (q[3], q[4]) ops.MZgate(np.pi, 0) | (q[0], q[1]) ops.MZgate(np.pi, 0) | (q[2], q[3]) ops.MZgate(np.pi, 0) | (q[4], q[5]) ops.MZgate(np.pi, np.pi) | (q[1], q[2]) ops.MZgate(np.pi, np.pi) | (q[3], q[4]) ops.MZgate(np.pi, 0) | (q[0], q[1]) ops.MZgate(np.pi, np.pi) | (q[2], q[3]) ops.MZgate(np.pi, np.pi) | (q[4], q[5]) ops.MZgate(np.pi, np.pi) | (q[1], q[2]) ops.MZgate(np.pi, np.pi) | (q[3], q[4]) ops.Rgate(np.pi) | (q[0]) ops.Rgate(0) | (q[1]) ops.Rgate(0) | (q[2]) ops.Rgate(0) | (q[3]) ops.Rgate(0) | (q[4]) ops.Rgate(0) | (q[5]) # corresponds to an identity on modes [6, 7, 8, 9, 10, 11] ops.MZgate(np.pi, 0) | (q[6], q[7]) ops.MZgate(np.pi, 0) | (q[8], q[9]) ops.MZgate(np.pi, 0) | (q[10], q[11]) ops.MZgate(np.pi, np.pi) | (q[7], q[8]) ops.MZgate(np.pi, np.pi) | (q[9], q[10]) ops.MZgate(np.pi, 0) | (q[6], q[7]) ops.MZgate(np.pi, 0) | (q[8], q[9]) ops.MZgate(np.pi, 0) | (q[10], q[11]) ops.MZgate(np.pi, np.pi) | (q[7], q[8]) ops.MZgate(np.pi, np.pi) | (q[9], q[10]) ops.MZgate(np.pi, 0) | (q[6], q[7]) ops.MZgate(np.pi, np.pi) | (q[8], q[9]) ops.MZgate(np.pi, np.pi) | (q[10], q[11]) ops.MZgate(np.pi, np.pi) | (q[7], q[8]) ops.MZgate(np.pi, np.pi) | (q[9], q[10]) ops.Rgate(np.pi) | (q[6]) ops.Rgate(0) | (q[7]) ops.Rgate(0) | (q[8]) ops.Rgate(0) | (q[9]) ops.Rgate(0) | (q[10]) ops.Rgate(0) | (q[11]) ops.MeasureFock() | q # Check that the applied symplectic is correct # remove the Fock measurements res.circuit = res.circuit[:-1] # extract the Gaussian symplectic matrix O = res.compile("gaussian_unitary").circuit[0].op.p[0] # construct the expected symplectic matrix corresponding # to just the initial two mode squeeze gates S = TMS(SQ_AMPLITUDE, 0) expected = np.zeros([2*12, 2*12]) l = 12 // 2 ch = np.cosh(SQ_AMPLITUDE) * np.identity(l) sh = np.sinh(SQ_AMPLITUDE) * np.identity(l) zh = np.zeros([l, l]) expected = np.block([[ch, sh, zh, zh], [sh, ch, zh, zh], [zh, zh, ch, -sh], [zh, zh, -sh, ch]]) assert np.allclose(O, expected, atol=tol)
def execution_context(self): """Initialize the engine""" self.reset() self.prog = sf.Program(self.num_wires) self.q = self.prog.register return self.prog
#!/usr/bin/env python3 import strawberryfields as sf from strawberryfields.ops import * from strawberryfields.utils import scale from numpy import pi, sqrt import numpy as np # initialize engine and program objects eng = sf.Engine(backend="gaussian") gaussian_cloning = sf.Program(4) with gaussian_cloning.context as q: # state to be cloned Coherent(0.7+1.2j) | q[0] # 50-50 beamsplitter BS = BSgate(pi/4, 0) # symmetric Gaussian cloning scheme BS | (q[0], q[1]) BS | (q[1], q[2]) MeasureX | q[1] MeasureP | q[2] Xgate(scale(q[1], sqrt(2))) | q[0] Zgate(scale(q[2], sqrt(2))) | q[0] # after the final beamsplitter, modes q[0] and q[3] # will contain identical approximate clones of the # initial state Coherent(0.1+0j) BS | (q[0], q[3]) # end circuit
params = [r1, sq_r, sq_phi, r2, d_r, d_phi, kappa] # layer architecture def layer(i, q): Rgate(r1[i]) | q Sgate(sq_r[i], sq_phi[i]) | q Rgate(r2[i]) | q Dgate(d_r[i], d_phi[i]) | q Dgate(d_r[i]) | q Kgate(kappa[i]) | q return q # Create program and engine prog = sf.Program(1) eng = sf.Engine('tf', backend_options={"cutoff_dim": cutoff}) # Apply circuit of layers with corresponding depth with prog.context as q: for k in range(depth): layer(k, q[0]) # Run engine state = eng.run(prog, run_options={"eval": False}).state ket = state.ket()
#!/usr/bin/env python3 import strawberryfields as sf from strawberryfields.ops import * # initialize engine and program objects eng = sf.Engine(backend="fock", backend_options={"cutoff_dim": 7}) boson_sampling = sf.Program(4) with boson_sampling.context as q: # prepare the input fock states Fock(1) | q[0] Fock(1) | q[1] Vac | q[2] Fock(1) | q[3] # rotation gates Rgate(0.5719) | q[0] Rgate(-1.9782) | q[1] Rgate(2.0603) | q[2] Rgate(0.0644) | q[3] # beamsplitter array BSgate(0.7804, 0.8578) | (q[0], q[1]) BSgate(0.06406, 0.5165) | (q[2], q[3]) BSgate(0.473, 0.1176) | (q[1], q[2]) BSgate(0.563, 0.1517) | (q[0], q[1]) BSgate(0.1323, 0.9946) | (q[2], q[3]) BSgate(0.311, 0.3231) | (q[1], q[2]) BSgate(0.4348, 0.0798) | (q[0], q[1]) BSgate(0.4368, 0.6157) | (q[2], q[3]) # end circuit
to the output of a GKP qubit phase gate applied to a finite energy |+> state. The qubit phase gate is applied using measurement-based squeezing.""" import strawberryfields as sf import numpy as np import matplotlib.pyplot as plt from matplotlib import colors, colorbar sf.hbar = 1 x = np.linspace(-4, 4, 400) * np.sqrt(np.pi) p = np.linspace(-4, 4, 400) * np.sqrt(np.pi) wigners = [] # Finite energy GKP |+i> state prog_y = sf.Program(1) with prog_y.context as q: sf.ops.GKP([np.pi / 2, -np.pi / 2], epsilon=0.1) | q[0] eng = sf.Engine("bosonic") result = eng.run(prog_y) wigners.append(result.state.wigner(0, x, p)) # Squeezing values for measurement-based squeezing ancillae rdB = np.array([14, 10, 6]) r_anc = np.log(10) * rdB / 20 # Detection efficiencies eta_anc = np.array([1, 0.98, 0.95]) # Target squeezing and rotation angles to apply a GKP phase gate r = np.arccosh(np.sqrt(5 / 4))
def test_no_unitary(self, tol): """Test compilation works with no unitary provided""" prog = sf.Program(8) with prog.context as q: ops.S2gate(SQ_AMPLITUDE) | (q[0], q[4]) ops.S2gate(SQ_AMPLITUDE) | (q[1], q[5]) ops.S2gate(SQ_AMPLITUDE) | (q[2], q[6]) ops.S2gate(SQ_AMPLITUDE) | (q[3], q[7]) ops.MeasureFock() | q res = prog.compile("X8_01") expected = sf.Program(8) with expected.context as q: ops.S2gate(SQ_AMPLITUDE, 0) | (q[0], q[4]) ops.S2gate(SQ_AMPLITUDE, 0) | (q[1], q[5]) ops.S2gate(SQ_AMPLITUDE, 0) | (q[2], q[6]) ops.S2gate(SQ_AMPLITUDE, 0) | (q[3], q[7]) # corresponds to an identity on modes [0, 1, 2, 3] # This can be easily seen from below by noting that: # MZ(pi, pi) = R(0) = I # MZ(pi, 0) @ MZ(pi, 0) = I # [R(pi) \otimes I] @ MZ(pi, 0) = I ops.MZgate(np.pi, 0) | (q[0], q[1]) ops.MZgate(np.pi, 0) | (q[2], q[3]) ops.MZgate(np.pi, np.pi) | (q[1], q[2]) ops.MZgate(np.pi, 0) | (q[0], q[1]) ops.MZgate(np.pi, 0) | (q[2], q[3]) ops.MZgate(np.pi, np.pi) | (q[1], q[2]) ops.Rgate(0) | (q[0]) ops.Rgate(0) | (q[1]) ops.Rgate(0) | (q[2]) ops.Rgate(0) | (q[3]) # corresponds to an identity on modes [4, 5, 6, 7] ops.MZgate(np.pi, 0) | (q[4], q[5]) ops.MZgate(np.pi, 0) | (q[6], q[7]) ops.MZgate(np.pi, np.pi) | (q[5], q[6]) ops.MZgate(np.pi, 0) | (q[4], q[5]) ops.MZgate(np.pi, 0) | (q[6], q[7]) ops.MZgate(np.pi, np.pi) | (q[5], q[6]) ops.Rgate(0) | (q[4]) ops.Rgate(0) | (q[5]) ops.Rgate(0) | (q[6]) ops.Rgate(0) | (q[7]) ops.MeasureFock() | q assert program_equivalence(res, expected, atol=tol) # double check that the applied symplectic is correct # remove the Fock measurements res.circuit = res.circuit[:-1] # extract the Gaussian symplectic matrix O = res.compile("gaussian_unitary").circuit[0].op.p[0] # construct the expected symplectic matrix corresponding # to just the initial two mode squeeze gates S = TMS(SQ_AMPLITUDE, 0) expected = np.zeros([2 * 8, 2 * 8]) idx = np.arange(2 * 8).reshape(4, 4).T for i in idx: expected[i.reshape(-1, 1), i.reshape(1, -1)] = S assert np.allclose(O, expected, atol=tol)