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