Exemple #1
0
    def test_validate_gate_parameters_bb_program_invalid_param(self):
        """Test the ``.sf.program_utils.validate_gate_parameters`` function with a ``BlackbirdProgram``
        when a parameter value is invalid"""

        mock_layout = textwrap.dedent("""\
            name mock
            version 1.0

            S2gate({squeezing_amplitude_0}, 0.0) | [0, 1]
            """)

        device_dict = {
            "target": None,
            "layout": mock_layout,
            "modes": 2,
            "compiler": ["DummyCompiler"],
            "gate_parameters": {
                "squeezing_amplitude_0": [0, 1],
            },
        }

        mock_prog = bb.loads(mock_layout.format(squeezing_amplitude_0=42))
        device = sf.Device(spec=device_dict)

        with pytest.raises(ValueError, match="has invalid value 42"):
            validate_gate_parameters(mock_prog, device)
Exemple #2
0
    def test_validate_gate_parameters_sf_program_not_compiled_no_device(self):
        """Test the ``.sf.program_utils.validate_gate_parameters`` function with a not compiled ``sf.Program``
        when no device is passed"""
        mock_prog = sf.Program(2)
        with mock_prog.context as q:
            ops.S2gate(1) | q

        with pytest.raises(ValueError,
                           match="device is required to validate the circuit"):
            validate_gate_parameters(mock_prog)
Exemple #3
0
    def test_validate_gate_parameters_sf_program_compiled(self):
        """Test the ``.sf.program_utils.validate_gate_parameters`` function with a compiled ``sf.Program``
        when no device is passed"""

        mock_layout = textwrap.dedent("""\
            name mock
            version 1.0

            S2gate({squeezing_amplitude_0}, 0.0) | [0, 1]
            """)

        device_dict = {
            "target": None,
            "layout": mock_layout,
            "modes": 2,
            "compiler": ["DummyCompiler"],
            "gate_parameters": {
                "squeezing_amplitude_0": [0, 1],
            },
        }

        mock_prog = sf.Program(2)
        with mock_prog.context as q:
            ops.S2gate(1) | q

        device = sf.Device(spec=device_dict)

        mock_prog._compile_info = (device, "compiler")

        assert validate_gate_parameters(mock_prog)
Exemple #4
0
    def test_validate_gate_parameters_bb_program_no_device(self):
        """Test the ``.sf.program_utils.validate_gate_parameters`` function with a ``BlackbirdProgram``
        when a parameter value is invalid"""

        mock_layout = textwrap.dedent("""\
            name mock
            version 1.0

            S2gate({squeezing_amplitude_0}, 0.0) | [0, 1]
            """)

        mock_prog = bb.loads(mock_layout.format(squeezing_amplitude_0=42))

        with pytest.raises(
                ValueError,
                match="device is required when validating a Blackbird program"
        ):
            validate_gate_parameters(mock_prog)
Exemple #5
0
    def test_validate_gate_parameters_bb_program(self):
        """Test the ``.sf.program_utils.validate_gate_parameters`` function with a ``BlackbirdProgram``"""

        mock_layout = textwrap.dedent("""\
            name mock
            version 1.0

            S2gate({squeezing_amplitude_0}, 0.0) | [0, 1]
            """)

        device_dict = {
            "target": None,
            "layout": mock_layout,
            "modes": 2,
            "compiler": ["DummyCompiler"],
            "gate_parameters": {
                "squeezing_amplitude_0": [0, 1],
            },
        }

        mock_prog = bb.loads(mock_layout.format(squeezing_amplitude_0=1))
        device = sf.Device(spec=device_dict)

        assert validate_gate_parameters(mock_prog, device)
