示例#1
0
    def optimize(self):
        """Simplify and optimize the program.

        The simplifications are based on the algebraic properties of the gates,
        e.g., combining two consecutive gates of the same gate family.

        Returns a copy of the program, sharing RegRefs with the original.

        See :func:`~strawberryfields.program_utils.optimize_circuit`.

        Returns:
            Program: optimized copy of the program
        """
        opt = self._linked_copy()
        opt.circuit = pu.optimize_circuit(self.circuit)
        return opt
示例#2
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:`~.DeviceSpec` 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.DeviceSpec): 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:`~.DeviceSpec`.

        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:
                if isinstance(device.modes, int):
                    # check that the number of modes is correct, if device.modes
                    # is provided as an integer
                    self.assert_number_of_modes(device)
                else:
                    # check that the number of measurements is within the allowed
                    # limits for each measurement type; device.modes will be a dictionary
                    self.assert_max_number_of_measurements(device)

        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)

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

        # create the compiled Program
        compiled = self._linked_copy()
        compiled.circuit = seq
        compiled._target = target
        compiled._compile_info = (device, compiler.short_name)

        # 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)

        # validate gate parameters
        if device is not None and device.gate_parameters:
            bb_device = bb.loads(device.layout)
            bb_compiled = sf.io.to_blackbird(compiled)

            try:
                user_parameters = match_template(bb_device, bb_compiled)
            except bb.utils.TemplateError as e:
                raise CircuitError(
                    "Program cannot be used with the compiler '{}' "
                    "due to incompatible topology.".format(
                        compiler.short_name)) from e

            device.validate_parameters(**user_parameters)

        return compiled
示例#3
0
    def compile(self, target, **kwargs):
        """Compile the program targeting the given circuit template.

        Validates the program against the given target, making sure all the Operations
        used are accepted by the target template.
        Additionally, depending on the target, the compilation may modify the quantum circuit
        into an equivalent circuit, e.g., by decomposing certain gates into sequences
        of simpler gates, or optimizing the gate ordering using commutation rules.

        The returned compiled Program shares its :class:`RegRefs <RegRef>` with the original,
        which makes it easier to access the measurement results, but also necessitates the
        :meth:`locking <lock>` of both the compiled program and the original to make sure the
        RegRef state remains consistent.

        Args:
            target (str, DeviceSpecs): short name of the target circuit template, or the template itself

        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.

        Returns:
            Program: compiled program
        """
        if isinstance(target, specs.DeviceSpecs):
            db = target
            target = db.short_name
        elif target in specs.backend_specs:
            db = specs.backend_specs[target]()
        else:
            raise ValueError(
                "Could not find target '{}' in Strawberry Fields template database"
                .format(target))

        if db.modes is not None:
            # subsystems may be created and destroyed, this is total number that has ever existed
            modes_total = len(self.reg_refs)
            if modes_total > db.modes:
                raise CircuitError(
                    "This program requires {} modes, but the target '{}' "
                    "only supports a {}-mode program".format(
                        modes_total, target, db.modes))

        def compile_sequence(seq):
            """Compiles the given Command sequence."""
            compiled = []
            for cmd in seq:
                op_name = cmd.op.__class__.__name__
                if op_name in db.decompositions:
                    # target can implement this op decomposed
                    if hasattr(cmd.op, 'decomp') and not cmd.op.decomp:
                        # user has requested application of the op as a primitive
                        if op_name in db.primitives:
                            compiled.append(cmd)
                            continue
                        else:
                            raise CircuitError(
                                "The operation {} is not a primitive for the target '{}'"
                                .format(cmd.op.__class__.__name__, target))
                    try:
                        kwargs = db.decompositions[op_name]
                        temp = cmd.op.decompose(cmd.reg, **kwargs)
                        # now compile the decomposition
                        temp = compile_sequence(temp)
                        compiled.extend(temp)
                    except NotImplementedError as err:
                        # Operation does not have _decompose() method defined!
                        # simplify the error message by suppressing the previous exception
                        raise err from None

                elif op_name in db.primitives:
                    # target can handle the op natively
                    compiled.append(cmd)

                else:
                    raise CircuitError(
                        "The operation {} cannot be used with the target '{}'."
                        .format(cmd.op.__class__.__name__, target))

            return compiled

        seq = compile_sequence(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)

        # does the device have its own compilation method?
        if db.compile is not None:
            seq = db.compile(seq)

        # create the compiled Program
        compiled = self._linked_copy()
        compiled.circuit = seq
        compiled.target = target
        return compiled
