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])
Example #2
0
    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))
Example #7
0
    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))
Example #11
0
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
Example #12
0
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