Exemple #6
0
    def compile(self, *, device=None, compiler=None, **kwargs):
        """Compile the program given a Strawberry Fields photonic compiler, or
        hardware device specification.

        The compilation process can involve up to three stages:

        1. **Validation:** Validates properties of the program, including number of modes and
           allowed operations, making sure all the :doc:`/introduction/ops` used are accepted by the
           compiler.

        2. **Decomposition:** Once the program has been validated, decomposition are performed,
           transforming certain gates into sequences of simpler gates.

        3. **General compilation:** Finally, the compiler might specify bespoke compilation logic
           for transforming the  quantum circuit into an equivalent circuit which can be executed
           by the target device.

        **Example:**

        The ``gbs`` compile target will
        compile a circuit consisting of Gaussian operations and Fock measurements
        into canonical Gaussian boson sampling form.

        >>> prog2 = prog.compile(compiler="gbs")

        For a hardware device a :class:`~.Device` object, and optionally a specified compile strategy,
        must be supplied. If no compile strategy is supplied the default compiler from the device
        specification is used.

        >>> eng = sf.RemoteEngine("X8")
        >>> device = eng.device_spec
        >>> prog2 = prog.compile(device=device, compiler="Xcov")

        Args:
            device (~strawberryfields.Device): device specification object to use for
                program compilation
            compiler (str, ~strawberryfields.compilers.Compiler): Compiler name or compile strategy
                to use. If a device is specified, this overrides the compile strategy specified by
                the hardware :class:`~.Device`.

        Keyword Args:
            optimize (bool): If True, try to optimize the program by merging and canceling gates.
                The default is False.
            warn_connected (bool): If True, the user is warned if the quantum circuit is not weakly
                connected. The default is True.
            shots (int): Number of times the program measurement evaluation is repeated. Passed
                along to the compiled program's ``run_options``.

        Returns:
            Program: compiled program
        """
        # pylint: disable=too-many-branches
        if device is None and compiler is None:
            raise ValueError(
                "Either one or both of 'device' and 'compiler' must be specified"
            )

        def _get_compiler(compiler_or_name):
            if compiler_or_name in compiler_db:
                return compiler_db[compiler_or_name]()

            if isinstance(compiler_or_name, Compiler):
                return compiler_or_name

            raise ValueError(f"Unknown compiler '{compiler_or_name}'.")

        if device is not None:
            target = device.target

            if compiler is None:
                # get the default compiler from the device spec
                compiler = compiler_db[device.default_compiler]()
            else:
                compiler = _get_compiler(compiler)

            if device.modes is not None:
                # check that the number of modes is correct, if device.modes is provided
                # as an integer, or that the number of measurements is within the allowed
                # limits for each measurement type, if `device.modes` is a dictionary
                self.assert_modes(device)

            # if a device layout exist and the device default compiler is the same as
            # the requested compiler, initialize the circuit in the compiler class
            if device.layout and device.default_compiler == compiler.short_name:
                compiler.init_circuit(device.layout)
        else:
            compiler = _get_compiler(compiler)
            target = compiler.short_name

        seq = compiler.decompose(self.circuit)

        if kwargs.get("warn_connected", True):
            DAG = pu.list_to_DAG(seq)
            temp = nx.algorithms.components.number_weakly_connected_components(
                DAG)
            if temp > 1:
                warnings.warn(
                    "The circuit consists of {} disconnected components.".
                    format(temp))

        # run optimizations
        if kwargs.get("optimize", False):
            seq = pu.optimize_circuit(seq)

        compiled = self._linked_copy()

        seq = compiler.compile(seq, self.register)

        # create the compiled Program
        compiled.circuit = seq
        compiled._target = target  # pylint: disable=protected-access
        compiled._compile_info = (device, compiler.short_name)  # pylint: disable=protected-access

        # parameters are updated if necessary due to compiler changes
        compiler.update_params(compiled, device)

        # Get run options of compiled program.
        run_options = {
            k: kwargs[k]
            for k in ALLOWED_RUN_OPTIONS if k in kwargs
        }
        compiled.run_options.update(run_options)

        # set backend options of the program
        backend_options = {
            k: kwargs[k]
            for k in kwargs if k not in ALLOWED_RUN_OPTIONS
        }
        compiled.backend_options.update(backend_options)

        if kwargs.get("realistic_loss", False):
            try:
                compiler.add_loss(compiled, device)
            except NotImplementedError:
                warnings.warn(
                    f"Compiler {compiler} does not support adding realistic loss."
                )

        # if device spec has allowed gate parameters, validate the applied gate parameters
        if device and device.gate_parameters:
            if not device.layout:
                raise ValueError(
                    "Gate parameters cannot be validated. Device specification is missing a "
                    "circuit layout.")

            pu.validate_gate_parameters(compiled)

        return compiled