def build(self, context=None): """Override the build method to provide the native gates.""" if context is not None: raise JaqalError( "Do not provide a context to the build method of CircuitBuilder" ) return super().build(self.native_gates)
def build_let(self, sexpression, _context, _gate_context): args = list(sexpression.args) if len(args) != 2: raise JaqalError( f"let statement requires two arguments, found {args}") name, value = args return Constant(name, as_integer(value))
def build_usepulses(self, sexpression, context, gate_context): # Process a from ... usepulses * statement if autoload_pulses ret = {} if not self.autoload_pulses: return ret name, filt = sexpression.args module = import_module(str(name)) native_gates = module.NATIVE_GATES if (filt is not all) and (filt != "*"): raise JaqalError("Only from ... usepulses * currently supported.") for g in native_gates: # inject_pulses overrides usepulses if self.inject_pulses and g.name in self.inject_pulses: continue # but later usepulses override earlier imports gate_context[g.name] = g ret[g.name] = g # Separate imported context from local return ret
def _store(self, cmd): gate = cmd.gate if len(self.measure_accumulator) == len(self._q) and len(self._q) > 0: self.measure_accumulator = set() self._block.gate("prepare_all") if gate == Allocate: qid = self._mapped_qubit_id(cmd.qubits[0][0]) self._circuit.stretch_register(qid + 1) elif gate == Deallocate: pass # The user might stop caring about the qubit, but we need to keep it around. elif gate == Measure: qid = self._mapped_qubit_id(cmd.qubits[0][0]) if qid in self.measure_accumulator: raise JaqalError("Can't measure qubit %d twice!" % qid) else: self.measure_accumulator.add(qid) if len(self.measure_accumulator) == len(self._q): self._block.gate("measure_all") elif gate == Barrier: self._block = UnscheduledBlockBuilder() self._circuit.expression.append(self._block.expression) elif type(gate) in one_qubit_gates: qid = self._mapped_qubit_id(cmd.qubits[0][0]) if qid in self.measure_accumulator: raise JaqalError( "Can't do gates in the middle of measurement!") else: self._block.gate( *self.one_qubit_gates[type(gate)](gate, self._q[qid])) elif type(gate) in two_qubit_gates: qids = [self._mapped_qubit_id(qb[0]) for qb in cmd.qubits] for qid in qids: if qid in self.measure_accumulator: raise JaqalError( "Can't do gates in the middle of measurement!") self._block.gate(*self.two_qubit_gates[type(gate)]( gate, *[self._q[qid] for qid in qids])) else: raise JaqalError("Unknown instruction! %s" % gate)
def visit_loop_statement(self, repetition_count, block): """Validate the repetition count is a non-negative integer.""" num = self.extract_signed_number(repetition_count) if not is_non_negative_integer(num): raise JaqalError( f"While resolving let values: illegal loop statement count: {num}" ) return self.make_loop_statement(repetition_count, block)
def visit_array_declaration(self, identifier, size): """Validate the size is a non-negative integer.""" num = self.extract_signed_number(size) if not is_non_negative_integer(num): raise JaqalError( f"While resolving let values: illegal array declaration size: {num}" ) return self.make_array_declaration(identifier, size)
def visit_macro_definition(self, name, arguments, block): name_id = self.extract_identifier(name) arg_ids = [self.extract_identifier(arg) for arg in arguments] if name_id in self.macro_mapping: raise JaqalError(f"Redefinition of {name_id} macro") self.macro_mapping[name_id] = MacroRecord( arg_ids, self.deconstruct_macro_gate_block(block)) return self.make_macro_definition(name, arguments, block)
def build_noiseless_native_model( registers, gates, ): """Builds a noise model for each Jaqal gate :param register: the Jaqal registers that the gates may act on :param gates: a dictionary of Jaqal gates :return: a pyGSTi noise model object """ gate_names = [] unitaries = {} availability = {} for g in gates.values(): name = f"G{g.name.lower()}" gate_names.append(name) if len(g.quantum_parameters) == 0: # AER: We are special casing prepare and measurements right now if g.name in ("prepare_all", "measure_all"): unitaries[name] = np.identity(2) else: raise JaqalError(f"{g.name} not supported") continue if len(g.classical_parameters) > 0: unitaries[name] = g._ideal_unitary_pygsti else: unitaries[name] = g.ideal_unitary() if len(g.quantum_parameters) > 1: availability[name] = "all-permutations" fundamental_registers = [ r for r in registers.values() if r._alias_from is None ] if len(fundamental_registers) > 1: print( "Warning: More than one physical register name in use; ordering may be borked." ) physical_qubit_list = [] for r in fundamental_registers: for q in r: physical_qubit_list.append(q._name) num_qubits = len(physical_qubit_list) target_model = pygsti.construction.build_localnoise_model( nQubits=num_qubits, gate_names=gate_names, nonstd_gate_unitaries=unitaries, availability=availability, qubit_labels=physical_qubit_list, parameterization="static unitary", ) return target_model
def run(self): """ Does not run a previously loaded Jaqal program on an abstraction of the QSCOUT hardware. :raises JaqalError: Because the Quil API should not be used to try to execute programs on QSCOUT. """ raise JaqalError( "QSCOUT cannot run programs through the Quil API. Generate a Jaqal file with compile() and submit it directly to the QSCOUT team." )
def parse_jaqal_string( jaqal, override_dict=None, expand_macro=False, expand_let=False, expand_let_map=False, return_usepulses=False, inject_pulses=None, autoload_pulses=True, ): """Parse a string written in Jaqal into core types. :param str jaqal: The Jaqal code. :param override_dict: An optional dictionary that overrides let statements in the Jaqal code. Note: all keys in this dictionary must exist as let statements or an error will be raised. :type override_dict: dict[str, float] :param bool expand_macro: Replace macro invocations by their body while parsing. :param bool expand_let: Replace let constants by their value while parsing. :param bool expand_let_map: Replace let constants and mapped qubits while parsing. expand_let is ignored if this is True. :param bool return_usepulses: Whether to both add a second return value and populate it with the usepulses statement. :param inject_pulses: If given, use these pulses specifically. :param bool autoload_pulses: Whether to employ the usepulses statement for parsing. Requires appropriate gate definitions. :return: The circuit representation of the file and usepulses if requested. usepulses is stored in a dict under the key 'usepulses'. It is itself a dict mapping :class:`Identifier` objects to what the import, which may be the special symbol all. """ # The interface will automatically expand macros and scrape let, map, and register metadata. iface = Interface(jaqal, allow_no_usepulses=True) # Do some minimal processing to fill in all let and map values. The interface does not # automatically do this as they may rely on values from override_dict. let_dict = iface.make_let_dict(override_dict) tree = iface.tree expand_let = expand_let or expand_let_map if expand_macro: tree = iface.resolve_macro(tree) if expand_let: tree = iface.resolve_let(tree, let_dict=let_dict) if expand_let_map: tree = iface.resolve_map(tree) circuit = convert_to_circuit( tree, inject_pulses=inject_pulses, autoload_pulses=autoload_pulses ) if return_usepulses: ret_extra = {"usepulses": iface.usepulses} ret_value = (circuit, ret_extra) else: ret_value = circuit if sum(reg.fundamental for reg in circuit.registers.values()) > 1: raise JaqalError(f"Circuit has too many registers: {list(circuit.registers)}") return ret_value
def __getitem__(self, key): name = make_item_name(self, key) if isinstance(key, slice): raise JaqalError( "Anonymous slices are not currently supported; slice only in a map statement." ) # But if the backend ever supports it, just replace the above line with the below line: # return Register(self.name + '[' + str(key) + ']', alias_from=self, alias_slice=key) else: return NamedQubit(name, self, key)
def resolve_constant(self, const): """Return the value for the given constant defined either in the override_dict or in the circuit itself.""" if const.name in self.override_dict: return self.override_dict[const.name] if isinstance(const.value, (int, float)): return const.value else: # I don't think this can happen raise JaqalError(f"Constant {const.name} has non-numeric value")
def visit_array_slice(self, identifier, index_slice): """Validate all parts of the slice are integers.""" for value in [index_slice.start, index_slice.stop, index_slice.step]: if value is not None: num = self.extract_signed_number(value) if not is_integer(num): raise JaqalError( f"While resolving let values: illegal array slice value {num}" ) return self.make_array_slice(identifier, index_slice)
def visit_GateStatement(self, gate, context=None): if gate.name == self.p_gate: # We allow for multiple prepare_all's in a row. But gates between those # prepare_all's do nothing. Notice also, we would not yet know what the # measured or used qubits are, if we had partial measurements. That would # have to wait until the measurement. c = self.current = Trace(self.address[:]) elif gate.name == self.m_gate: if self.current is None: raise JaqalError(f"{self.p_gate} must follow a {self.m_gate}") self.current.end = self.address[:] self.current.used_qubits = self.qubits self.subcircuits.append(self.current) self.current = None else: if self.current is None: raise JaqalError(f"gates must follow a {self.p_gate}") return super().visit_GateStatement(gate, context=context)
def quantum_parameters(self): """The quantum parameters (qubits or registers) this gate takes. :raises JaqalError: If this gate has parameters without type annotations; for example, if it is a macro. """ try: return [param for param in self.parameters if not param.classical] except JaqalError: pass raise JaqalError("Gate {self.name} has a parameter with unknown type")
def build(self, expression, context=None, gate_context=None): """Build the appropriate thing based on the expression.""" if context is None: context = self.make_context() if gate_context is None: gate_context = self.make_gate_context() if isinstance(expression, str): # Identifiers if expression in context: return context[expression] raise JaqalError(f"Identifier {expression} not found in context") if not SExpression.is_convertible(expression): # This is either a number used as a gate argument or an already-created type. return expression sexpression = SExpression(expression) method_name = f"build_{sexpression.command}" if not hasattr(self, method_name): raise JaqalError( f"Cannot handle object of type {sexpression.command}") return getattr(self, method_name)(sexpression, context, gate_context)
def __init__(self, name, value): if isinstance(value, Constant): super().__init__(name, value.kind) elif isinstance(value, float): super().__init__(name, ParamType.FLOAT) elif isinstance(value, int): super().__init__(name, ParamType.INT) else: raise JaqalError( f"Invalid/non-numeric value {value} for constant {name}!") self._value = value
def build_map(self, sexpression, context, gate_context): args = list(sexpression.args) if len(args) > 1: if isinstance(args[1], Register): src = args[1] else: try: name, src_name = args[:2] src = context[src_name] except KeyError: raise JaqalError( f"Cannot map {src_name} to {name}, {src_name} does not exist" ) if len(args) == 2: # Mapping a whole register or alias onto this alias name, src_name = args return Register(name, alias_from=src) if len(args) == 3: # Mapping a single qubit name, src_name, src_index = args index = self.build( src_index, context) # This may be either an integer or defined parameter. return NamedQubit(name, src, index) if len(args) == 5: # Mapping a slice of a register name, src_name, src_start, src_stop, src_step = args # These may be either integers, None, or let constants start = self.build(src_start, context, gate_context) if start is None: start = 0 stop = self.build(src_stop, context, gate_context) if stop is None: stop = src.size step = self.build(src_step, context, gate_context) if step is None: step = 1 return Register(name, alias_from=src, alias_slice=slice(start, stop, step)) raise JaqalError(f"Wrong number of arguments for map, found {args}")
def enforce_signed_integer_if_numeric(cls, number): if cls.is_signed_integer(number): return number elif cls.is_integer(number): return cls.make_signed_integer(int(number)) elif cls.is_number(number) or cls.is_signed_number(number): # A signed number token can be converted to a float but not an int, so we have a workaround here. if float(number) != int(float(number)): raise JaqalError(f"Expected signed integer, found {number}") return cls.make_signed_integer(int(float(number))) else: return number
def classical_parameters(self): """The classical parameters (ints or floats) this gate takes. :raises JaqalError: If this gate has parameters without type annotations; for example, if it is a macro. """ try: return [param for param in self.parameters if param.classical] except JaqalError: pass raise JaqalError("Gate {self.name} has a parameter with unknown type")
def classical(self): """ A boolean flag denoting whether this AnnotatedValue has a classical type (`ParamType.INT` or `ParamType.FLOAT`) or a quantum type (`ParamType.QUBIT` or `ParamType.REGISTER`). :raises JaqalError: If the AnnotatedValue doesn't have a type annotation. """ if self._kind == ParamType.NONE: raise JaqalError(f"No type defined for parameter {self.name}.") return self._kind not in (ParamType.QUBIT, ParamType.REGISTER)
def get_gate_definition(self, name, arg_count, gate_context): """Return the definition for the given gate. If no such definition exists, and we aren't requiring all gates to be a native gate or macro, then create a new definition and return it.""" if name in gate_context: gate_def = gate_context[name] if not isinstance(gate_def, AbstractGate): raise JaqalError( f"Cannot call gate {name}: it is type {type(gate_def)}") return gate_def is_anonymous_gate_allowed = (self.inject_pulses is None) and not self.autoload_pulses if not is_anonymous_gate_allowed: raise JaqalError(f"No gate {name} defined") gate_def = GateDefinition( name, parameters=[Parameter(f"p{i}", None) for i in range(arg_count)]) gate_context[name] = gate_def return gate_def
def visit_register_statement(self, array_declaration): """Record information about the given register. We check that map statements are compatible with register statements.""" identifier, size = self.deconstruct_array_declaration( array_declaration) # Although in general this is not the right place to check for register inconsistencies, the errors might # be too confusing if we ignored the double register declaration error. identifier = Identifier(self.extract_identifier(identifier)) if identifier in self.registers: raise JaqalError(f"Register {identifier} already declared") self.registers[identifier] = size return self.make_register_statement(array_declaration)
def replace_gate(gate, macros): """Replace a gate with its definition in macros, or return the gate if it is not a macro.""" if gate.name in macros: macro = macros[gate.name] if len(gate.parameters) != len(macro.parameters): raise JaqalError( f"Cannot expand {gate.name}: wrong argument count: {len(gate.parameters)} != {len(macro.parameters)}" ) visitor = GateReplacer(gate.parameters, macros) return visitor.visit(macro) else: return gate
def resolve_map_element_single(self, identifier_value, index_value): """Find the array or register that this identifier maps to, and return the name and index.""" if identifier_value not in self.map_dict: raise JaqalError(f"Cannot resolve map {identifier_value}") source = self.map_dict[identifier_value] if self.is_identifier(source): src_identifier = self.extract_identifier(source) src_index = index_value elif self.is_array_slice(source): src_id_token, src_slice = self.deconstruct_array_slice(source) src_identifier = self.extract_identifier(src_id_token) if any(comp is not None and not self.is_signed_integer(comp) for comp in src_slice): raise JaqalError(f"Unresolved map element {identifier_value}") src_start, src_stop, src_step = [ self.extract_signed_integer(comp) if comp is not None else comp for comp in src_slice ] limit = src_stop or maxsize src_start, src_stop, src_step = slice(src_start, src_stop, src_step).indices(limit) src_range = range(src_start, src_stop, src_step) try: src_index = src_range[index_value] except IndexError: raise JaqalError( f"Index {index_value} out of range for mapping {identifier_value}" ) elif self.is_array_element(source): raise JaqalError( f"Cannot use map alias {identifier_value} as an array element") else: raise JaqalError(f"Unknown map source format: {source}") return src_identifier, src_index
def extract_token(cls, token): """Figure out what the token is and call the appropriate extract method.""" if cls.is_identifier(token): return cls.extract_identifier(token) elif cls.is_integer(token): return cls.extract_integer(token) elif cls.is_signed_integer(token): return cls.extract_signed_integer(token) elif cls.is_number(token): return cls.extract_number(token) elif cls.is_signed_number(token): return cls.extract_signed_number(token) else: raise JaqalError(f"Unknown token: {token}")
def __init__(self, name, size=None, alias_from=None, alias_slice=None): self._name = name self._size = size if (alias_from is None) and not (alias_slice is None and size is not None): raise JaqalError(f"Invalid register declaration: {name}.") if (size is not None) and (alias_from is not None): raise JaqalError( f"Illegal size specification in map statement defining {name}." ) self._alias_from = alias_from self._alias_slice = alias_slice if alias_slice is not None: if (isinstance(alias_slice.start, AnnotatedValue) or isinstance(alias_slice.stop, AnnotatedValue) or isinstance(alias_slice.step, AnnotatedValue) or isinstance(alias_from, AnnotatedValue)): # Verify that the Parameters given have the correct types if isinstance( alias_slice.start, AnnotatedValue) and alias_slice.start.kind not in ( ParamType.INT, ParamType.NONE): raise JaqalError( f"Cannot slice register {alias_from.name} with parameter {alias_slice.start.name} of non-integer kind {alias_slice.start.kind}." ) elif isinstance( alias_slice.stop, AnnotatedValue) and alias_slice.stop.kind not in ( ParamType.INT, ParamType.NONE): raise JaqalError( f"Cannot slice register {alias_from.name} with parameter {alias_slice.stop.name} of non-integer kind {alias_slice.stop.kind}." ) elif isinstance( alias_slice.step, AnnotatedValue) and alias_slice.step.kind not in ( ParamType.INT, ParamType.NONE): raise JaqalError( f"Cannot slice register {alias_from.name} with parameter {alias_slice.step.name} of non-integer kind {alias_slice.step.kind}." ) elif isinstance(alias_from, AnnotatedValue) and alias_from.kind not in ( ParamType.REGISTER, ParamType.NONE, ): raise JaqalError( f"Cannot slice parameter {alias_from.name} of non-register kind {alias_from.kind}." ) elif alias_from.size is not None and not isinstance( alias_from.size, AnnotatedValue): if alias_slice.stop > alias_from.size: raise JaqalError("Index out of range.")
def build_macro(self, sexpression, context, gate_context): args = list(sexpression.args) if len(args) < 2: raise JaqalError( f"Macro must have at least two arguments, found {args}") name = args[0] if name in gate_context: raise JaqalError(f"Attempting to redefine gate {name}") parameter_args = args[1:-1] block = args[-1] parameter_list = [ param if isinstance(param, Parameter) else Parameter(param, None) for param in parameter_args ] parameter_dict = {param.name: param for param in parameter_list} macro_context = { **context, **parameter_dict, } # parameters must be listed second to take precedence built_block = self.build(block, macro_context, gate_context) if not isinstance(built_block, BlockStatement): raise JaqalError( f"Macro body must be a block, found {type(built_block)}") return Macro(name, parameters=parameter_list, body=built_block)
def visit_gate_statement(self, gate_name, gate_args): """If this gate statement is really a macro, replace it with the macro's block.""" gate_name_key = self.extract_qualified_identifier(gate_name) if gate_name_key in self.macro_mapping: arguments, block = self.macro_mapping[gate_name_key] if len(arguments) != len(gate_args): raise JaqalError( f"In resolving macro {gate_name_key}, expected {len(arguments)} arguments, found {len(gate_args)}" ) arg_dict = { arg: gate_arg for arg, gate_arg in zip(arguments, gate_args) } return substitute_macro_arguments(block, arg_dict) return self.make_gate_statement(gate_name, gate_args)
def visit_array_element_qual(self, identifier, index): """Validate the index is an integer.""" identifier_value = self.extract_qualified_identifier(identifier) index_value = self.extract_signed_integer(index) if self.is_macro_argument(identifier_value): raise JaqalError( "This macro uses an argument as a register; please resolve macros before resolving maps" ) identifier_value, index_value = self.resolve_map_element( identifier_value, index_value) identifier = self.make_qualified_identifier(identifier_value) index = self.make_signed_integer(index_value) return self.make_array_element_qual(identifier, index)