def test_passing_list_of_tdmprograms(self): """Test that error is raised when passing a list containing TDM programs""" prog = TDMProgram(N=2) with prog.context([1, 1], [1, 1], [1, 1]) as (p, q): ops.Sgate(0, 0) | q[1] ops.BSgate(p[0]) | (q[0], q[1]) ops.Rgate(p[1]) | q[1] ops.MeasureHomodyne(p[2]) | q[0] eng = sf.Engine("gaussian") with pytest.raises( NotImplementedError, match="Lists of TDM programs are not currently supported" ): eng.run([prog, prog])
def test_tdm_program(self): """Test TDM program converts properly""" prog = TDMProgram(2) with prog.context([1, 2], [3, 4], [5, 6]) as (p, q): ops.Sgate(0.7, 0) | q[1] ops.BSgate(p[0]) | (q[0], q[1]) ops.Rgate(p[1]) | q[1] ops.MeasureHomodyne(p[2]) | q[0] bb = io.to_blackbird(prog) assert bb.operations[0] == { "kwargs": {}, "args": [0.7, 0], "op": "Sgate", "modes": [1] } assert bb.operations[1] == { "kwargs": {}, "args": ["p0", 0.0], "op": "BSgate", "modes": [0, 1], } assert bb.operations[2] == { "kwargs": {}, "args": ["p1"], "op": "Rgate", "modes": [1] } assert bb.operations[3] == { "kwargs": {}, "args": ["p2"], "op": "MeasureHomodyne", "modes": [0], } assert bb.programtype == { "name": "tdm", "options": { "temporal_modes": 2 } } assert list(bb._var.keys()) == ["p0", "p1", "p2"] assert np.all(bb._var["p0"] == np.array([[1, 2]])) assert np.all(bb._var["p1"] == np.array([[3, 4]])) assert np.all(bb._var["p2"] == np.array([[5, 6]])) assert bb.modes == {0, 1}
def test_delays_tdmprogram_with_several_spatial_modes(self): """Test that error is raised when calculating the delays of a TDM program with more than one spatial mode""" prog = TDMProgram(N=[1, 2]) with prog.context([0, np.pi / 2], [0, np.pi / 2]) as (p, q): ops.Sgate(4) | q[0] ops.Sgate(4) | q[2] ops.MeasureHomodyne(p[0]) | q[0] ops.MeasureHomodyne(p[1]) | q[1] with pytest.raises( NotImplementedError, match="Calculating delays for programs with more than one spatial mode is not implemented.", ): prog.get_delays()
def test_cropping_tdmprogram_with_several_spatial_modes(self): """Test that error is raised when cropping samples from a TDM program with more than one spatial mode""" prog = TDMProgram(N=[1, 2]) with prog.context([0, np.pi / 2], [0, np.pi / 2]) as (p, q): ops.Sgate(4) | q[0] ops.Sgate(4) | q[2] ops.MeasureHomodyne(p[0]) | q[0] ops.MeasureHomodyne(p[1]) | q[1] with pytest.raises( NotImplementedError, match="Cropping vacuum modes for programs with more than one spatial mode is not implemented.", ): prog.get_crop_value()
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_one_dimensional_cluster_tokyo(): """ One-dimensional temporal-mode cluster state as demonstrated in https://aip.scitation.org/doi/pdf/10.1063/1.4962732 """ 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(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_tdm_program(self): """Test the TDM programs converts properly""" sf_prog = TDMProgram(2) with sf_prog.context([1, 2], [3, 4], [5, 6]) as (p, q): ops.Sgate(0.7, 0) | q[1] ops.BSgate(p[0]) | (q[0], q[1]) ops.Rgate(p[1]) | q[1] ops.MeasureHomodyne(p[2]) | q[0] xir_prog = io.to_xir(sf_prog) expected = [ ("Sgate", [0.7, 0], (1,)), ("BSgate", ["p0", 0.0], (0, 1)), ("Rgate", ["p1"], (1,)), ("MeasureHomodyne", {"phi": "p2"}, (0,)), ] assert [(stmt.name, stmt.params, stmt.wires) for stmt in xir_prog.statements] == expected
def singleloop(r, alpha, phi, theta, shots, shift="default"): """Single-loop program. Args: r (float): squeezing parameter alpha (Sequence[float]): beamsplitter angles phi (Sequence[float]): rotation angles theta (Sequence[float]): homodyne measurement angles shots (int): number of shots shift (string): type of shift used in the program Returns: (list): homodyne samples from the single loop simulation """ prog = TDMProgram(N=2) with prog.context(alpha, phi, theta, shift=shift) as (p, q): ops.Sgate(r, 0) | q[1] ops.BSgate(p[0]) | (q[0], q[1]) ops.Rgate(p[1]) | q[1] ops.MeasureHomodyne(p[2]) | q[0] eng = sf.Engine("gaussian") result = eng.run(prog, shots=shots) return result.samples
def test_two_dimensional_cluster_tokyo(): """ Two-dimensional temporal-mode cluster state as demonstrated by Universtiy of Tokyo. See: https://arxiv.org/pdf/1903.03918.pdf """ # temporal delay in timebins for each spatial mode delayA = 0 delayB = 1 delayC = 5 delayD = 0 # concurrent modes in each spatial mode concurrA = 1 + delayA concurrB = 1 + delayB concurrC = 1 + delayC concurrD = 1 + delayD N = [concurrA, concurrB, concurrC, concurrD] sq_r = 5 # first half of cluster state measured in X, second half in P n = 400 # number of timebins 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 theta_C = theta_A theta_D = theta_A shots = 10 # 2D cluster prog = TDMProgram(N) with prog.context(theta_A, theta_B, theta_C, theta_D, shift="default") as (p, q): ops.Sgate(sq_r, 0) | q[0] ops.Sgate(sq_r, 0) | q[2] ops.Sgate(sq_r, 0) | q[8] ops.Sgate(sq_r, 0) | q[9] ops.Rgate(np.pi / 2) | q[0] ops.Rgate(np.pi / 2) | q[8] ops.BSgate(np.pi / 4) | (q[0], q[2]) ops.BSgate(np.pi / 4) | (q[8], q[9]) ops.BSgate(np.pi / 4) | (q[2], q[8]) ops.BSgate(np.pi / 4) | (q[0], q[1]) ops.BSgate(np.pi / 4) | (q[3], q[9]) ops.MeasureHomodyne(p[0]) | q[0] ops.MeasureHomodyne(p[1]) | q[1] ops.MeasureHomodyne(p[2]) | q[3] ops.MeasureHomodyne(p[3]) | q[9] 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 X_C = reshaped_samples[sh][2][: n // 2] # X samples from detector C P_C = reshaped_samples[sh][2][n // 2 :] # P samples from detector C X_D = reshaped_samples[sh][3][: n // 2] # X samples from detector D P_D = reshaped_samples[sh][3][n // 2 :] # P samples from detector D N = delayC # nullifiers defined in https://arxiv.org/pdf/1903.03918.pdf, Fig. S5 ntot = len(X_A) - N - 1 nX1 = np.array( [ X_A[k] + X_B[k] - np.sqrt(1 / 2) * (-X_A[k + 1] + X_B[k + 1] + X_C[k + N] + X_D[k + N]) for k in range(ntot) ] ) nX2 = np.array( [ X_C[k] - X_D[k] - np.sqrt(1 / 2) * (-X_A[k + 1] + X_B[k + 1] - X_C[k + N] - X_D[k + N]) for k in range(ntot) ] ) nP1 = np.array( [ P_A[k] + P_B[k] + np.sqrt(1 / 2) * (-P_A[k + 1] + P_B[k + 1] + P_C[k + N] + P_D[k + N]) for k in range(ntot) ] ) nP2 = np.array( [ P_C[k] - P_D[k] + np.sqrt(1 / 2) * (-P_A[k + 1] + P_B[k + 1] - P_C[k + N] - P_D[k + N]) for k in range(ntot) ] ) nX1var = np.var(nX1) nX2var = np.var(nX2) nP1var = np.var(nP1) nP2var = np.var(nP2) assert np.allclose(nX1var, 2 * sf.hbar * np.exp(-2 * sq_r), rtol=5 / np.sqrt(ntot)) assert np.allclose(nX2var, 2 * sf.hbar * np.exp(-2 * sq_r), rtol=5 / np.sqrt(ntot)) assert np.allclose(nP1var, 2 * sf.hbar * np.exp(-2 * sq_r), rtol=5 / np.sqrt(ntot)) assert np.allclose(nP2var, 2 * sf.hbar * np.exp(-2 * sq_r), rtol=5 / np.sqrt(ntot))
def test_two_dimensional_cluster_denmark(): """ Two-dimensional temporal-mode cluster state as demonstrated in https://arxiv.org/pdf/1906.08709 """ 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([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 from_xir_to_tdm(xir_prog: xir.Program) -> TDMProgram: """Convert an XIR Program to a ``TDMProgram``. Args: xir_prog (xir.Program): the input XIR program object Returns: TDMProgram: corresponding ``TDMProgram`` Raises: ValueError: if the number of modes 'N' is missing from the XIR program options NameError: if an applied quantum operation is not defined in Strawberry Fields """ N = xir_prog.options.get("N") if not N: raise ValueError( "Number of modes 'N' is missing from the XIR program options.") prog = TDMProgram(N, name=xir_prog.options.get("_name_", "xir")) # extract the tdm gate arguments from the xir program constants args = [val for key, val in xir_prog.constants.items() if is_ptype(key)] # convert arguments to float/complex if stored as Decimal/DecimalComplex objects for i, params in enumerate(args): for j, p in enumerate(params): if isinstance(p, Decimal): args[i][j] = float(p) elif isinstance(p, xir.DecimalComplex): args[i][j] = complex(p) # append the quantum operations with prog.context(*args) as (p, q): for op in get_expanded_statements(xir_prog): # check if operation name is in the list of # defined StrawberryFields operations. # This is used by checking against the ops.py __all__ # module attribute, which contains the names # of all defined quantum operations if op.name in ops.__all__: # get the quantum operation from the sf.ops module gate = getattr(ops, op.name) else: raise NameError(f"Quantum operation {op.name!r} not defined!") # create the list of regrefs regrefs = [q[int(i)] for i in op.wires] if op.params: # convert symbolic expressions to symbolic expressions containing the corresponding # MeasuredParameter and FreeParameter instances. if isinstance(op.params, dict): vals = sfpar.par_convert(op.params.values(), prog) params = dict(zip(op.params.keys(), vals)) for key, val in params.items(): if is_ptype(val): params[key] = p[int(val[1:])] gate(**params) | regrefs # pylint:disable=expression-not-assigned else: params = [] for param in op.params: if isinstance(param, Decimal): params.append(float(param)) elif isinstance(param, (list, np.ndarray)): params.append(np.array(_listr(param))) elif isinstance(param, str) and is_ptype(param): params.append(p[int(param[1:])]) else: params.append(param) params = sfpar.par_convert(params, prog) gate(*params) | regrefs # pylint:disable=expression-not-assigned else: gate() | regrefs # pylint:disable=expression-not-assigned,pointless-statement prog._target = xir_prog.options.get("target", None) # pylint: disable=protected-access if "shots" in xir_prog.options: prog.run_options["shots"] = xir_prog.options["shots"] return prog
def from_blackbird_to_tdm(bb: blackbird.BlackbirdProgram) -> TDMProgram: """Convert a ``BlackbirdProgram`` to a ``TDMProgram``. Args: bb (blackbird.BlackbirdProgram): the input Blackbird program object Returns: TDMProgram: corresponding ``TDMProgram`` Raises: NameError: if an applied quantum operation is not defined in Strawberry Fields """ prog = TDMProgram(max(bb.modes) + 1, name=bb.name) def is_free_param(param): return isinstance(param, str) and is_ptype(param) # retrieve all the free parameters in the Blackbird program (e.g. "p0", "p1" # etc.) and add their corresponding values to args args = [] for k in bb._var.keys(): if is_free_param(k): v = bb._var[k].flatten() args.append(v) # append the quantum operations with prog.context(*args) as (p, q): for op in bb.operations: # check if operation name is in the list of # defined StrawberryFields operations. # This is used by checking against the ops.py __all__ # module attribute, which contains the names # of all defined quantum operations if op["op"] in ops.__all__: # get the quantum operation from the sf.ops module gate = getattr(ops, op["op"]) else: raise NameError("Quantum operation {} not defined!".format( op["op"])) # create the list of regrefs regrefs = [q[i] for i in op["modes"]] if "args" in op: # the gate has arguments args = op["args"] kwargs = op["kwargs"] for i, p in enumerate(args): if is_free_param(p): args[i] = sfpar.FreeParameter(p) for k, v in kwargs.items(): if is_free_param(v): kwargs[k] = sfpar.FreeParameter(v) # Convert symbolic expressions in args/kwargs containing measured and free parameters to # symbolic expressions containing the corresponding MeasuredParameter and FreeParameter instances. args = sfpar.par_convert(args, prog) vals = sfpar.par_convert(kwargs.values(), prog) kwargs = dict(zip(kwargs.keys(), vals)) gate(*args, **kwargs) | regrefs # pylint:disable=expression-not-assigned else: # the gate has no arguments gate | regrefs # pylint:disable=expression-not-assigned,pointless-statement prog._target = bb.target["name"] if "shots" in bb.target["options"]: prog.run_options["shots"] = bb.target["options"]["shots"] if "cutoff_dim" in bb.target["options"]: prog.backend_options["cutoff_dim"] = bb.target["options"]["cutoff_dim"] return prog