示例#4
0
    def compile(self, target, **kwargs):
        """Compile the program targeting the given circuit template.

        Validates the program against the given target, making sure all the Operations
        used are accepted by the target template.
        Additionally, depending on the target, the compilation may modify the quantum circuit
        into an equivalent circuit, e.g., by decomposing certain gates into sequences
        of simpler gates, or optimizing the gate ordering using commutation rules.

        The returned compiled Program shares its :class:`RegRefs <RegRef>` with the original,
        which makes it easier to access the measurement results, but also necessitates the
        :meth:`locking <lock>` of both the compiled program and the original to make sure the
        RegRef state remains consistent.

        Args:
            target (str, ~strawberryfields.circuitspecs.CircuitSpecs): short name of the target circuit specification, or the specification object itself

        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.

        Returns:
            Program: compiled program
        """
        if isinstance(target, specs.CircuitSpecs):
            db = target
            target = db.short_name
        elif target in specs.circuit_db:
            db = specs.circuit_db[target]()
        else:
            raise ValueError(
                "Could not find target '{}' in the Strawberry Fields circuit database."
                .format(target))

        if db.modes is not None:
            # subsystems may be created and destroyed, this is total number that has ever existed
            modes_total = len(self.reg_refs)
            if modes_total > db.modes:
                raise CircuitError(
                    "This program requires {} modes, but the target '{}' "
                    "only supports a {}-mode program".format(
                        modes_total, target, db.modes))

        seq = db.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)

        # does the circuit spec  have its own compilation method?
        if db.compile is not None:
            seq = db.compile(seq, self.register)

        # create the compiled Program
        compiled = self._linked_copy()
        compiled.circuit = seq
        compiled.target = target

        # get run options of compiled program
        # for the moment, shots is the only supported run option.
        if "shots" in kwargs:
            compiled.run_options["shots"] = kwargs["shots"]

        return compiled
示例#5
0
    def compile(self, target, **kwargs):
        """Compile the program targeting the given circuit specification.

        Validates the program against the given target, making sure all the
        :doc:`/introduction/ops` used are accepted by the target specification.

        Additionally, depending on the target, the compilation may modify the quantum circuit
        into an equivalent circuit, e.g., by decomposing certain gates into sequences
        of simpler gates, or optimizing the gate ordering using commutation rules.

        **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('gbs')

        Args:
            target (str, ~strawberryfields.circuitspecs.CircuitSpecs): short name of the target
                circuit specification, or the specification object itself

        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.

        Returns:
            Program: compiled program
        """
        if isinstance(target, specs.CircuitSpecs):
            db = target
            target = db.short_name
        elif target in specs.circuit_db:
            db = specs.circuit_db[target]()
        else:
            raise ValueError(
                "Could not find target '{}' in the Strawberry Fields circuit database."
                .format(target))

        if db.modes is not None:
            # subsystems may be created and destroyed, this is total number that has ever existed
            modes_total = len(self.reg_refs)
            if modes_total > db.modes:
                raise CircuitError(
                    "This program requires {} modes, but the target '{}' "
                    "only supports a {}-mode program".format(
                        modes_total, target, db.modes))

        seq = db.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)

        # does the circuit spec  have its own compilation method?
        if db.compile is not None:
            seq = db.compile(seq, self.register)

        # create the compiled Program
        compiled = self._linked_copy()
        compiled.circuit = seq
        compiled._target = db.short_name

        # get run options of compiled program
        # for the moment, shots is the only supported run option.
        if "shots" in kwargs:
            compiled.run_options["shots"] = kwargs["shots"]

        compiled.backend_options = {}
        if "cutoff_dim" in kwargs:
            compiled.backend_options["cutoff_dim"] = kwargs["cutoff_dim"]

        return compiled