def __init__(self, duration: int, kernel: Optional[Kernel] = None, discriminator: Optional[Discriminator] = None, name: Optional[str] = None): """Create new acquire command. Args: duration: Duration of acquisition kernel: The data structures defining the measurement kernels to be used (from the list of available kernels) and set of parameters (if applicable) if the measurement level is 1 or 2. discriminator: Discriminators to be used (from the list of available discriminator) if the measurement level is 2 name: Name of this command. Raises: PulseError: when invalid discriminator or kernel object is input. """ super().__init__(duration=duration) self._name = Acquire.create_name(name) if kernel and not isinstance(kernel, Kernel): raise PulseError('Invalid kernel object is specified.') self._kernel = kernel if discriminator and not isinstance(discriminator, Discriminator): raise PulseError('Invalid discriminator object is specified.') self._discriminator = discriminator
def __init__(self, command: Acquire, acquire: Union[AcquireChannel, List[AcquireChannel]], mem_slot: Optional[Union[MemorySlot, List[MemorySlot]]] = None, reg_slots: Optional[Union[RegisterSlot, List[RegisterSlot]]] = None, mem_slots: Optional[Union[List[MemorySlot]]] = None, reg_slot: Optional[RegisterSlot] = None, name: Optional[str] = None): if isinstance(acquire, list) or isinstance(mem_slot, list) or reg_slots: warnings.warn( 'The AcquireInstruction on multiple qubits, multiple ' 'memory slots and multiple reg slots is deprecated. The ' 'parameter "mem_slots" has been replaced by "mem_slot" and ' '"reg_slots" has been replaced by "reg_slot"', DeprecationWarning, 3) if not isinstance(acquire, list): acquire = [acquire] if isinstance(acquire[0], Qubit): raise PulseError( "AcquireInstruction can not be instantiated with Qubits, " "which are deprecated.") if mem_slot and not isinstance(mem_slot, list): mem_slot = [mem_slot] elif mem_slots: mem_slot = mem_slots if reg_slot: reg_slot = [reg_slot] elif reg_slots and not isinstance(reg_slots, list): reg_slot = [reg_slots] else: reg_slot = reg_slots if not (mem_slot or reg_slot): raise PulseError('Neither memoryslots or registers were supplied') if mem_slot and len(acquire) != len(mem_slot): raise PulseError( "The number of mem_slots must be equals to the number of acquires" ) if reg_slot: if len(acquire) != len(reg_slot): raise PulseError("The number of reg_slots must be equals " "to the number of acquires") else: reg_slot = [] super().__init__(command, *acquire, *mem_slot, *reg_slot, name=name) self._acquires = acquire self._mem_slots = mem_slot self._reg_slots = reg_slot
def __init__(self, pulse: Pulse, channel: PulseChannel, name: Optional[str] = None): """Create a new pulse instruction. Args: pulse: A pulse waveform description, such as :py:class:`~qiskit.pulse.library.Waveform`. channel: The channel to which the pulse is applied. name: Name of the instruction for display purposes. Defaults to ``pulse.name``. Raises: PulseError: If pulse is not a Pulse type. """ if not isinstance(pulse, Pulse): raise PulseError( "The `pulse` argument to `Play` must be of type `library.Pulse`." ) if not isinstance(channel, PulseChannel): raise PulseError( "The `channel` argument to `Play` must be of type `channels.PulseChannel`." ) if name is None: name = pulse.name super().__init__(operands=(pulse, channel), name=name)
def visit_Call(self, node: ast.Call) -> Union[ast.Call, ast.Constant]: """Evaluate function and return ast.Constant if all arguments are bound. Args: node: Function to evaluate. Returns: Evaluated value. Raises: PulseError: When unsupported or unsafe function is specified. """ if not isinstance(node.func, ast.Name): raise PulseError("Unsafe expression is detected.") node.args = [self.visit(arg) for arg in node.args] if all(isinstance(arg, (ast.Constant, ast.Num)) for arg in node.args): if node.func.id not in self._math_ops.keys(): raise PulseError("Function %s is not supported." % node.func.id) _args = [arg.n for arg in node.args] _val = self._math_ops[node.func.id](*_args) if not _val.imag: _val = _val.real val = ast.Constant(n=_val) return ast.copy_location(val, node) return node
def validate_parameters(self) -> None: if not _is_parameterized(self.amp) and abs(self.amp) > 1.: raise PulseError("The amplitude norm must be <= 1, " "found: {}".format(abs(self.amp))) if not _is_parameterized(self.sigma) and self.sigma <= 0: raise PulseError("Sigma must be greater than 0.") if not _is_parameterized(self.beta) and isinstance(self.beta, complex): raise PulseError("Beta must be real.") # Check if beta is too large: the amplitude norm must be <=1 for all points if (not _is_parameterized(self.beta) and not _is_parameterized(self.sigma) and self.beta > self.sigma): # If beta <= sigma, then the maximum amplitude is at duration / 2, which is # already constrainted by self.amp <= 1 # 1. Find the first maxima associated with the beta * d/dx gaussian term # This eq is derived from solving for the roots of the norm of the drag function. # There is a second maxima mirrored around the center of the pulse with the same # norm as the first, so checking the value at the first x maxima is sufficient. argmax_x = (self.duration / 2 - (self.sigma / self.beta) * math.sqrt(self.beta ** 2 - self.sigma ** 2)) if argmax_x < 0: # If the max point is out of range, either end of the pulse will do argmax_x = 0 # 2. Find the value at that maximum max_val = continuous.drag(np.array(argmax_x), sigma=self.sigma, beta=self.beta, amp=self.amp, center=self.duration / 2) if abs(max_val) > 1.: raise PulseError("Beta is too large; pulse amplitude norm exceeds 1.")
def validate_parameters(self) -> None: """Validate parameters. Raises: PulseError: If the parameters passed are not valid. """ if self.is_parameterized(): return if self._constraints is not None: fargs = _get_expression_args(self._constraints, self.parameters) if not bool(self._constraints_lam(*fargs)): param_repr = ", ".join(f"{p}={v}" for p, v in self.parameters.items()) const_repr = str(self._constraints) raise PulseError( f"Assigned parameters {param_repr} violate following constraint: {const_repr}." ) if self._limit_amplitude: if self._valid_amp_conditions is not None: fargs = _get_expression_args(self._valid_amp_conditions, self.parameters) check_full_waveform = not bool(self._valid_amp_conditions_lam(*fargs)) else: check_full_waveform = True if check_full_waveform: # Check full waveform only when the condition is satisified or # evaluation condition is not provided. # This operation is slower due to overhead of 'get_waveform'. if not _is_amplitude_valid(self): param_repr = ", ".join(f"{p}={v}" for p, v in self.parameters.items()) raise PulseError( f"Maximum pulse amplitude norm exceeds 1.0 with parameters {param_repr}." "This can be overruled by setting Pulse.limit_amplitude." )
def _remove_timeslots(self, time: int, schedule: ScheduleComponent): """Delete the timeslots if present for the respective schedule component. Args: time: The time to remove the timeslots for the ``schedule`` component. schedule: The schedule to insert into self. Raises: PulseError: If timeslots overlap or an invalid start time is provided. """ if not isinstance(time, int): raise PulseError("Schedule start time must be an integer.") for channel in schedule.channels: if channel not in self._timeslots: raise PulseError( 'The channel {} is not present in the schedule'.format(channel)) channel_timeslots = self._timeslots[channel] for interval in schedule._timeslots[channel]: if channel_timeslots: interval = (interval[0] + time, interval[1] + time) index = _interval_index(channel_timeslots, interval) if channel_timeslots[index] == interval: channel_timeslots.pop(index) continue raise PulseError( "Cannot find interval ({t0}, {tf}) to remove from " "channel {ch} in Schedule(name='{name}').".format( ch=channel, t0=interval[0], tf=interval[1], name=schedule.name)) if not channel_timeslots: self._timeslots.pop(channel)
def get_waveform(self) -> Waveform: r"""Return a Waveform with samples filled according to the formula that the pulse represents and the parameter values it contains. Since the returned array is a discretized time series of the continuous function, this method uses a midpoint sampler. For ``duration``, return: .. math:: \{f(t+0.5) \in \mathbb{C} | t \in \mathbb{Z} \wedge 0<=t<\texttt{duration}\} Returns: A waveform representation of this pulse. Raises: PulseError: When parameters are not assigned. PulseError: When expression for pulse envelope is not assigned. """ if self.is_parameterized(): raise PulseError("Unassigned parameter exists. All parameters must be assigned.") if self._envelope is None: raise PulseError("Pulse envelope expression is not assigned.") fargs = _get_expression_args(self._envelope, self.parameters) return Waveform(samples=self._envelope_lam(*fargs), name=self.name)
def __init__(self, duration, discriminator=None, kernel=None): """Create new acquire command. Args: duration (int): Duration of acquisition. discriminator (Discriminator): Discriminators to be used (from the list of available discriminator) if the measurement level is 2. kernel (Kernel): The data structures defining the measurement kernels to be used (from the list of available kernels) and set of parameters (if applicable) if the measurement level is 1 or 2. Raises: PulseError: when invalid discriminator or kernel object is input. """ super().__init__(duration=duration) if discriminator: if isinstance(discriminator, Discriminator): self.discriminator = discriminator else: raise PulseError('Invalid discriminator object is specified.') else: self.discriminator = None if kernel: if isinstance(kernel, Kernel): self.kernel = kernel else: raise PulseError('Invalid kernel object is specified.') else: self.kernel = None
def __init__(self, pulse: Pulse, channel: PulseChannel, name: Optional[str] = None): """Create a new pulse instruction. Args: pulse: A pulse waveform description, such as :py:class:`~qiskit.pulse.library.Waveform`. channel: The channel to which the pulse is applied. name: Name of the instruction for display purposes. Defaults to ``pulse.name``. Raises: PulseError: If pulse is not a Pulse type. """ if not isinstance(pulse, Pulse): raise PulseError( "The `pulse` argument to `Play` must be of type `library.Pulse`." ) if not isinstance(channel, PulseChannel): raise PulseError( "The `channel` argument to `Play` must be of type " "`channels.PulseChannel`.") if name is None: name = pulse.name super().__init__((pulse, channel), None, (channel, ), name=name) if pulse.is_parameterized(): for value in pulse.parameters.values(): if isinstance(value, ParameterExpression): for param in value.parameters: # Table maps parameter to operand index, 0 for ``pulse`` self._parameter_table[param].append(0)
def get( self, instruction: Union[str, Instruction], qubits: Union[int, Iterable[int]], *params: Union[complex, ParameterExpression], **kwparams: Union[complex, ParameterExpression], ) -> Union[Schedule, ScheduleBlock]: """Return the defined :py:class:`~qiskit.pulse.Schedule` or :py:class:`~qiskit.pulse.ScheduleBlock` for the given instruction on the given qubits. If all keys are not specified this method returns schedule with unbound parameters. Args: instruction: Name of the instruction or the instruction itself. qubits: The qubits for the instruction. *params: Command parameters for generating the output schedule. **kwparams: Keyworded command parameters for generating the schedule. Returns: The Schedule defined for the input. Raises: PulseError: When invalid parameters are specified. """ instruction = _get_instruction_string(instruction) self.assert_has(instruction, qubits) generator = self._map[instruction][_to_tuple(qubits)] _error_message = ( f"*params={params}, **kwparams={kwparams} do not match with " f"the schedule generator signature {generator.signature}." ) function = generator.function if callable(function): try: # callables require full binding, but default values can exist. binds = generator.signature.bind(*params, **kwparams) binds.apply_defaults() except TypeError as ex: raise PulseError(_error_message) from ex return function(**binds.arguments) try: # schedules allow partial binding binds = generator.signature.bind_partial(*params, **kwparams) except TypeError as ex: raise PulseError(_error_message) from ex if len(binds.arguments) > 0: value_dict = dict() for param in function.parameters: try: value_dict[param] = binds.arguments[param.name] except KeyError: pass return function.assign_parameters(value_dict, inplace=False) else: return function
def __init__(self, command: Acquire, qubits: Union[Qubit, List[Qubit]], mem_slots: Union[MemorySlot, List[MemorySlot]], reg_slots: Union[RegisterSlot, List[RegisterSlot]] = None, start_time: int = 0): if isinstance(qubits, Qubit): qubits = [qubits] if mem_slots: if isinstance(mem_slots, MemorySlot): mem_slots = [mem_slots] elif len(qubits) != len(mem_slots): raise PulseError("#mem_slots must be equals to #qubits") if reg_slots: if isinstance(reg_slots, RegisterSlot): reg_slots = [reg_slots] if len(qubits) != len(reg_slots): raise PulseError("#reg_slots must be equals to #qubits") else: reg_slots = [] # TODO: more precise time-slots slots = [ Timeslot(Interval(0, command.duration), q.acquire) for q in qubits ] slots.extend([ Timeslot(Interval(0, command.duration), mem) for mem in mem_slots ]) super().__init__(command, start_time, TimeslotCollection(slots)) self._qubits = qubits self._mem_slots = mem_slots self._reg_slots = reg_slots
def validate_parameters(self) -> None: if not _is_parameterized(self.amp) and abs(self.amp) > 1.: raise PulseError("The amplitude norm must be <= 1, " "found: {}".format(abs(self.amp))) if not _is_parameterized(self.sigma) and self.sigma <= 0: raise PulseError("Sigma must be greater than 0.") if not _is_parameterized(self.width) and self.width < 0 or self.width >= self.duration: raise PulseError("The pulse width must be at least 0 and less than its duration.")
def validate_parameters(self) -> None: if not _is_parameterized( self.amp) and abs(self.amp) > 1.0 and self.limit_amplitude: raise PulseError( f"The amplitude norm must be <= 1, found: {abs(self.amp)}" + "This can be overruled by setting Pulse.limit_amplitude.") if not _is_parameterized(self.sigma) and self.sigma <= 0: raise PulseError("Sigma must be greater than 0.")
def get( self, instruction: Union[str, Instruction], qubits: Union[int, Iterable[int]], *params: Union[int, float, complex, ParameterExpression], **kwparams: Union[int, float, complex, ParameterExpression]) -> Schedule: """Return the defined :py:class:`~qiskit.pulse.Schedule` for the given instruction on the given qubits. If all keys are not specified this method returns schedule with unbound parameters. Args: instruction: Name of the instruction or the instruction itself. qubits: The qubits for the instruction. *params: Command parameters for generating the output schedule. **kwparams: Keyworded command parameters for generating the schedule. Returns: The Schedule defined for the input. Raises: PulseError: When invalid parameters are specified. """ instruction = _get_instruction_string(instruction) self.assert_has(instruction, qubits) schedule_args_tuple = self._map[instruction][_to_tuple(qubits)] # Verify parameter-value mapping if len(params) > len(schedule_args_tuple.arguments): raise PulseError('Too many values to bind: {}.'.format(', '.join( map(str, params)))) if not all(key in schedule_args_tuple.arguments for key in kwparams): raise PulseError('Parameters not defined: {}'.format(', '.join( kwparams.keys()))) bind_parameters = dict( zip_longest(schedule_args_tuple.arguments, params)) bind_parameters.update(kwparams) sched = schedule_args_tuple.schedule # callback function if callable(sched): return sched(**bind_parameters) # schedule if sched.is_parameterized(): parameter_mapping = dict() for param_obj in sched.parameters: bind_value = bind_parameters[param_obj.name] # if value is not set, keep the parameter unassigned if bind_value is not None: parameter_mapping[param_obj] = bind_value return deepcopy(sched).assign_parameters(parameter_mapping) return sched
def _add_timeslots(self, time: int, schedule: Union['Schedule', Instruction]) -> None: """Update all time tracking within this schedule based on the given schedule. Args: time: The time to insert the schedule into self. schedule: The schedule to insert into self. Raises: PulseError: If timeslots overlap or an invalid start time is provided. """ if not isinstance(time, int): raise PulseError("Schedule start time must be an integer.") other_timeslots = _get_timeslots(schedule) self._duration = max(self._duration, time + schedule.duration) for channel in schedule.channels: if channel not in self._timeslots: if time == 0: self._timeslots[channel] = copy.copy( other_timeslots[channel]) else: self._timeslots[channel] = [ (i[0] + time, i[1] + time) for i in other_timeslots[channel] ] continue for idx, interval in enumerate(other_timeslots[channel]): if interval[0] + time >= self._timeslots[channel][-1][1]: # Can append the remaining intervals self._timeslots[channel].extend([ (i[0] + time, i[1] + time) for i in other_timeslots[channel][idx:] ]) break try: interval = (interval[0] + time, interval[1] + time) index = _find_insertion_index(self._timeslots[channel], interval) self._timeslots[channel].insert(index, interval) except PulseError: raise PulseError( "Schedule(name='{new}') cannot be inserted into Schedule(name='{old}') at " "time {time} because its instruction on channel {ch} scheduled from time " "{t0} to {tf} overlaps with an existing instruction." "".format(new=schedule.name or '', old=self.name or '', time=time, ch=channel, t0=interval[0], tf=interval[1])) _check_nonnegative_timeslot(self._timeslots)
def gaussian_square(duration: int, amp: complex, sigma: float, risefall: Optional[float] = None, width: Optional[float] = None, name: Optional[str] = None, zero_ends: bool = True) -> 'SamplePulse': r"""Generates gaussian square :class:`~qiskit.pulse.SamplePulse`. For :math:`d=` ``duration``, :math:`A=` ``amp``, :math:`\sigma=` ``sigma``, and :math:`r=` ``risefall``, applies the `midpoint` sampling strategy to generate a discrete pulse sampled from the continuous function: .. math:: f(x) = \begin{cases} g(x - r) ) & x\leq r \\ A & r\leq x\leq d-r \\ g(x - (d - r)) & d-r\leq x \end{cases} where :math:`g(x)` is the Gaussian function sampled from in :meth:`gaussian` with :math:`A=` ``amp``, :math:`\mu=1`, and :math:`\sigma=` ``sigma``. I.e. :math:`f(x)` represents a square pulse with smooth Gaussian edges. If ``zero_ends == True``, the samples for the Gaussian ramps are remapped as in :meth:`gaussian`. Args: duration: Duration of pulse. Must be greater than zero. amp: Pulse amplitude. sigma: Width (standard deviation) of Gaussian rise/fall portion of the pulse. risefall: Number of samples over which pulse rise and fall happen. Width of square portion of pulse will be ``duration-2*risefall``. width: The duration of the embedded square pulse. Only one of ``width`` or ``risefall`` should be specified as the functional form requires ``width = duration - 2 * risefall``. name: Name of pulse. zero_ends: If ``True``, make the first and last sample zero, but rescale to preserve amp. Raises: PulseError: If ``risefall`` and ``width`` arguments are inconsistent or not enough info. """ if risefall is None and width is None: raise PulseError("gaussian_square missing required argument: 'width' or 'risefall'.") if risefall is not None: if width is None: width = duration - 2 * risefall elif 2 * risefall + width != duration: raise PulseError("Both width and risefall were specified, and they are " "inconsistent: 2 * risefall + width == {} != " "duration == {}.".format(2 * risefall + width, duration)) center = duration / 2 zeroed_width = duration if zero_ends else None return _sampled_gaussian_square_pulse(duration, amp, center, width, sigma, zeroed_width=zeroed_width, name=name)
def __init__(self, operands: Tuple, duration: int, channels: Tuple[Channel], name: Optional[str] = None): """Instruction initializer. Args: operands: The argument list. duration: Length of time taken by the instruction in terms of dt. channels: Tuple of pulse channels that this instruction operates on. name: Optional display name for this instruction. Raises: PulseError: If duration is negative. PulseError: If the input ``channels`` are not all of type :class:`Channel`. """ if not isinstance(duration, (int, np.integer)): raise PulseError("Instruction duration must be an integer, " "got {} instead.".format(duration)) if duration < 0: raise PulseError( "{} duration of {} is invalid: must be nonnegative." "".format(self.__class__.__name__, duration)) for channel in channels: if not isinstance(channel, Channel): raise PulseError( "Expected a channel, got {} instead.".format(channel)) self._duration = duration self._channels = channels self._timeslots = { channel: [(0, self.duration)] for channel in channels } self._operands = operands self._name = name self._hash = None self._parameter_table = defaultdict(list) for idx, op in enumerate(operands): if isinstance(op, ParameterExpression): for param in op.parameters: self._parameter_table[param].append(idx) elif isinstance(op, Channel) and op.is_parameterized(): for param in op.parameters: self._parameter_table[param].append(idx)
def __init__(self, index: int, lo_freq: float = None, lo_freq_range: Tuple[float, float] = (0, float("inf"))): super().__init__(index) self._lo_freq = lo_freq if (not isinstance(lo_freq_range, tuple)) or len(lo_freq_range) != 2: raise PulseError("Invalid form of lo_freq_range is specified.") self._lo_freq_range = LoRange(*lo_freq_range) if self._lo_freq: if not self._lo_freq_range.includes(self._lo_freq): raise PulseError("lo_freq %f must be within lo_freq_range %s" % (self._lo_freq, self._lo_freq_range))
def gaussian_square(duration: int, amp: complex, sigma: float, risefall: Optional[float] = None, width: Optional[float] = None, name: Optional[str] = None, zero_ends: bool = True) -> 'SamplePulse': """Generates gaussian square `SamplePulse`. Centered at `duration/2` and zeroed at `t=0` and `t=duration` to prevent large initial/final discontinuities. Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. Args: duration: Duration of pulse. Must be greater than zero. amp: Pulse amplitude. sigma: Width (standard deviation) of Gaussian rise/fall portion of the pulse. risefall: Number of samples over which pulse rise and fall happen. Width of square portion of pulse will be `duration-2*risefall`. width: The duration of the embedded square pulse. Only one of `width` or `risefall` should be specified since width = duration - 2 * risefall. name: Name of pulse. zero_ends: If True, make the first and last sample zero, but rescale to preserve amp. Raises: PulseError: If risefall and width arguments are inconsistent or not enough info. """ if risefall is None and width is None: raise PulseError( "gaussian_square missing required argument: 'width' or 'risefall'." ) if risefall is not None: if width is None: width = duration - 2 * risefall elif 2 * risefall + width != duration: raise PulseError( "Both width and risefall were specified, and they are " "inconsistent: 2 * risefall + width == {} != " "duration == {}.".format(2 * risefall + width, duration)) center = duration / 2 zeroed_width = duration if zero_ends else None return _sampled_gaussian_square_pulse(duration, amp, center, width, sigma, zeroed_width=zeroed_width, name=name)
def _mutable_shift(self, time: int ) -> 'Schedule': """Return this schedule shifted forward by `time`. Args: time: Time to shift by Raises: PulseError: if ``time`` is not an integer. """ if not isinstance(time, int): raise PulseError( "Schedule start time must be an integer.") timeslots = {} for chan, ch_timeslots in self._timeslots.items(): timeslots[chan] = [(ts[0] + time, ts[1] + time) for ts in ch_timeslots] _check_nonnegative_timeslot(timeslots) self._duration = self._duration + time self._timeslots = timeslots self.__children = [(orig_time + time, child) for orig_time, child in self._children] return self
def gaussian_square(times: np.ndarray, amp: complex, center: float, square_width: float, sigma: float, zeroed_width: Optional[float] = None) -> np.ndarray: r"""Continuous gaussian square pulse. Args: times: Times to output pulse for. amp: Pulse amplitude. center: Center of the square pulse component. square_width: Width of the square pulse component. sigma: Standard deviation of Gaussian rise/fall portion of the pulse. zeroed_width: Subtract baseline of gaussian square pulse to enforce $\OmegaSquare(center \pm zeroed_width/2)=0$. Raises: PulseError: if zeroed_width is not compatible with square_width. """ square_start = center-square_width/2 square_stop = center+square_width/2 if zeroed_width: if zeroed_width < square_width: raise PulseError("zeroed_width cannot be smaller than square_width.") gaussian_zeroed_width = zeroed_width-square_width else: gaussian_zeroed_width = None funclist = [functools.partial(gaussian, amp=amp, center=square_start, sigma=sigma, zeroed_width=gaussian_zeroed_width, rescale_amp=True), functools.partial(gaussian, amp=amp, center=square_stop, sigma=sigma, zeroed_width=gaussian_zeroed_width, rescale_amp=True), functools.partial(constant, amp=amp)] condlist = [times <= square_start, times >= square_stop] return np.piecewise(times.astype(np.complex_), condlist, funclist)
def assign(self, parameter: Parameter, value: ParameterValueType) -> "Channel": """Return a new channel with the input Parameter assigned to value. Args: parameter: A parameter in this expression whose value will be updated. value: The new value to bind to. Returns: A new channel with updated parameters. Raises: PulseError: If the parameter is not present in the channel. """ if parameter not in self.parameters: raise PulseError( f"Cannot bind parameters ({parameter}) not present in the channel." ) new_index = self.index.assign(parameter, value) if not new_index.parameters: self._validate_index(new_index) new_index = int(new_index) return type(self)(new_index)
def _insertion_index(intervals: List[Interval], new_interval: Interval, index: int = 0) -> int: """Using binary search on start times, return the index into `intervals` where the new interval belongs, or raise an error if the new interval overlaps with any existing ones. Args: intervals: A sorted list of non-overlapping Intervals. new_interval: The interval for which the index into intervals will be found. index: A running tally of the index, for recursion. The user should not pass a value. Returns: The index into intervals that new_interval should be inserted to maintain a sorted list of intervals. Raises: PulseError: If new_interval overlaps with the given intervals. """ if not intervals: return index if len(intervals) == 1: if _overlaps(intervals[0], new_interval): raise PulseError("New interval overlaps with existing.") return index if new_interval[1] <= intervals[0][0] else index + 1 mid_idx = len(intervals) // 2 if new_interval[1] <= intervals[mid_idx][0]: return _insertion_index(intervals[:mid_idx], new_interval, index=index) else: return _insertion_index(intervals[mid_idx:], new_interval, index=index + mid_idx)
def align_func(schedule: Schedule, duration: int, func: Callable[[int], float]) -> Schedule: """Schedule a list of pulse instructions with schedule position defined by the numerical expression. Args: schedule: Input schedule of which top-level ``child`` nodes will be reschedulued. duration: Duration of context. This should be larger than the schedule duration. func: A function that takes an index of sub-schedule and returns the fractional coordinate of of that sub-schedule. The returned value should be defined within [0, 1]. The pulse index starts from 1. Returns: New schedule with input `schedule`` child schedules and instructions aligned with equivalent interval. Notes: This context is convenient for writing UDD sequence for example. """ if duration < schedule.duration: return schedule aligned = Schedule() for ind, (_, child) in enumerate(schedule._children): _t_center = duration * func(ind + 1) _t0 = int(_t_center - 0.5 * child.duration) if _t0 < 0 or _t0 > duration: PulseError('Invalid schedule position t=%d is specified at index=%d' % (_t0, ind)) aligned.insert(_t0, child, inplace=True) return pad(aligned, aligned.channels, until=duration, inplace=True)
def __init__(self, command, *channels: List[Channel], timeslots: TimeslotCollection = None, name=None): """ command (Command): Pulse command to schedule *channels: List of pulse channels to schedule with command timeslots: Optional list of timeslots. If channels are supplied timeslots cannot also be given name: Name of Instruction """ self._command = command self._name = name if name else self._command.name if timeslots and channels: raise PulseError( 'Channels and timeslots may not both be supplied.') if not timeslots: duration = command.duration self._timeslots = TimeslotCollection( *(Timeslot(Interval(0, duration), channel) for channel in channels)) else: self._timeslots = timeslots
def align(self, schedule: Schedule) -> Schedule: """Reallocate instructions according to the policy. Only top-level sub-schedules are aligned. If sub-schedules are nested, nested schedules are not recursively aligned. Args: schedule: Schedule to align. Returns: Schedule with reallocated instructions. """ instruction_duration_validation(self.duration) if self.duration < schedule.duration: return schedule aligned = Schedule.initialize_from(schedule) for ind, (_, child) in enumerate(schedule.children): _t_center = self.duration * self._func(ind + 1) _t0 = int(_t_center - 0.5 * child.duration) if _t0 < 0 or _t0 > self.duration: PulseError( "Invalid schedule position t=%d is specified at index=%d" % (_t0, ind)) aligned.insert(_t0, child, inplace=True) return aligned
def __init__(self, begin: int, end: int): """Create an interval = (begin, end)) Args: begin: begin time of this interval end: end time of this interval Raises: PulseError: when invalid time or duration is specified """ if begin < 0: raise PulseError("Cannot create Interval with negative begin time") if end < 0: raise PulseError("Cannot create Interval with negative end time") self._begin = begin self._end = end
def _inline_block(block: ScheduleBlock) -> ScheduleBlock: """A helper function to inline subroutine of schedule block. .. note:: If subroutine is ``Schedule`` the function raises an error. """ ret_block = ScheduleBlock.initialize_from(block) for inst in block.blocks: if isinstance(inst, instructions.Call): # bind parameter subroutine = inst.assigned_subroutine() if isinstance(subroutine, Schedule): raise PulseError( f"A subroutine {subroutine.name} is a pulse Schedule. " "This program cannot be inserted into ScheduleBlock because " "t0 associated with instruction will be lost.") # recursively inline the program inline_block = _inline_block(subroutine) ret_block.append(inline_block, inplace=True) elif isinstance(inst, ScheduleBlock): # recursively inline the program inline_block = _inline_block(inst) ret_block.append(inline_block, inplace=True) else: ret_block.append(inst, inplace=True) return ret_block
def __init__(self, *schedules, parameters: Optional[Dict[str, Union[float, complex]]] = None, name: Optional[str] = None): warnings.warn( 'ParameterizedSchedule is deprecated. Use Schedule with ' 'circuit.Parameter objects.', DeprecationWarning) full_schedules = [] parameterized = [] parameters = parameters or [] self.name = name or '' # partition schedules into callable and schedules for schedule in schedules: if isinstance(schedule, ParameterizedSchedule): parameterized.append(schedule) parameters += schedule.parameters elif callable(schedule): parameterized.append(schedule) elif isinstance(schedule, Schedule): full_schedules.append(schedule) else: raise PulseError('Input type: {} not supported'.format( type(schedule))) self._parameterized = tuple(parameterized) self._schedules = tuple(full_schedules) self._parameters = tuple(sorted(set(parameters)))