def test_gate_measured_par(self): """Test a gate with a MeasuredParameter argument.""" bb_script = """\ name test_program version 1.0 MeasureX | 0 Dgate(q0) | 1 Rgate(2*q0) | 2 """ bb = blackbird.loads(bb_script) prog = io.to_program(bb) assert len(prog) == 3 cmd = prog.circuit[1] assert cmd.op.__class__.__name__ == "Dgate" p = cmd.op.p[0] assert isinstance(p, MeasuredParameter) assert p.regref.ind == 0 assert cmd.reg[0].ind == 1 cmd = prog.circuit[2] assert cmd.op.__class__.__name__ == "Rgate" p = cmd.op.p[0] assert par_is_symbolic(p) # symbolic expression assert cmd.reg[0].ind == 2
def test_gate_free_par(self): """Test a FreeParameter with some transformations converts properly""" bb_script = """\ name test_program version 1.0 Dgate(1-{ALPHA}, 0) | 0 # keyword arg, compound expr Rgate(theta={foo_bar1}) | 0 # keyword arg, atomic Dgate({ALPHA}**2, 0) | 0 # positional arg, compound expr Rgate({foo_bar2}) | 0 # positional arg, atomic """ bb = blackbird.loads(bb_script) prog = io.to_program(bb) assert prog.free_params.keys() == set( ["foo_bar1", "foo_bar2", "ALPHA"]) assert len(prog) == 4 assert prog.circuit cmd = prog.circuit[0] assert cmd.op.__class__.__name__ == "Dgate" p = cmd.op.p[0] assert par_is_symbolic(p) assert cmd.reg[0].ind == 0 cmd = prog.circuit[1] assert cmd.op.__class__.__name__ == "Rgate" p = cmd.op.p[0] assert isinstance(p, FreeParameter) assert p.name == "foo_bar1" assert cmd.reg[0].ind == 0 cmd = prog.circuit[2] assert cmd.op.__class__.__name__ == "Dgate" p = cmd.op.p[0] assert par_is_symbolic(p) assert cmd.reg[0].ind == 0 cmd = prog.circuit[3] assert cmd.op.__class__.__name__ == "Rgate" p = cmd.op.p[0] assert isinstance(p, FreeParameter) assert p.name == "foo_bar2" assert cmd.reg[0].ind == 0
def apply_op(self, cmd, modes, t): """Apply a particular operation on register q at timestep t.""" params = cmd.op.p.copy() for i, _ in enumerate(params): if par_is_symbolic(params[i]): params[i] = self.parameters[params[i].name][t % self.timebins] self.append(cmd.op.__class__(*params), modes)
def apply_op(self, cmd, q, t): """Apply a particular operation on register q at timestep t""" params = cmd.op.p.copy() if par_is_symbolic(params[0]): arg_index = int(params[0].name[1:]) params[0] = self.tdm_params[arg_index][t % self.timebins] self.append(cmd.op.__class__(*params), get_modes(cmd, q))
def to_blackbird(prog, version="1.0"): """Convert a Strawberry Fields Program to a Blackbird Program. Args: prog (Program): the Strawberry Fields program version (str): Blackbird script version number Returns: blackbird.BlackbirdProgram: """ bb = blackbird.BlackbirdProgram(name=prog.name, version=version) # TODO not sure if this makes sense: the program has *already been* compiled using this target if prog.target is not None: # set the target bb._target["name"] = prog.target # set the run options if prog.run_options: bb._target["options"].update(prog.run_options) if prog.backend_options: bb._target["options"].update(prog.backend_options) # fill in the quantum circuit for cmd in prog.circuit: op = {"kwargs": {}, "args": []} op["op"] = cmd.op.__class__.__name__ op["modes"] = [i.ind for i in cmd.reg] if "Measure" in op["op"]: # special case to take into account 'select' keyword argument if cmd.op.select is not None: op["kwargs"]["select"] = cmd.op.select if cmd.op.p: # argument is quadrature phase op["kwargs"]["phi"] = cmd.op.p[0] if op["op"] == "MeasureFock": # special case to take into account 'dark_counts' keyword argument if cmd.op.dark_counts is not None: op["kwargs"]["dark_counts"] = cmd.op.dark_counts else: for a in cmd.op.p: if sfpar.par_is_symbolic(a): # SymPy object, convert to string a = str(a) op["args"].append(a) bb._operations.append(op) return bb
def test_par_is_symbolic(self, r): """Recognizing symbolic parameters.""" p = FreeParameter("x") q = MeasuredParameter(RegRef(0)) assert not par_is_symbolic(r) assert par_is_symbolic(pf.sin(r)) assert par_is_symbolic(q) assert par_is_symbolic(p) assert par_is_symbolic(pf.sin(p)) assert par_is_symbolic(p + r) assert par_is_symbolic(p - r) assert par_is_symbolic(p * r) assert par_is_symbolic(p / r) assert par_is_symbolic(p**r) assert par_is_symbolic(p - p) # no simplification # object array with symbols a = np.array([[0.1, 3, 0], [0.3, 2, p], [1, 2, 4]]) assert a.dtype == object assert par_is_symbolic(a) # object array, no symbols a = np.array([[0.1, 3, 0], [0.3, 2, 0], [1, 2, 4]], dtype=object) assert a.dtype == object assert not par_is_symbolic(a) # float array, no symbols a = np.array([[0.1, 3, 0], [0.3, 2, 0], [1, 2, 4]]) assert a.dtype != object assert not par_is_symbolic(a) assert par_is_symbolic(pf.sin(a))
def to_blackbird(prog: Program, version: str = "1.0") -> blackbird.BlackbirdProgram: """Convert a Strawberry Fields Program to a Blackbird Program. Args: prog (Program): the Strawberry Fields program version (str): Blackbird script version number Returns: blackbird.BlackbirdProgram: """ bb = blackbird.BlackbirdProgram(name=prog.name, version=version) bb._modes = set(prog.reg_refs.keys()) isMeasuredParameter = lambda x: isinstance(x, sfpar.MeasuredParameter) # not sure if this makes sense: the program has *already been* compiled using this target if prog.target is not None: # set the target bb._target["name"] = prog.target # set the run options if prog.run_options: bb._target["options"].update(prog.run_options) if prog.backend_options: bb._target["options"].update(prog.backend_options) # fill in the quantum circuit for cmd in prog.circuit: op = {"kwargs": {}, "args": []} op["op"] = cmd.op.__class__.__name__ op["modes"] = [i.ind for i in cmd.reg] if "Measure" in op["op"]: # special case to take into account 'select' keyword argument if cmd.op.select is not None: op["kwargs"]["select"] = cmd.op.select if cmd.op.p: # argument is quadrature phase op["args"] = cmd.op.p if op["op"] == "MeasureFock": # special case to take into account 'dark_counts' keyword argument if cmd.op.dark_counts is not None: op["kwargs"]["dark_counts"] = cmd.op.dark_counts else: for a in cmd.op.p: if sfpar.par_is_symbolic(a): # SymPy object, convert to string if any(map(isMeasuredParameter, a.free_symbols)): # check if there are any measured parameters in `a` a = blackbird.RegRefTransform(a) else: a = str(a) op["args"].append(a) # If program is a TDMProgram then add the looped-over arrays to the # blackbird program. `prog.loop_vars` are symbolic parameters (e.g. # `{p0}`), which should be replaced with `p.name` (e.g. `p0`) inside the # Blackbird operation (keyword) arguments. if isinstance(prog, TDMProgram): for p in prog.loop_vars: for i, ar in enumerate(op["args"]): if str(p) == str(ar): op["args"][i] = p.name for k, v in op["kwargs"].items(): if str(p) == str(v): op["kwargs"][k] = p.name bb._operations.append(op) # add the specific "tdm" metadata to the Blackbird program if isinstance(prog, TDMProgram): bb._type["name"] = "tdm" bb._type["options"].update({ "temporal_modes": prog.timebins, }) bb._var.update({ f"{p.name}": np.array([prog.tdm_params[i]]) for i, p in enumerate(prog.loop_vars) }) return bb
def to_xir(prog: Program, **kwargs) -> xir.Program: """Convert a Strawberry Fields Program to an XIR Program. Args: prog (Program): the Strawberry Fields program Keyword Args: add_decl (bool): Whether gate and output declarations should be added to the XIR program. Default is ``False``. Returns: xir.Program """ xir_prog = xir.Program() add_decl = kwargs.get("add_decl", False) if isinstance(prog, TDMProgram): xir_prog.add_option("_type_", "tdm") xir_prog.add_option("N", prog.N) for i, p in enumerate(prog.tdm_params): xir_prog.add_constant(f"p{i}", _listr(p)) if prog.name: xir_prog.add_option("_name_", prog.name) if prog.target: xir_prog.add_option("target", prog.target) # pylint: disable=protected-access if "cutoff_dim" in prog.backend_options: xir_prog.add_option("cutoff_dim", prog.backend_options["cutoff_dim"]) if "shots" in prog.run_options: xir_prog.add_option("shots", prog.run_options["shots"]) # fill in the quantum circuit for cmd in prog.circuit or []: name = cmd.op.__class__.__name__ wires = tuple(i.ind for i in cmd.reg) if "Measure" in name: if add_decl: output_decl = xir.Declaration(name, type_="out", wires=wires) xir_prog.add_declaration(output_decl) params = {} if cmd.op.p: # argument is quadrature phase a = cmd.op.p[0] if a in getattr(prog, "loop_vars", ()): params["phi"] = a.name else: params["phi"] = a # special case to take into account 'select' keyword argument if cmd.op.select is not None: params["select"] = cmd.op.select if name == "MeasureFock": # special case to take into account 'dark_counts' keyword argument if cmd.op.dark_counts is not None: params["dark_counts"] = cmd.op.dark_counts else: if add_decl: if name not in [ gdecl.name for gdecl in xir_prog.declarations["gate"] ]: params = [f"p{i}" for i, _ in enumerate(cmd.op.p)] gate_decl = xir.Declaration(name, type_="gate", params=params, wires=tuple(range(len(wires)))) xir_prog.add_declaration(gate_decl) params = [] for i, a in enumerate(cmd.op.p): if sfpar.par_is_symbolic(a): # try to evaluate symbolic parameter try: a = sfpar.par_evaluate(a) except sfpar.ParameterError: # if a tdm param if a in getattr(prog, "loop_vars", ()): a = a.name # if a pure symbol (free parameter), convert to string elif a.is_symbol: a = a.name # else, assume it's a symbolic function and replace all free parameters # with string representations else: symbolic_func = a.copy() for s in symbolic_func.free_symbols: symbolic_func = symbolic_func.subs(s, s.name) a = str(symbolic_func) elif isinstance(a, str): pass elif isinstance(a, Iterable): # if an iterable, make sure it only consists of lists and Python types a = _listr(a) params.append(a) op = xir.Statement(name, params, wires) xir_prog.add_statement(op) return xir_prog
def compile(self, *, device=None, compiler=None): """Compile the time-domain program given a Strawberry Fields photonic hardware device specification. The compilation checks that the program matches the device and sets the compile information and the program target to the correct device target. 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`. If no compiler is passed, the default TDM compiler is used. Currently, the only other allowed compilers are "gaussian" and "passive". Returns: Program: compiled program """ alt_compilers = ("gaussian", "passive") if compiler != "TDM": if compiler in alt_compilers or getattr( device, "default_compiler") in alt_compilers: return super().compile(device=device, compiler=compiler) if device is not None: device_layout = bb.loads(device.layout) if device_layout.programtype["name"] != "tdm": raise TypeError( 'TDM compiler only supports "tdm" type device specification layouts. ' "Received {} type.".format( device_layout.programtype["name"])) if device.modes is not None: self.assert_number_of_modes(device) # First check: the gates are in the correct order program_gates = [ cmd.op.__class__.__name__ for cmd in self.rolled_circuit ] device_gates = [op["op"] for op in device_layout.operations] if device_gates != program_gates: raise CircuitError( "The gates or the order of gates used in the Program is incompatible with the device '{}' " .format(device.target)) # Second check: the gates act on the correct modes program_modes = [[r.ind for r in cmd.reg] for cmd in self.rolled_circuit] device_modes = [op["modes"] for op in device_layout.operations] if program_modes != device_modes: raise CircuitError( "Program cannot be used with the device '{}' " "due to incompatible mode ordering.".format(device.target)) # Third check: the parameters of the gates are valid # We will loop over the different operations in the device specification for i, operation in enumerate(device_layout.operations): # We obtain the name of the parameter(s) param_names = operation["args"] program_params_len = len(self.rolled_circuit[i].op.p) device_params_len = len(param_names) # The next if is to make sure we do not flag incorrectly things like Sgate(r,0) being different Sgate(r) # This assumes that parameters other than the first one are zero if not explicitly stated. if device_params_len < program_params_len: for j in range(1, program_params_len): if self.rolled_circuit[i].op.p[j] != 0: raise CircuitError( "Program cannot be used with the device '{}' " "due to incompatible parameter.".format( device.target)) # Now we will check explicitly if the parameters in the program match for k, param_name in enumerate(param_names): # Obtain the value of the corresponding parameter in the program program_param = self.rolled_circuit[i].op.p[k] # make sure that hardcoded parameters in the device layout are correct if not isinstance(param_name, str) and not par_is_symbolic(param_name): if not program_param == param_name: raise CircuitError( "Program cannot be used with the device '{}' " "due to incompatible parameter. Parameter has value '{}' " "while its valid value is '{}'".format( device.target, program_param, param_name)) continue # Obtain the relevant parameter range from the device param_range = device.gate_parameters.get(str(param_name)) if param_range is None: raise CircuitError( "Program cannot be used with the device '{}' " "due to parameter '{}' not found in device specification." .format(device.target, param_name)) if par_is_symbolic(program_param): # If it is a symbolic value go and lookup its corresponding list in self.tdm_params local_p_vals = self.parameters.get( program_param.name, []) for x in local_p_vals: if not x in param_range: raise CircuitError( "Program cannot be used with the device '{}' " "due to incompatible parameter. Parameter has value '{}' " "while its valid range is '{}'".format( device.target, x, param_range)) else: # If it is a numerical value check directly if not program_param in param_range: raise CircuitError( "Program cannot be used with the device '{}' " "due to incompatible parameter. Parameter has value '{}' " "while its valid range is '{}'".format( device.target, program_param, param_range)) self._compile_info = (device, "TDM") self._target = device.target return self raise CircuitError( "TDM programs cannot be compiled without a valid device specification." )