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