def test_invalid_multiple_match(self): """test exception raised if match involving duplicated template parameters results in inconsistent values""" template = loads( dedent("""\ name prog version 0.0 Dgate(-{r}, 0.45) | 1 Vac | 2 Sgate({r}, {phi}) | 0 """)) program = loads( dedent("""\ name prog version 0.0 Sgate(1.45, -1.432*pi) | 0 Dgate(-0.543, 0.45) | 1 Vac | 2 """)) with pytest.raises(TemplateError, match="matches inconsistent values"): match_template(template, program)
def test_multiple_match(self): """test a match involving duplicated template parameters""" template = loads( dedent("""\ name prog version 0.0 Dgate(-{r}, 0.45) | 1 Vac | 2 Sgate({r}, {phi}) | 0 """)) program = loads( dedent("""\ name prog version 0.0 Sgate(0.543, -1.432*pi) | 0 Dgate(-0.543, 0.45) | 1 Vac | 2 """)) res = match_template(template, program) expected = {'r': 0.543, 'phi': -1.432 * np.pi} assert res == expected
def test_match(self): """test a simple match""" template = loads( dedent("""\ name prog version 0.0 Dgate(0.543, 0.45) | 1 Vac | 2 Sgate({r}, {phi}) | 0 """)) program = loads( dedent("""\ name prog version 0.0 Sgate(0.543, -1.432*pi) | 0 Dgate(0.543, 0.45) | 1 Vac | 2 """)) res = match_template(template, program) expected = {'r': 0.543, 'phi': -1.432 * np.pi} assert res == expected
def test_implicit_variable_solving(self): """test solver is used for parameter arithmetic""" template = loads( dedent("""\ name prog version 0.0 Dgate(0.543, 0.45) | 1 Sgate(2*{r}, {phi}-1) | 0 """)) program = loads( dedent("""\ name prog version 0.0 Sgate(0.543, -1.432*pi) | 0 Dgate(0.412, 0.45) | 1 """)) res = match_template(template, program) expected = {'r': 0.543 / 2, 'phi': -1.432 * np.pi + 1} assert np.allclose(res['r'], expected['r']) assert np.allclose(res['phi'], expected['phi'])
def test_different_version(self): """Test exception raised if versions don't match""" template = loads( dedent("""\ name prog version 0.0 Sgate({r}, {phi}) | 0 """)) program = loads( dedent("""\ name prog version 0.1 Sgate(0.543, pi) | 0 """)) with pytest.raises(TemplateError, match="Mismatching Blackbird version"): match_template(template, program)
def test_different_target(self): """Test exception raised if targets don't match""" template = loads( dedent("""\ name prog version 0.0 target dev1 Sgate({r}, {phi}) | 0 """)) program = loads( dedent("""\ name prog version 0.0 target dev2 Sgate(0.543, pi) | 0 """)) with pytest.raises(TemplateError, match="Mismatching target"): match_template(template, program)
def test_multiple_match_tdm(self): """test a match involving duplicated template parameters with p-type variables""" template = loads( dedent("""\ name template_td3_fake version 1.0 target TD3_fake (shots=1) type tdm (temporal_modes=3, copies=1) Sgate({s}, 0.0) | 43 Rgate({r}) | 43 BSgate({bs}, 1.5707963267948966) | [42, 43] Rgate({offset}) | 43 MeasureFock() | 0 """)) program = loads( dedent("""\ name None version 1.0 target TD3_fake (shots=1) type tdm (temporal_modes=259) float array p0 = 0.999, 0.965, 0.678 float array p1 = 0.211, 0.042, 0.347 float array p2 = 0.042, 0.673, 0.924 int array p3 = 4, 4, 4 Sgate(p0, 0.0) | 43 Rgate(p1) | 43 BSgate(p2, 1.5707963267948966) | [42, 43] Rgate(p3) | 43 MeasureFock() | 0 """)) res = match_template(template, program) expected = { 's': np.array([[0.999, 0.965, 0.678]]), 'r': np.array([[0.211, 0.042, 0.347]]), 'bs': np.array([[0.042, 0.673, 0.924]]), 'offset': np.array([[4, 4, 4]]) } assert res.keys() == expected.keys() assert all( np.allclose(r, e) for r, e in zip(res.values(), expected.values()))
def test_different_program(self): """test exception raised if template not the same program""" template = loads( dedent("""\ name prog version 0.0 Dgate(0.543, 0.45) | 0 Sgate({r}, {phi}) | 0 """)) program = loads( dedent("""\ name prog version 0.0 Sgate(0.543, -1.432*pi) | 0 Dgate(0.543, 0.45) | 0 """)) with pytest.raises(TemplateError, match="Not the same program"): match_template(template, program)
def test_too_many_template_parameters(self): """test exception raised if gate contains multiple template parameters""" template = loads( dedent("""\ name prog version 0.0 Dgate(0.543, 0.45) | 1 Sgate({r}+{s}, {phi}) | 0 """)) program = loads( dedent("""\ name prog version 0.0 Sgate(0.543, -1.432*pi) | 0 Dgate(0.543, 0.45) | 1 """)) with pytest.raises(TemplateError, match="only supports one template parameter"): match_template(template, program)
def test_not_program(self): """test exception raised if second argument is a template""" template = loads( dedent("""\ name prog version 0.0 Dgate(0.543, 0.45) | 0 Sgate({r}, {phi}) | 0 """)) program = loads( dedent("""\ name prog version 0.0 Sgate(0.543, -1.432*pi) | 0 Dgate({alpha}, 0.45) | 0 """)) with pytest.raises(TemplateError, match="Argument 2 cannot be a template"): match_template(template, program)
def test_not_template(self): """test exception raised if first argument is not a template""" template = loads( dedent("""\ name prog version 0.0 Dgate(0.543, 0.45) | 1 Sgate(0.543, 0.123) | 0 """)) program = loads( dedent("""\ name prog version 0.0 Sgate(0.543, -1.432*pi) | 0 Dgate(0.543, 0.45) | 1 """)) with pytest.raises(TemplateError, match="Argument 1 is not a template"): match_template(template, program)
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