def export_to_file(self, filename=None, file_format=QCTRL_EXPANDED, file_type=CSV, coordinates=CYLINDRICAL): """Prepares and saves the driven control in a file. Parameters ---------- filename : str, optional Name and path of the file to save the control into. Defaults to None file_format : str Specified file format for saving the control. Defaults to 'Q-CTRL expanded'; Currently it does not support any other format. For detail of the `Q-CTRL Expanded Format` consult `Q-CTRL Control Data Format <https://docs.q-ctrl.com/output-data-formats#q-ctrl-hardware>` _. file_type : str, optional One of 'CSV' or 'JSON'; defaults to 'CSV'. coordinates : str, optional Indicates the co-ordinate system requested. Must be one of 'Cylindrical', 'Cartesian'; defaults to 'Cylindrical' References ---------- `Q-CTRL Control Data Format <https://docs.q-ctrl.com/output-data-formats#q-ctrl-hardware>` _. Raises ------ ArgumentsValueError Raised if some of the parameters are invalid. """ if filename is None: raise ArgumentsValueError('Invalid filename provided.', {'filename': filename}) if file_format not in [QCTRL_EXPANDED]: raise ArgumentsValueError( 'Requested file format is not supported. Please use ' 'one of {}'.format([QCTRL_EXPANDED]), {'file_format': file_format}) if file_type not in [CSV, JSON]: raise ArgumentsValueError( 'Requested file type is not supported. Please use ' 'one of {}'.format([CSV, JSON]), {'file_type': file_type}) if coordinates not in [CYLINDRICAL, CARTESIAN]: raise ArgumentsValueError( 'Requested coordinate type is not supported. Please use ' 'one of {}'.format([CARTESIAN, CYLINDRICAL]), {'coordinates': coordinates}) if file_format == QCTRL_EXPANDED: self._export_to_qctrl_expanded_format(filename=filename, file_type=file_type, coordinates=coordinates)
def __init__(self, base_attributes=None): self.base_attributes = base_attributes if self.base_attributes is not None: if not isinstance(base_attributes, list): raise ArgumentsValueError( 'Attributes must be provided as a list object', {'base_attributes': self.base_attributes}, extras={'base_attributes_type': type(base_attributes)}) if not self.base_attributes: raise ArgumentsValueError( 'No attributes provided', {'base_attributes': self.base_attributes}) for attribute in self.base_attributes: if not isinstance(attribute, str): raise ArgumentsValueError( 'Each attribute must be a string. Found ' '{0} type.'.format(type(attribute)), { 'attribute': attribute, 'attribute_type': type(attribute) }, extras={'base_attributes': self.base_attributes})
def new_periodic_single_axis_sequence( duration=None, # pylint: disable=invalid-name number_of_offsets=None, **kwargs): """Periodic Single Axis Sequence. Parameters --------- duration : float Total duration of the sequence. Defaults to None number_of_offsets : int, optional Number of offsets. Defaults to None kwargs : dict Additional keywords required by qctrlopencontrols.sequences.DynamicDecouplingSequence Returns ------- qctrlopencontrols.sequences.DynamicDecouplingSequence Periodic (single-axis) sequence Raises ------ ArgumentsValueError Raised when an argument is invalid. """ if duration is None: duration = 1. if duration <= 0.: raise ArgumentsValueError('Sequence duration must be above zero:', {'duration': duration}) if number_of_offsets is None: number_of_offsets = 1 number_of_offsets = int(number_of_offsets) if number_of_offsets <= 0.: raise ArgumentsValueError('Number of offsets must be above zero:', {'number_of_offsets': number_of_offsets}) spacing = 1. / (number_of_offsets + 1) # prepare the offsets for delta comb deltas = [k * spacing for k in range(1, number_of_offsets + 1)] deltas = np.array(deltas) offsets = duration * deltas rabi_rotations = np.zeros(offsets.shape) azimuthal_angles = np.zeros(offsets.shape) detuning_rotations = np.zeros(offsets.shape) # set all the rabi_rotations to X_pi rabi_rotations[0:] = np.pi return DynamicDecouplingSequence(duration=duration, offsets=offsets, rabi_rotations=rabi_rotations, azimuthal_angles=azimuthal_angles, detuning_rotations=detuning_rotations, **kwargs)
def new_carr_purcell_meiboom_gill_sequence( duration=None, # pylint: disable=invalid-name number_of_offsets=None, **kwargs): """Carr-Purcell-Meiboom-Gill Sequences. Parameters --------- duration : float Total duration of the sequence. Defaults to None number_of_offsets : int, optional Number of offsets. Defaults to None kwargs : dict Additional keywords required by qctrlopencontrols.sequences.DynamicDecouplingSequence Returns ------- qctrlopencontrols.sequences.DynamicDecouplingSequence Carr-Purcell-Meiboom-Gill sequence Raises ------ ArgumentsValueError Raised when an argument is invalid. """ if duration is None: duration = 1. if duration <= 0.: raise ArgumentsValueError('Sequence duration must be above zero:', {'duration': duration}) if number_of_offsets is None: number_of_offsets = 1 number_of_offsets = int(number_of_offsets) if number_of_offsets <= 0.: raise ArgumentsValueError('Number of offsets must be above zero:', {'number_of_offsets': number_of_offsets}) offsets = _carr_purcell_meiboom_gill_offsets(duration, number_of_offsets) rabi_rotations = np.zeros(offsets.shape) azimuthal_angles = np.zeros(offsets.shape) detuning_rotations = np.zeros(offsets.shape) # set all azimuthal_angles=pi/2, rabi_rotations = pi rabi_rotations[0:] = np.pi azimuthal_angles[0:] = np.pi / 2 return DynamicDecouplingSequence(duration=duration, offsets=offsets, rabi_rotations=rabi_rotations, azimuthal_angles=azimuthal_angles, detuning_rotations=detuning_rotations, **kwargs)
def new_uhrig_single_axis_sequence(duration=None, number_of_offsets=None, **kwargs): """Uhrig Single Axis Sequence. Parameters --------- duration : float Total duration of the sequence. Defaults to None number_of_offsets : int, optional Number of offsets. Defaults to None kwargs : dict Additional keywords required by qctrlopencontrols.sequences.DynamicDecouplingSequence Returns ------- qctrlopencontrols.sequences.DynamicDecouplingSequence Uhrig (single-axis) sequence Raises ------ ArgumentsValueError Raised when an argument is invalid. """ if duration is None: duration = 1. if duration <= 0.: raise ArgumentsValueError('Sequence duration must be above zero:', {'duration': duration}) if number_of_offsets is None: number_of_offsets = 1 number_of_offsets = int(number_of_offsets) if number_of_offsets <= 0.: raise ArgumentsValueError('Number of offsets must be above zero:', {'number_of_offsets': number_of_offsets}) offsets = _uhrig_single_axis_offsets(duration, number_of_offsets) rabi_rotations = np.zeros(offsets.shape) azimuthal_angles = np.zeros(offsets.shape) detuning_rotations = np.zeros(offsets.shape) # set all the azimuthal_angles as pi/2, rabi_rotations = pi rabi_rotations[0:] = np.pi azimuthal_angles[0:] = np.pi / 2 return DynamicDecouplingSequence(duration=duration, offsets=offsets, rabi_rotations=rabi_rotations, azimuthal_angles=azimuthal_angles, detuning_rotations=detuning_rotations, **kwargs)
def _get_transformed_rabi_rotation_wimperis(rabi_rotation): """ Calculates the Rabi rotation angle as required by Wimperis 1 (BB1) and Solovay-Kitaev driven controls. Parameters ---------- rabi_rotation : float Rotation angle of the operation Returns ------- float The transformed angle as per definition for the Wimperis 1 (BB1) control Raises ------ ArgumentsValueError Raised when an argument is invalid. """ # Raise error if the polar angle is incorrect if rabi_rotation > 4 * np.pi: raise ArgumentsValueError( 'The polar angle must be between -4 pi and 4 pi (inclusive).', {'rabi_rotation': rabi_rotation}) return np.arccos(-rabi_rotation / (4 * np.pi))
def new_uhrig_single_axis_sequence(duration=None, number_of_offsets=None, pre_post_rotation=False, **kwargs): """Uhrig Single Axis Sequence. Parameters --------- duration : float Total duration of the sequence. Defaults to None number_of_offsets : int, optional Number of offsets. Defaults to None pre_post_rotation : bool, optional If True, a :math:`\\pi.2` rotation is added at the start and end of the sequence. kwargs : dict Additional keywords required by qctrlopencontrols.sequences.DynamicDecouplingSequence Returns ------- qctrlopencontrols.sequences.DynamicDecouplingSequence Uhrig (single-axis) sequence Raises ------ ArgumentsValueError Raised when an argument is invalid. """ duration = _check_duration(duration) if number_of_offsets is None: number_of_offsets = 1 number_of_offsets = int(number_of_offsets) if number_of_offsets <= 0.: raise ArgumentsValueError('Number of offsets must be above zero:', {'number_of_offsets': number_of_offsets}) offsets = _uhrig_single_axis_offsets(duration, number_of_offsets) rabi_rotations = np.zeros(offsets.shape) azimuthal_angles = np.zeros(offsets.shape) # set all azimuthal_angles=pi/2, rabi_rotations = pi rabi_rotations[0:] = np.pi azimuthal_angles[0:] = np.pi / 2 if pre_post_rotation: offsets = np.insert(offsets, [0, offsets.shape[0]], [0, duration]) rabi_rotations = np.insert(rabi_rotations, [0, rabi_rotations.shape[0]], [np.pi / 2, np.pi / 2]) azimuthal_angles = np.insert(azimuthal_angles, [0, azimuthal_angles.shape[0]], [0, 0]) detuning_rotations = np.zeros(offsets.shape) return DynamicDecouplingSequence(duration=duration, offsets=offsets, rabi_rotations=rabi_rotations, azimuthal_angles=azimuthal_angles, detuning_rotations=detuning_rotations, **kwargs)
def new_periodic_single_axis_sequence( duration=None, # pylint: disable=invalid-name number_of_offsets=None, pre_post_rotation=False, **kwargs): """Periodic Single Axis Sequence. Parameters --------- duration : float Total duration of the sequence. Defaults to None number_of_offsets : int, optional Number of offsets. Defaults to None pre_post_rotation : bool, optional If True, a :math:`\\pi.2` rotation is added at the start and end of the sequence. kwargs : dict Additional keywords required by qctrlopencontrols.sequences.DynamicDecouplingSequence Returns ------- qctrlopencontrols.sequences.DynamicDecouplingSequence Periodic (single-axis) sequence Raises ------ ArgumentsValueError Raised when an argument is invalid. """ duration = _check_duration(duration) if number_of_offsets is None: number_of_offsets = 1 number_of_offsets = int(number_of_offsets) if number_of_offsets <= 0.: raise ArgumentsValueError('Number of offsets must be above zero:', {'number_of_offsets': number_of_offsets}) spacing = 1. / (number_of_offsets + 1) deltas = [k * spacing for k in range(1, number_of_offsets + 1)] deltas = np.array(deltas) offsets = duration * deltas rabi_rotations = np.zeros(offsets.shape) rabi_rotations[0:] = np.pi if pre_post_rotation: offsets = np.insert(offsets, [0, offsets.shape[0]], [0, duration]) rabi_rotations = np.insert(rabi_rotations, [0, rabi_rotations.shape[0]], [np.pi / 2, np.pi / 2]) azimuthal_angles = np.zeros(offsets.shape) detuning_rotations = np.zeros(offsets.shape) return DynamicDecouplingSequence(duration=duration, offsets=offsets, rabi_rotations=rabi_rotations, azimuthal_angles=azimuthal_angles, detuning_rotations=detuning_rotations, **kwargs)
def _predefined_common_attributes(maximum_rabi_rate, rabi_rotation, azimuthal_angle): """ Adds some checks etc for all the predefined pulses Parameters ---------- rabi_rotation : float The total polar angle to be performed by the pulse. Defined in polar coordinates. maximum_rabi_rate : float Defaults to 2.*np.pi The maximum rabi frequency for the pulse. azimuthal_angle : float The azimuthal position of the pulse. Returns ------- tuple Tuple of floats made of: (rabi_rate, rabi_rotation, azimuthal) Raises ------ ArgumentsValueError Raised when an argument is invalid. """ maximum_rabi_rate = float(maximum_rabi_rate) if maximum_rabi_rate <= 0: raise ArgumentsValueError( 'Maximum rabi angular frequency should be greater than zero.', {'maximum_rabi_rate': maximum_rabi_rate}) rabi_rotation = float(rabi_rotation) if rabi_rotation == 0: raise ArgumentsValueError('The rabi rotation must be non zero.', {'rabi_rotation': rabi_rotation}) azimuthal_angle = float(azimuthal_angle) return (maximum_rabi_rate, rabi_rotation, azimuthal_angle)
def _check_maximum_rotation_rate( maximum_rabi_rate, maximum_detuning_rate): """Checks if the maximum rabi and detuning rate are within valid limits Parameters ---------- maximum_rabi_rate : float, optional Maximum Rabi Rate; Defaults to 1.0 maximum_detuning_rate : float, optional Maximum Detuning Rate; Defaults to None Raises ------ ArgumentsValueError Raised when an argument is invalid or a valid driven control cannot be created from the sequence parameters, maximum rabi rate and maximum detuning rate provided """ # check against global parameters if maximum_rabi_rate < 0. or maximum_rabi_rate > UPPER_BOUND_RABI_RATE: raise ArgumentsValueError( 'Maximum rabi rate must be between 0. and maximum value of {0}'.format( UPPER_BOUND_RABI_RATE), {'maximum_rabi_rate': maximum_rabi_rate}, extras={'maximum_detuning_rate': maximum_detuning_rate, 'allowed_maximum_rabi_rate': UPPER_BOUND_RABI_RATE}) if maximum_detuning_rate < 0. or maximum_detuning_rate > UPPER_BOUND_DETUNING_RATE: raise ArgumentsValueError( 'Maximum detuning rate must be between 0. and maximum value of {0}'.format( UPPER_BOUND_DETUNING_RATE), {'maximum_detuning_rate': maximum_detuning_rate, }, extras={'maximum_rabi_rate': maximum_rabi_rate, 'allowed_maximum_rabi_rate': UPPER_BOUND_RABI_RATE, 'allowed_maximum_detuning_rate': UPPER_BOUND_DETUNING_RATE})
def new_ramsey_sequence(duration=None, **kwargs): """Ramsey sequence Parameters ---------- duration : float, optional Total duration of the sequence. Defaults to None kwargs : dict Additional keywords required by qctrlopencontrols.sequences.DynamicDecouplingSequence Returns ------- qctrlopencontrols.sequence.DynamicDecouplingSequence The Ramsey sequence Raises ------ ArgumentsValueError Raised when an argument is invalid. """ if duration is None: duration = 1. if duration <= 0.: raise ArgumentsValueError('Sequence duration must be above zero:', {'duration': duration}) offsets = np.array([0.0, duration]) rabi_rotations = np.zeros(offsets.shape) azimuthal_angles = np.zeros(offsets.shape) detuning_rotations = np.zeros(offsets.shape) return DynamicDecouplingSequence(duration=duration, offsets=offsets, rabi_rotations=rabi_rotations, azimuthal_angles=azimuthal_angles, detuning_rotations=detuning_rotations, **kwargs)
def _check_duration(duration): """Validates sequence duration Parameters ---------- duration : float, optional Total duration of the sequence. Defaults to None Returns ------- float The validated duration Raises ------ ArgumentsValueError If the duration is negative """ if duration is None: duration = 1. if duration <= 0.: raise ArgumentsValueError('Sequence duration must be above zero:', {'duration': duration}) return duration
def get_plot_formatted_arrays(self, coordinates=CARTESIAN, dimensionless_rabi_rate=True): """ Gets arrays for plotting a driven control. Parameters ---------- dimensionless_rabi_rate: boolean If True, calculates the dimensionless values for segments coordinates : string Indicated the type of segments that need to be transformed can be 'cartesian' or 'cylindrical'. Returns ------- dict A dict with keywords depending on the chosen coordinates. For 'cylindrical', we have 'rabi_rate', 'azimuthal_angle', 'detuning' and 'times', and for 'cartesian' we have 'amplitude_x', 'amplitude_y', 'detuning' and 'times'. Notes ----- The plot data can have repeated times and for amplitudes, because it is expected that these coordinates are to be used with plotting software that 'joins the dots' with linear lines between each coordinate. The time array gives the x values for all the amplitude arrays, which give the y values. Raises ------ ArgumentsValueError Raised when an argument is invalid. """ if coordinates not in [CARTESIAN, CYLINDRICAL]: raise ArgumentsValueError('Unsupported coordinates provided: ', arguments={'coordinates': coordinates}) if dimensionless_rabi_rate: normalizer = self.maximum_rabi_rate else: normalizer = 1 if coordinates == CARTESIAN: control_segments = np.vstack( (self.amplitude_x / normalizer, self.amplitude_y / normalizer, self.detunings, self.durations)).T elif coordinates == CYLINDRICAL: control_segments = np.vstack( (self.rabi_rates / normalizer, self.azimuthal_angles, self.detunings, self.durations)).T segment_times = np.insert(np.cumsum(control_segments[:, 3]), 0, 0.) plot_time = (segment_times[:, np.newaxis] * np.ones((1, 2))).flatten() plot_amplitude_x = control_segments[:, 0] plot_amplitude_y = control_segments[:, 1] plot_amplitude_z = control_segments[:, 2] plot_amplitude_x = np.concatenate( ([0.], (plot_amplitude_x[:, np.newaxis] * np.ones( (1, 2))).flatten(), [0.])) plot_amplitude_y = np.concatenate( ([0.], (plot_amplitude_y[:, np.newaxis] * np.ones( (1, 2))).flatten(), [0.])) plot_amplitude_z = np.concatenate( ([0.], (plot_amplitude_z[:, np.newaxis] * np.ones( (1, 2))).flatten(), [0.])) plot_dictionary = {} if coordinates == CARTESIAN: plot_dictionary = { 'amplitudes_x': plot_amplitude_x, 'amplitudes_y': plot_amplitude_y, 'detunings': plot_amplitude_z, 'times': plot_time } if coordinates == CYLINDRICAL: x_plot = plot_amplitude_x y_plot = plot_amplitude_y x_plot[np.equal(x_plot, -0.0)] = 0. y_plot[np.equal(y_plot, -0.0)] = 0. azimuthal_angles_plot = np.arctan2(y_plot, x_plot) amplitudes_plot = np.sqrt(np.abs(x_plot**2 + y_plot**2)) plot_dictionary = { 'rabi_rates': amplitudes_plot, 'azimuthal_angles': azimuthal_angles_plot, 'detunings': plot_amplitude_z, 'times': plot_time } return plot_dictionary
def convert_dds_to_quantum_circuit(dynamic_decoupling_sequence, target_qubits=None, gate_time=0.1, add_measurement=True, algorithm=INSTANT_UNITARY, quantum_registers=None, circuit_name=None): """Converts a Dynamic Decoupling Sequence into QuantumCircuit as defined in Qiskit Parameters ---------- dynamic_decoupling_sequence : DynamicDecouplingSequence The dynamic decoupling sequence target_qubits : list, optional List of integers specifying target qubits for the sequence operation; defaults to None gate_time : float, optional Time (in seconds) delay introduced by a gate; defaults to 0.1 add_measurement : bool, optional If True, the circuit contains a measurement operation for each of the target qubits and a set of ClassicalRegister objects created with length equal to `len(target_qubits)` algorithm : str, optional One of 'fixed duration unitary' or 'instant unitary'; In the case of 'fixed duration unitary', the sequence operations are assumed to be taking the amount of gate_time while 'instant unitary' assumes the sequence operations are instantaneous (and hence does not contribute to the delay between offsets). Defaults to 'instant unitary'. quantum_registers : QuantumRegister, optional The set of quantum registers; defaults to None If not None, it must have the target qubit specified in `target_qubit` indices list circuit_name : str, optional A string indicating the name of the circuit; defaults to None Returns ------- QuantumCircuit The circuit defined from the specified dynamic decoupling sequence Raises ------ ArgumentsValueError If any of the input parameters are invalid Notes ----- Dynamic Decoupling Sequences (DDS) consist of idealized pulse operation. Theoretically, these operations (pi-pulses in X,Y or Z) occur instantaneously. However, in practice, pulses require time. Therefore, this method of converting an idealized sequence results to a circuit that is only an approximate implementation of the idealized sequence. In idealized definition of DDS, `offsets` represents the instances within sequence `duration` where a pulse occurs instantaneously. A series of appropriate circuit component is placed in order to represent these pulses. The `gaps` or idle time in between active pulses are filled up with `identity` gates. Each identity gate introduces a delay of `gate_time`. In this implementation, the number of identity gates is determined by :math:`np.int(np.floor(offset_distance / gate_time))`. As a consequence, the duration of the real-circuit is :math:`gate_time \\times number_of_identity_gates + pulse_gate_time \\times number_of_pulses`. Q-CTRL Open Controls support operation resulting in rotation around at most one axis at any offset. """ if dynamic_decoupling_sequence is None: raise ArgumentsValueError( 'No dynamic decoupling sequence provided.', {'dynamic_decoupling_sequence': dynamic_decoupling_sequence}) if not isinstance(dynamic_decoupling_sequence, DynamicDecouplingSequence): raise ArgumentsValueError( 'Dynamical decoupling sequence is not recognized.' 'Expected DynamicDecouplingSequence instance', { 'type(dynamic_decoupling_sequence)': type(dynamic_decoupling_sequence) }) if target_qubits is None: target_qubits = [0] if gate_time <= 0: raise ArgumentsValueError( 'Time delay of identity gate must be greater than zero.', {'gate_time': gate_time}) if np.any(target_qubits) < 0: raise ArgumentsValueError( 'Every target qubits index must be positive.', {'target_qubits': target_qubits}) if algorithm not in [FIX_DURATION_UNITARY, INSTANT_UNITARY]: raise ArgumentsValueError( 'Algorithm must be one of {} or {}'.format(INSTANT_UNITARY, FIX_DURATION_UNITARY), {'algorithm': algorithm}) if quantum_registers is not None: if (max(target_qubits) + 1) > len(quantum_registers): raise ArgumentsValueError( 'Target qubit is not present in quantum_registers', { 'target_qubits': target_qubits, 'size(quantum_registers)': len(quantum_registers) }, extras={'max(target_qubits)': max(target_qubits)}) quantum_registers = quantum_registers else: quantum_registers = QuantumRegister(max(target_qubits) + 1) classical_registers = None if add_measurement: classical_registers = ClassicalRegister(len(target_qubits)) quantum_circuit = QuantumCircuit(quantum_registers, classical_registers) else: quantum_circuit = QuantumCircuit(quantum_registers) if circuit_name is not None: quantum_circuit.name = circuit_name unitary_time = 0. if algorithm == FIX_DURATION_UNITARY: unitary_time = gate_time rabi_rotations = dynamic_decoupling_sequence.rabi_rotations azimuthal_angles = dynamic_decoupling_sequence.azimuthal_angles detuning_rotations = dynamic_decoupling_sequence.detuning_rotations if len(rabi_rotations.shape) == 1: rabi_rotations = rabi_rotations[np.newaxis, :] if len(azimuthal_angles.shape) == 1: azimuthal_angles = azimuthal_angles[np.newaxis, :] if len(detuning_rotations.shape) == 1: detuning_rotations = detuning_rotations[np.newaxis, :] operations = np.vstack( (rabi_rotations, azimuthal_angles, detuning_rotations)) offsets = dynamic_decoupling_sequence.offsets time_covered = 0 for operation_idx in range(operations.shape[1]): offset_distance = offsets[operation_idx] - time_covered if np.isclose(offset_distance, 0.0): offset_distance = 0.0 if offset_distance < 0: raise ArgumentsValueError("Offsets cannot be placed properly", {'sequence_operations': operations}) if offset_distance > 0: while (time_covered + gate_time) <= offsets[operation_idx]: for qubit in target_qubits: quantum_circuit.iden(quantum_registers[qubit]) # pylint: disable=no-member quantum_circuit.barrier(quantum_registers[qubit]) # pylint: disable=no-member time_covered += gate_time rabi_rotation = operations[0, operation_idx] azimuthal_angle = operations[1, operation_idx] x_rotation = rabi_rotation * np.cos(azimuthal_angle) y_rotation = rabi_rotation * np.sin(azimuthal_angle) z_rotation = operations[2, operation_idx] rotations = np.array([x_rotation, y_rotation, z_rotation]) zero_pulses = np.isclose(rotations, 0.0).astype(np.int) nonzero_pulse_counts = 3 - np.sum(zero_pulses) if nonzero_pulse_counts > 1: raise ArgumentsValueError( 'Open Controls support a sequence with one ' 'valid pulse at any offset. Found sequence ' 'with multiple rotation operations at an offset.', { 'dynamic_decoupling_sequence': str(dynamic_decoupling_sequence), 'offset': dynamic_decoupling_sequence.offsets[operation_idx], 'rabi_rotation': dynamic_decoupling_sequence.rabi_rotations[operation_idx], 'azimuthal_angle': dynamic_decoupling_sequence. azimuthal_angles[operation_idx], 'detuning_rotaion': dynamic_decoupling_sequence. detuning_rotations[operation_idx] }) for qubit in target_qubits: if nonzero_pulse_counts == 0: quantum_circuit.u3( 0., 0., 0., # pylint: disable=no-member quantum_registers[qubit]) else: if not np.isclose(rotations[0], 0.0): quantum_circuit.u3( rotations[0], -pi / 2, pi / 2, # pylint: disable=no-member quantum_registers[qubit]) elif not np.isclose(rotations[1], 0.0): quantum_circuit.u3( rotations[1], 0., 0., # pylint: disable=no-member quantum_registers[qubit]) elif not np.isclose(rotations[2], 0.): quantum_circuit.u1( rotations[2], # pylint: disable=no-member quantum_registers[qubit]) quantum_circuit.barrier(quantum_registers[qubit]) # pylint: disable=no-member if np.isclose(np.sum(rotations), 0.0): time_covered = offsets[operation_idx] else: time_covered = offsets[operation_idx] + unitary_time if add_measurement: for q_index, qubit in enumerate(target_qubits): quantum_circuit.measure( quantum_registers[qubit], #pylint: disable=no-member classical_registers[q_index]) return quantum_circuit
def get_plot_formatted_arrays(self, plot_format=MATPLOTLIB): """Gets arrays for plotting a pulse. Parameters ---------- plot_format : string, optional Indicates the format of the plot; Defaults to `matplotlib` Returns ------- dict A dict with keywords 'rabi_rotations', 'azimuthal_angles', 'detuning_rotations' and 'times'. Raises ------ ArgumentsValueError Raised if `plot_format` is not recognized. """ if plot_format != MATPLOTLIB: raise ArgumentsValueError( "Open Controls currently supports `matplotlib` " "data format only.", {'data_format': plot_format}) offsets = self.offsets number_of_offsets = self.number_of_offsets plot_data = dict() rabi_rotations = self.rabi_rotations azimuthal_angles = self.azimuthal_angles detuning_rotations = self.detuning_rotations rabi_rotations = np.reshape(rabi_rotations, (-1, 1)) azimuthal_angles = np.reshape(azimuthal_angles, (-1, 1)) detuning_rotations = np.reshape(detuning_rotations, (-1, 1)) plot_times = offsets[:, np.newaxis] plot_times = np.repeat(plot_times, 3, axis=1) multiplier = np.array([0, 1, 0]) multiplier = multiplier[np.newaxis, :] multiplier = np.repeat(multiplier, number_of_offsets, axis=0) multiplier = multiplier[np.newaxis, :] rabi_rotations = rabi_rotations * multiplier azimuthal_angles = azimuthal_angles * multiplier detuning_rotations = detuning_rotations * multiplier plot_times = plot_times.flatten() rabi_rotations = rabi_rotations.flatten() azimuthal_angles = azimuthal_angles.flatten() detuning_rotations = detuning_rotations.flatten() plot_data['rabi_rotations'] = rabi_rotations plot_data['azimuthal_angles'] = azimuthal_angles plot_data['detuning_rotations'] = detuning_rotations plot_data['times'] = plot_times return plot_data
def convert_dds_to_cirq_schedule(dynamic_decoupling_sequence, target_qubits=None, gate_time=0.1, add_measurement=True, device=None): """Converts a Dynamic Decoupling Sequence into schedule as defined in cirq Parameters ---------- dynamic_decoupling_sequence : DynamicDecouplingSequence The dynamic decoupling sequence target_qubits : list, optional List of target qubits for the sequence operation; the qubits must be cirq.Qid type; defaults to None in which case a 1-D lattice of one qubit is used (indexed as 0). gate_time : float, optional Time (in seconds) delay introduced by a gate; defaults to 0.1 add_measurement : bool, optional If True, the schedule contains a measurement operation for each of the target qubits. Measurement from each of the qubits is associated with a string as key. The string is formatted as 'qubit-X' where X is a number between 0 and len(target_qubits). device : cirq.Device, optional A cirq.Device that specifies hardware constraints for validating operations. If None, a unconstrained device is used. See `Cirq Documentation <https://cirq.readthedocs.io/en/stable/schedules.html/>` _. Returns ------- cirq.Schedule The schedule of sequence rotation operations. Raises ------ ArgumentsValueError If any of the input parameters result in an invalid operation. Notes ----- Dynamic Decoupling Sequences (DDS) consist of idealized pulse operation. Theoretically, these operations (pi-pulses in X,Y or Z) occur instantaneously. However, in practice, pulses require time. Therefore, this method of converting an idealized sequence results to a schedule that is only an approximate implementation of the idealized sequence. In idealized definition of DDS, `offsets` represents the instances within sequence `duration` where a pulse occurs instantaneously. A series of appropriate rotation operations is placed in order to represent these pulses. In cirq.schedule, the active pulses are scheduled to be activated at a certain instant calculated from the start of the sequence and continues for a duration of gate_time. This does not require identity gates to be placed between offsets. Q-CTRL Open Controls support operation resulting in rotation around at most one axis at any offset. """ if dynamic_decoupling_sequence is None: raise ArgumentsValueError( 'No dynamic decoupling sequence provided.', {'dynamic_decoupling_sequence': dynamic_decoupling_sequence}) if not isinstance(dynamic_decoupling_sequence, DynamicDecouplingSequence): raise ArgumentsValueError( 'Dynamical decoupling sequence is not recognized.' 'Expected DynamicDecouplingSequence instance', { 'type(dynamic_decoupling_sequence)': type(dynamic_decoupling_sequence) }) if gate_time <= 0: raise ArgumentsValueError( 'Time delay of gates must be greater than zero.', {'gate_time': gate_time}) if target_qubits is None: target_qubits = [cirq.LineQubit(0)] if device is None: device = cirq.UnconstrainedDevice if not isinstance(device, cirq.Device): raise ArgumentsValueError('Device must be a cirq.Device type.', {'device': device}) # time in nano seconds gate_time = gate_time * 1e9 rabi_rotations = dynamic_decoupling_sequence.rabi_rotations azimuthal_angles = dynamic_decoupling_sequence.azimuthal_angles detuning_rotations = dynamic_decoupling_sequence.detuning_rotations if len(rabi_rotations.shape) == 1: rabi_rotations = rabi_rotations[np.newaxis, :] if len(azimuthal_angles.shape) == 1: azimuthal_angles = azimuthal_angles[np.newaxis, :] if len(detuning_rotations.shape) == 1: detuning_rotations = detuning_rotations[np.newaxis, :] operations = np.vstack( (rabi_rotations, azimuthal_angles, detuning_rotations)) offsets = dynamic_decoupling_sequence.offsets # offsets in nano seconds offsets = offsets * 1e9 scheduled_operations = [] offset_count = 0 for op_idx in range(operations.shape[1]): rabi_rotation = dynamic_decoupling_sequence.rabi_rotations[ offset_count] azimuthal_angle = dynamic_decoupling_sequence.azimuthal_angles[ offset_count] x_rotation = rabi_rotation * np.cos(azimuthal_angle) y_rotation = rabi_rotation * np.sin(azimuthal_angle) z_rotation = dynamic_decoupling_sequence.detuning_rotations[ offset_count] rotations = np.array([x_rotation, y_rotation, z_rotation]) zero_pulses = np.isclose(rotations, 0.0).astype(np.int) nonzero_pulse_counts = 3 - np.sum(zero_pulses) if nonzero_pulse_counts > 1: raise ArgumentsValueError( 'Open Controls support a sequence with one ' 'valid pulse at any offset. Found sequence ' 'with multiple rotation operations at an offset.', { 'dynamic_decoupling_sequence': str(dynamic_decoupling_sequence), 'offset': dynamic_decoupling_sequence.offsets[op_idx], 'rabi_rotation': dynamic_decoupling_sequence.rabi_rotations[op_idx], 'azimuthal_angle': dynamic_decoupling_sequence.azimuthal_angles[op_idx], 'detuning_rotaion': dynamic_decoupling_sequence.detuning_rotations[op_idx] }) for qubit in target_qubits: if nonzero_pulse_counts == 0: operation = cirq.ScheduledOperation( time=cirq.Timestamp(nanos=offsets[op_idx]), duration=cirq.Duration(nanos=gate_time), operation=cirq.I(qubit)) else: if not np.isclose(rotations[0], 0.0): operation = cirq.ScheduledOperation( time=cirq.Timestamp(nanos=offsets[op_idx]), duration=cirq.Duration(nanos=gate_time), operation=cirq.Rx(rotations[0])(qubit)) elif not np.isclose(rotations[1], 0.0): operation = cirq.ScheduledOperation( time=cirq.Timestamp(nanos=offsets[op_idx]), duration=cirq.Duration(nanos=gate_time), operation=cirq.Rx(rotations[1])(qubit)) elif not np.isclose(rotations[2], 0.): operation = cirq.ScheduledOperation( time=cirq.Timestamp(nanos=offsets[op_idx]), duration=cirq.Duration(nanos=gate_time), operation=cirq.Rx(rotations[2])(qubit)) offset_count += 1 scheduled_operations.append(operation) if add_measurement: for idx, qubit in enumerate(target_qubits): operation = cirq.ScheduledOperation( time=cirq.Timestamp(nanos=offsets[-1] + gate_time), duration=cirq.Duration(nanos=gate_time), operation=cirq.MeasurementGate( 1, key='qubit-{}'.format(idx))(qubit)) scheduled_operations.append(operation) schedule = cirq.Schedule(device=device, scheduled_operations=scheduled_operations) return schedule
def _get_circuit_gate_list(dynamic_decoupling_sequence, gate_time, unitary_time): """Converts the operations in a sequence into list of gates of a quantum circuit Parameters ---------- dynamic_decoupling_sequence : DynamicDecouplingSequence Dynamic decoupling sequence instance gate_time : float Indicates the delay time of the identity gates unitary_time : float Indicates the delay time introduced by unitary gates Returns ------- list A list of circuit components with required parameters Raises ------ ArgumentsValueError If the offsets cannot be placed properly """ rabi_rotations = dynamic_decoupling_sequence.rabi_rotations azimuthal_angles = dynamic_decoupling_sequence.azimuthal_angles detuning_rotations = dynamic_decoupling_sequence.detuning_rotations if len(rabi_rotations.shape) == 1: rabi_rotations = rabi_rotations[np.newaxis, :] if len(azimuthal_angles.shape) == 1: azimuthal_angles = azimuthal_angles[np.newaxis, :] if len(detuning_rotations.shape) == 1: detuning_rotations = detuning_rotations[np.newaxis, :] operations = np.vstack( (rabi_rotations, azimuthal_angles, detuning_rotations)) offsets = dynamic_decoupling_sequence.offsets time_covered = 0 circuit_operations = [] for operation_idx in range(operations.shape[1]): offset_distance = offsets[operation_idx] - time_covered if np.isclose(offset_distance, 0.0): offset_distance = 0.0 if offset_distance < 0: raise ArgumentsValueError("Offsets cannot be placed properly", {'sequence_operations': operations}) elif offset_distance == 0: circuit_operations.append('offset') if np.isclose(np.sum(operations[:, operation_idx]), 0.0): time_covered = offsets[operation_idx] else: time_covered = offsets[operation_idx] + unitary_time else: number_of_id_gates = 0 while (time_covered + (number_of_id_gates+1) * gate_time) <= \ offsets[operation_idx]: circuit_operations.append('id') number_of_id_gates += 1 circuit_operations.append('offset') time_covered = offsets[operation_idx] + unitary_time return circuit_operations
def new_predefined_dds(scheme=SPIN_ECHO, **kwargs): """Create a new instance of ne of the predefined dynamic decoupling sequence Parameters ---------- scheme : string The name of the sequence; Defaults to 'Spin echo' Available options are, - 'Ramsey' - 'Spin echo', - 'Carr-Purcell', - 'Carr-Purcell-Meiboom-Gill', - 'Uhrig single-axis' - 'Periodic single-axis' - 'Walsh single-axis' - 'Quadratic' - 'X concatenated' - 'XY concatenated' kwargs : dict, optional Additional keyword argument to create the sequence Returns ------ qctrlopencontrols.sequences.DynamicDecouplingSequence Returns a sequence corresponding to the name Raises ----- ArgumentsValueError Raised when an argument is invalid. """ if scheme == RAMSEY: sequence = new_ramsey_sequence(**kwargs) elif scheme == SPIN_ECHO: sequence = new_spin_echo_sequence(**kwargs) elif scheme == CARR_PURCELL: sequence = new_carr_purcell_sequence(**kwargs) elif scheme == CARR_PURCELL_MEIBOOM_GILL: sequence = new_carr_purcell_meiboom_gill_sequence(**kwargs) elif scheme == UHRIG_SINGLE_AXIS: sequence = new_uhrig_single_axis_sequence(**kwargs) elif scheme == PERIODIC_SINGLE_AXIS: sequence = new_periodic_single_axis_sequence(**kwargs) elif scheme == WALSH_SINGLE_AXIS: sequence = new_walsh_single_axis_sequence(**kwargs) elif scheme == QUADRATIC: sequence = new_quadratic_sequence(**kwargs) elif scheme == X_CONCATENATED: sequence = new_x_concatenated_sequence(**kwargs) elif scheme == XY_CONCATENATED: sequence = new_xy_concatenated_sequence(**kwargs) # Raise an error if the input sequence is not known else: raise ArgumentsValueError( 'Unknown predefined sequence scheme. Allowed schemes are: ' + ', '.join([ RAMSEY, SPIN_ECHO, CARR_PURCELL, CARR_PURCELL_MEIBOOM_GILL, UHRIG_SINGLE_AXIS, PERIODIC_SINGLE_AXIS, WALSH_SINGLE_AXIS, QUADRATIC, X_CONCATENATED, XY_CONCATENATED ]) + '.', {'sequence_name': scheme}) return sequence
def new_x_concatenated_sequence(duration=1.0, concatenation_order=None, **kwargs): """X-Concatenated Dynamic Decoupling Sequence Concatenation of base sequence C(\tau/2)XC(\tau/2)X Parameters ---------- duration : float, optional defaults to None The total duration of the sequence concatenation_order : int, optional defaults to None The number of concatenation of base sequence kwargs : dict Additional keywords required by qctrlopencontrols.sequences.DynamicDecouplingSequence Returns ------- qctrlopencontrols.sequences.DynamicDecouplingSequence X concatenated sequence Raises ------ ArgumentsValueError Raised when an argument is invalid. """ if duration is None: duration = 1. if duration <= 0.: raise ArgumentsValueError('Sequence duration must be above zero:', {'duration': duration}) if concatenation_order is None: concatenation_order = 1 concatenation_order = int(concatenation_order) if concatenation_order <= 0.: raise ArgumentsValueError('Concatenation oder must be above zero:', {'concatenation_order': concatenation_order}, extras={'duration': duration}) unit_spacing = duration / (2**concatenation_order) cumulations = _concatenation_x(concatenation_order) pos_cum = cumulations * unit_spacing pos_cum_sum = np.cumsum(pos_cum) values, counts = np.unique(pos_cum_sum, return_counts=True) offsets = [values[i] for i in range(counts.shape[0]) if counts[i] % 2 == 0] if concatenation_order % 2 == 1: offsets = offsets[0:-1] offsets = np.array(offsets) rabi_rotations = np.pi * np.ones(offsets.shape) azimuthal_angles = np.zeros(offsets.shape) detuning_rotations = np.zeros(offsets.shape) return DynamicDecouplingSequence(duration=duration, offsets=offsets, rabi_rotations=rabi_rotations, azimuthal_angles=azimuthal_angles, detuning_rotations=detuning_rotations, **kwargs)
def new_walsh_single_axis_sequence(duration=None, paley_order=None, **kwargs): """Welsh Single Axis Sequence. Parameters --------- duration : float Total duration of the sequence. Defaults to None paley_order : int, optional Defaults to 1. The paley order of the walsh sequence. kwargs : dict Additional keywords required by qctrlopencontrols.sequences.DynamicDecouplingSequence Returns ------- qctrlopencontrols.sequences.DynamicDecouplingSequence Walsh (single-axis) sequence Raises ------ ArgumentsValueError Raised when an argument is invalid. """ if duration is None: duration = 1. if duration <= 0.: raise ArgumentsValueError('Sequence duration must be above zero:', {'duration': duration}) if paley_order is None: paley_order = 1 paley_order = int(paley_order) if paley_order < 1 or paley_order > 2000: raise ArgumentsValueError('Paley order must be between 1 and 2000.', {'paley_order': paley_order}) hamming_weight = int(np.floor(np.log2(paley_order))) + 1 samples = 2**hamming_weight relative_offset = np.arange(1. / (2 * samples), 1., 1. / samples) binary_string = np.binary_repr(paley_order) binary_order = [int(binary_string[i]) for i in range(hamming_weight)] walsh_array = np.ones([samples]) for i in range(hamming_weight): walsh_array *= np.sign( np.sin(2**(i + 1) * np.pi * relative_offset))**binary_order[hamming_weight - 1 - i] walsh_relative_offsets = [] for i in range(samples - 1): if walsh_array[i] != walsh_array[i + 1]: walsh_relative_offsets.append((i + 1) * (1. / samples)) walsh_relative_offsets = np.array(walsh_relative_offsets, dtype=np.float) offsets = duration * walsh_relative_offsets rabi_rotations = np.zeros(offsets.shape) azimuthal_angles = np.zeros(offsets.shape) detuning_rotations = np.zeros(offsets.shape) # set the rabi_rotations to X_pi rabi_rotations[0:] = np.pi return DynamicDecouplingSequence(duration=duration, offsets=offsets, rabi_rotations=rabi_rotations, azimuthal_angles=azimuthal_angles, detuning_rotations=detuning_rotations, **kwargs)
def convert_dds_to_cirq_circuit(dynamic_decoupling_sequence, target_qubits=None, gate_time=0.1, add_measurement=True, algorithm=INSTANT_UNITARY): """Converts a Dynamic Decoupling Sequence into quantum circuit as defined in cirq Parameters ---------- dynamic_decoupling_sequence : DynamicDecouplingSequence The dynamic decoupling sequence target_qubits : list, optional List of target qubits for the sequence operation; the qubits must be cirq.Qid type; defaults to None in which case a 1-D lattice of one qubit is used (indexed as 0). gate_time : float, optional Time (in seconds) delay introduced by a gate; defaults to 0.1 add_measurement : bool, optional If True, the circuit contains a measurement operation for each of the target qubits. Measurement from each of the qubits is associated with a string as key. The string is formatted as 'qubit-X' where X is a number between 0 and len(target_qubits). algorithm : str, optional One of 'fixed duration unitary' or 'instant unitary'; In the case of 'fixed duration unitary', the sequence operations are assumed to be taking the amount of gate_time while 'instant unitary' assumes the sequence operations are instantaneous (and hence does not contribute to the delay between offsets). Defaults to 'instant unitary'. Returns ------- cirq.Circuit The circuit containing gates corresponding to sequence operation. Raises ------ ArgumentsValueError If any of the input parameters result in an invalid operation. Notes ----- Dynamic Decoupling Sequences (DDS) consist of idealized pulse operation. Theoretically, these operations (pi-pulses in X,Y or Z) occur instantaneously. However, in practice, pulses require time. Therefore, this method of converting an idealized sequence results to a circuit that is only an approximate implementation of the idealized sequence. In idealized definition of DDS, `offsets` represents the instances within sequence `duration` where a pulse occurs instantaneously. A series of appropriate circuit components is placed in order to represent these pulses. In 'standard circuit', the `gaps` or idle time in between active pulses are filled up with `identity` gates. Each identity gate introduces a delay of `gate_time`. In this implementation, the number of identity gates is determined by :math:`np.int(np.floor(offset_distance / gate_time))`. As a consequence, :math:`np.int(np.floor(offset_distance / gate_time))`. As a consequence, the duration of the real-circuit is :math:`gate_time \\times number_of_identity_gates + pulse_gate_time \\times number_of_pulses`. Q-CTRL Open Controls support operation resulting in rotation around at most one axis at any offset. """ if dynamic_decoupling_sequence is None: raise ArgumentsValueError( 'No dynamic decoupling sequence provided.', {'dynamic_decoupling_sequence': dynamic_decoupling_sequence}) if not isinstance(dynamic_decoupling_sequence, DynamicDecouplingSequence): raise ArgumentsValueError( 'Dynamical decoupling sequence is not recognized.' 'Expected DynamicDecouplingSequence instance', { 'type(dynamic_decoupling_sequence)': type(dynamic_decoupling_sequence) }) if gate_time <= 0: raise ArgumentsValueError( 'Time delay of gates must be greater than zero.', {'gate_time': gate_time}) if target_qubits is None: target_qubits = [cirq.LineQubit(0)] if algorithm not in [FIX_DURATION_UNITARY, INSTANT_UNITARY]: raise ArgumentsValueError( 'Algorithm must be one of {} or {}'.format(INSTANT_UNITARY, FIX_DURATION_UNITARY), {'algorithm': algorithm}) unitary_time = 0. if algorithm == FIX_DURATION_UNITARY: unitary_time = gate_time rabi_rotations = dynamic_decoupling_sequence.rabi_rotations azimuthal_angles = dynamic_decoupling_sequence.azimuthal_angles detuning_rotations = dynamic_decoupling_sequence.detuning_rotations if len(rabi_rotations.shape) == 1: rabi_rotations = rabi_rotations[np.newaxis, :] if len(azimuthal_angles.shape) == 1: azimuthal_angles = azimuthal_angles[np.newaxis, :] if len(detuning_rotations.shape) == 1: detuning_rotations = detuning_rotations[np.newaxis, :] operations = np.vstack( (rabi_rotations, azimuthal_angles, detuning_rotations)) offsets = dynamic_decoupling_sequence.offsets time_covered = 0 circuit = cirq.Circuit() for operation_idx in range(operations.shape[1]): offset_distance = offsets[operation_idx] - time_covered if np.isclose(offset_distance, 0.0): offset_distance = 0.0 if offset_distance < 0: raise ArgumentsValueError("Offsets cannot be placed properly", {'sequence_operations': operations}) if offset_distance > 0: while (time_covered + gate_time) <= offsets[operation_idx]: gate_list = [] for qubit in target_qubits: gate_list.append(cirq.I(qubit)) time_covered += gate_time circuit.append(gate_list) rabi_rotation = operations[0, operation_idx] azimuthal_angle = operations[1, operation_idx] x_rotation = rabi_rotation * np.cos(azimuthal_angle) y_rotation = rabi_rotation * np.sin(azimuthal_angle) z_rotation = operations[2, operation_idx] rotations = np.array([x_rotation, y_rotation, z_rotation]) zero_pulses = np.isclose(rotations, 0.0).astype(np.int) nonzero_pulse_counts = 3 - np.sum(zero_pulses) if nonzero_pulse_counts > 1: raise ArgumentsValueError( 'Open Controls support a sequence with one ' 'valid pulse at any offset. Found sequence ' 'with multiple rotation operations at an offset.', { 'dynamic_decoupling_sequence': str(dynamic_decoupling_sequence), 'offset': dynamic_decoupling_sequence.offsets[operation_idx], 'rabi_rotation': dynamic_decoupling_sequence.rabi_rotations[operation_idx], 'azimuthal_angle': dynamic_decoupling_sequence. azimuthal_angles[operation_idx], 'detuning_rotaion': dynamic_decoupling_sequence. detuning_rotations[operation_idx] }) gate_list = [] for qubit in target_qubits: if nonzero_pulse_counts == 0: gate_list.append(cirq.I(qubit)) else: if not np.isclose(rotations[0], 0.0): gate_list.append(cirq.Rx(rotations[0])(qubit)) elif not np.isclose(rotations[1], 0.0): gate_list.append(cirq.Ry(rotations[1])(qubit)) elif not np.isclose(rotations[2], 0.): gate_list.append(cirq.Rz(rotations[2])(qubit)) circuit.append(gate_list) if np.isclose(np.sum(rotations), 0.0): time_covered = offsets[operation_idx] else: time_covered = offsets[operation_idx] + unitary_time if add_measurement: gate_list = [] for idx, qubit in enumerate(target_qubits): gate_list.append(cirq.measure(qubit, key='qubit-{}'.format(idx))) circuit.append(gate_list) return circuit
def __init__(self, rabi_rates=None, azimuthal_angles=None, detunings=None, durations=None, name=None): self.name = name if self.name is not None: self.name = str(self.name) check_none_values = [(rabi_rates is None), (azimuthal_angles is None), (detunings is None), (durations is None)] all_are_none = all(value is True for value in check_none_values) if all_are_none: rabi_rates = np.array([np.pi]) azimuthal_angles = np.array([0.]) detunings = np.array([0.]) durations = np.array([1.]) else: # some may be None while others are not input_array_lengths = [] if not check_none_values[0]: rabi_rates = np.array(rabi_rates, dtype=np.float).reshape( (-1, )) input_array_lengths.append(rabi_rates.shape[0]) if not check_none_values[1]: azimuthal_angles = np.array(azimuthal_angles, dtype=np.float).reshape((-1, )) input_array_lengths.append(len(azimuthal_angles)) if not check_none_values[2]: detunings = np.array(detunings, dtype=np.float).reshape((-1, )) input_array_lengths.append(len(detunings)) if not check_none_values[3]: durations = np.array(durations, dtype=np.float).reshape((-1, )) input_array_lengths.append(len(durations)) # check all valid array lengths are equal if max(input_array_lengths) != min(input_array_lengths): raise ArgumentsValueError( 'Rabi rates, Azimuthal angles, Detunings and Durations ' 'must be of same length', { 'rabi_rates': rabi_rates, 'azimuthal_angles': azimuthal_angles, 'detunings': detunings, 'durations': durations }) valid_input_length = max(input_array_lengths) if check_none_values[0]: rabi_rates = np.zeros((valid_input_length, )) if check_none_values[1]: azimuthal_angles = np.zeros((valid_input_length, )) if check_none_values[2]: detunings = np.zeros((valid_input_length, )) if check_none_values[3]: durations = np.ones((valid_input_length, )) self.rabi_rates = rabi_rates self.azimuthal_angles = azimuthal_angles self.detunings = detunings self.durations = durations # check if all the rabi_rates are greater than zero if np.any(rabi_rates < 0.): raise ArgumentsValueError( 'All rabi rates must be greater than zero.', {'rabi_rates': rabi_rates}, extras={ 'azimuthal_angles': azimuthal_angles, 'detunings': detunings, 'durations': durations }) # check if all the durations are greater than zero if np.any(durations <= 0): raise ArgumentsValueError( 'Duration of driven control segments must all be greater' + ' than zero.', {'durations': self.durations}) self.number_of_segments = rabi_rates.shape[0] if self.number_of_segments > UPPER_BOUND_SEGMENTS: raise ArgumentsValueError( 'The number of segments must be smaller than the upper bound:' + str(UPPER_BOUND_SEGMENTS), {'number_of_segments': self.number_of_segments}) super(DrivenControl, self).__init__(base_attributes=[ 'rabi_rates', 'azimuthal_angles', 'detunings', 'durations', 'name' ]) if self.maximum_rabi_rate > UPPER_BOUND_RABI_RATE: raise ArgumentsValueError( 'Maximum rabi rate of segments must be smaller than the upper bound: ' + str(UPPER_BOUND_RABI_RATE), {'maximum_rabi_rate': self.maximum_rabi_rate}) if self.maximum_detuning > UPPER_BOUND_DETUNING_RATE: raise ArgumentsValueError( 'Maximum detuning of segments must be smaller than the upper bound: ' + str(UPPER_BOUND_DETUNING_RATE), {'maximum_detuning': self.maximum_detuning}) if self.maximum_duration > UPPER_BOUND_DURATION: raise ArgumentsValueError( 'Maximum duration of segments must be smaller than the upper bound: ' + str(UPPER_BOUND_DURATION), {'maximum_duration': self.maximum_duration}) if self.minimum_duration < LOWER_BOUND_DURATION: raise ArgumentsValueError( 'Minimum duration of segments must be larger than the lower bound: ' + str(LOWER_BOUND_DURATION), {'minimum_duration': self.minimum_duration})
def convert_dds_to_quantum_circuit(dynamic_decoupling_sequence, target_qubits=None, gate_time=0.1, pre_post_gate_parameters=None, add_measurement=True, algorithm=FIX_DURATION_UNITARY, quantum_registers=None, circuit_name=None): """Converts a Dynamic Decoupling Sequence into QuantumCircuit as defined in Qiskit Parameters ---------- dynamic_decoupling_sequence : DynamicDecouplingSequence The dynamic decoupling sequence target_qubits : list, optional List of integers specifying target qubits for the sequence operation; defaults to None gate_time : float, optional Time (in seconds) delay introduced by a gate; defaults to 0.1 pre_post_gate_parameters : list, optional List of (length 3) floating point numbers; These numbers correspond to :math:`\\theta, \\phi, \\lambda` parameters in `U3` gate defined in Qiskit as `U3Gate(theta, phi, lamda)`. Qiskit documentation suggests this to be the most generalized definition of unitary gates. Defaults to None; if None, the parameters are assumed to be :math:`[pi/2, -pi/2, pi/2]` that corresponds to `pi/2` rotation around X-axis. See `IBM-Q Documentation <https://quantumexperience.ng.bluemix.net/proxy/tutorial/full-user-guide/ 002-The_Weird_and_Wonderful_World_of_the_Qubit/004-advanced_qubit_gates.html?` _. add_measurement : bool, optional If True, the circuit contains a measurement operation for each of the target qubits and a set of ClassicalRegister objects created with length equal to `len(target_qubits)` algorithm : str, optional One of 'Fixed duration unitary' or 'Instant unitary'; In the case of 'Fixed duration unitary', the operations are assumed to be taking the amount of gate_time while 'Instant unitary' assumes unitaries to be instantaneous; defaults to 'Fixed duration unitary' quantum_registers : QuantumRegister, optional The set of quantum registers; defaults to None If not None, it must have the target qubit specified in `target_qubit` indices list circuit_name : str, optional A string indicating the name of the circuit; defaults to None Returns ------- QuantumCircuit The circuit defined from the specified dynamic decoupling sequence Raises ------ ArgumentsValueError If any of the input parameters are invalid Notes ----- Dynamic Decoupling Sequences (DDS) consist of idealized pulse operation. Theoretically, these operations (pi-pulses in X,Y or Z) occur instantaneously. However, in practice, pulses require time. Therefore, this method of converting an idealized sequence results to a circuit that is only an approximate implementation of the idealized sequence. In idealized definition of DDS, `offsets` represents the instances within sequence `duration` where a pulse occurs instantaneously. A series of appropriate circuit component is placed in order to represent these pulses. The `gaps` or idle time in between active pulses are filled up with `identity` gates. Each identity gate introduces a delay of `gate_delay`. In this implementation, the number of identity gates is determined by :math:`np.int(np.floor(offset_distance / gate_time))`. As a consequence, the duration of the real-circuit is :math:`gate_time \\times number_of_identity_gates + pulse_gate_time \\times number_of_pulses`. Q-CTRL Open Controls support operation resulting in rotation around at most one axis at any offset. """ if dynamic_decoupling_sequence is None: raise ArgumentsValueError( 'No dynamic decoupling sequence provided.', {'dynamic_decoupling_sequence': dynamic_decoupling_sequence}) if not isinstance(dynamic_decoupling_sequence, DynamicDecouplingSequence): raise ArgumentsValueError( 'Dynamical decoupling sequence is not recognized.' 'Expected DynamicDecouplingSequence instance', { 'type(dynamic_decoupling_sequence)': type(dynamic_decoupling_sequence) }) if target_qubits is None: target_qubits = [0] if pre_post_gate_parameters is None: pre_post_gate_parameters = [np.pi / 2, -pi / 2, pi / 2] if len(pre_post_gate_parameters) != 3: raise ArgumentsValueError( 'Pre-Post gate parameters must be a list of 3 ' 'floating point numbers.', {'pre_post_gate_params': pre_post_gate_parameters}) if gate_time <= 0: raise ArgumentsValueError( 'Time delay of identity gate must be greater than zero.', {'identity_gate_time': gate_time}) if np.any(target_qubits) < 0: raise ArgumentsValueError( 'Every target qubits index must be positive.', {'target_qubits': target_qubits}) if algorithm not in [FIX_DURATION_UNITARY, INSTANT_UNITARY]: raise ArgumentsValueError( 'Algorithm must be one of {} or {}'.format(INSTANT_UNITARY, FIX_DURATION_UNITARY), {'algorithm': algorithm}) if quantum_registers is not None: if (max(target_qubits) + 1) > len(quantum_registers): raise ArgumentsValueError( 'Target qubit is not present in quantum_registers', { 'target_qubits': target_qubits, 'size(quantum_registers)': len(quantum_registers) }, extras={'max(target_qubits)': max(target_qubits)}) else: quantum_registers = quantum_registers else: quantum_registers = QuantumRegister(max(target_qubits) + 1) classical_registers = None if add_measurement: classical_registers = ClassicalRegister(len(target_qubits)) quantum_circuit = QuantumCircuit(quantum_registers, classical_registers) else: quantum_circuit = QuantumCircuit(quantum_registers) if circuit_name is not None: quantum_circuit.name = circuit_name unitary_time = 0. if algorithm == FIX_DURATION_UNITARY: unitary_time = gate_time circuit_gate_list = _get_circuit_gate_list( dynamic_decoupling_sequence=dynamic_decoupling_sequence, gate_time=gate_time, unitary_time=unitary_time) for qubit in target_qubits: quantum_circuit.u3( pre_post_gate_parameters[0], #pylint: disable=no-member pre_post_gate_parameters[1], pre_post_gate_parameters[2], quantum_registers[qubit]) quantum_circuit.barrier(quantum_registers[qubit]) #pylint: disable=no-member offset_count = 0 for gate in circuit_gate_list: if gate == 'id': for qubit in target_qubits: quantum_circuit.iden(quantum_registers[qubit]) # pylint: disable=no-member quantum_circuit.barrier(quantum_registers[qubit]) # pylint: disable=no-member continue instance_operation = np.array([ dynamic_decoupling_sequence.rabi_rotations[offset_count], dynamic_decoupling_sequence.azimuthal_angles[offset_count], dynamic_decoupling_sequence.detuning_rotations[offset_count] ]) rotations = _get_rotations(instance_operation) nonzero_pulse_counts = 0 for rotation in rotations: if not np.isclose(rotation, 0.0): nonzero_pulse_counts += 1 if nonzero_pulse_counts > 1: raise ArgumentsValueError( 'Open Controls support a sequence with one ' 'valid pulse at any offset. Found sequence ' 'with multiple rotation operations at an offset.', { 'dynamic_decoupling_sequence': str(dynamic_decoupling_sequence), 'instance_operation': instance_operation }) for qubit in target_qubits: if nonzero_pulse_counts == 0: quantum_circuit.u3( 0., 0., 0., #pylint: disable=no-member quantum_registers[qubit]) else: if not np.isclose(rotations[0], 0.0): quantum_circuit.u3( rotations[0], -pi / 2, pi / 2, #pylint: disable=no-member quantum_registers[qubit]) elif not np.isclose(rotations[1], 0.0): quantum_circuit.u3( rotations[1], 0., 0., #pylint: disable=no-member quantum_registers[qubit]) elif not np.isclose(rotations[2], 0.): quantum_circuit.u1( rotations[2], #pylint: disable=no-member quantum_registers[qubit]) quantum_circuit.barrier(quantum_registers[qubit]) #pylint: disable=no-member offset_count += 1 for qubit in target_qubits: quantum_circuit.u3( pre_post_gate_parameters[0], #pylint: disable=no-member pre_post_gate_parameters[1], pre_post_gate_parameters[2], quantum_registers[qubit]) quantum_circuit.barrier(quantum_registers) # pylint: disable=no-member if add_measurement: for q_index, qubit in enumerate(target_qubits): quantum_circuit.measure( quantum_registers[qubit], #pylint: disable=no-member classical_registers[q_index]) return quantum_circuit
def new_quadratic_sequence(duration=None, number_inner_offsets=None, number_outer_offsets=None, pre_post_rotation=False, **kwargs): """Quadratic Decoupling Sequence Parameters ---------- duration : float, optional defaults to None The total duration of the sequence number_outer_offsets : int, optional Number of outer X-pi Pulses. Defaults to None. Not used if number_of_offsets is supplied. number_inner_offsets : int, optional Number of inner Z-pi Pulses. Defaults to None. Not used if number_of_offsets is supplied pre_post_rotation : bool, optional If True, a :math:`\\pi.2` rotation is added at the start and end of the sequence. kwargs : dict Additional keywords required by qctrlopencontrols.sequences.DynamicDecouplingSequence Returns ------- qctrlopencontrols.sequences.DynamicDecouplingSequence Quadratic sequence Raises ------ ArgumentsValueError Raised when an argument is invalid. """ duration = _check_duration(duration) if number_inner_offsets is None: number_inner_offsets = 1 number_inner_offsets = int(number_inner_offsets) if number_inner_offsets <= 0.: raise ArgumentsValueError( 'Number of offsets of inner pulses must be above zero:', {'number_inner_offsets': number_inner_offsets}, extras={ 'duration': duration, 'number_outer_offsets': number_outer_offsets }) if number_outer_offsets is None: number_outer_offsets = 1 number_outer_offsets = int(number_outer_offsets) if number_outer_offsets <= 0.: raise ArgumentsValueError( 'Number of offsets of outer pulses must be above zero:', {'number_inner_offsets': number_outer_offsets}, extras={ 'duration': duration, 'number_inner_offsets': number_inner_offsets }) outer_offsets = _uhrig_single_axis_offsets(duration, number_outer_offsets) outer_offsets = np.insert(outer_offsets, [0, outer_offsets.shape[0]], [0, duration]) starts = outer_offsets[0:-1] ends = outer_offsets[1:] inner_durations = ends - starts offsets = np.zeros((inner_durations.shape[0], number_inner_offsets + 1)) for inner_duration_idx in range(inner_durations.shape[0]): inn_off = _uhrig_single_axis_offsets( inner_durations[inner_duration_idx], number_inner_offsets) inn_off = inn_off + starts[inner_duration_idx] offsets[inner_duration_idx, 0:number_inner_offsets] = inn_off offsets[0:number_outer_offsets, -1] = outer_offsets[1:-1] rabi_rotations = np.zeros(offsets.shape) detuning_rotations = np.zeros(offsets.shape) rabi_rotations[0:number_outer_offsets, -1] = np.pi detuning_rotations[0:(number_outer_offsets + 1), 0:number_inner_offsets] = np.pi offsets = np.reshape(offsets, (-1, )) rabi_rotations = np.reshape(rabi_rotations, (-1, )) detuning_rotations = np.reshape(detuning_rotations, (-1, )) # remove the last entry corresponding to the duration offsets = offsets[0:-1] rabi_rotations = rabi_rotations[0:-1] detuning_rotations = detuning_rotations[0:-1] if pre_post_rotation: offsets = np.insert(offsets, [0, offsets.shape[0]], [0, duration]) rabi_rotations = np.insert(rabi_rotations, [0, rabi_rotations.shape[0]], [np.pi / 2, np.pi / 2]) detuning_rotations = np.insert(detuning_rotations, [0, detuning_rotations.shape[0]], [0, 0]) # finally create the azimuthal angles as all zeros azimuthal_angles = np.zeros(offsets.shape) return DynamicDecouplingSequence(duration=duration, offsets=offsets, rabi_rotations=rabi_rotations, azimuthal_angles=azimuthal_angles, detuning_rotations=detuning_rotations, **kwargs)
def __init__(self, segments=None, name=None): self.name = name if self.name is not None: self.name = str(self.name) if segments is None: segments = [[np.pi, 0, 0, 1], ] self.segments = np.array(segments, dtype=np.float) self.number_of_segments = len(self.segments) if self.segments.shape != (self.number_of_segments, 4): raise ArgumentsValueError('Segments must be of shape (number_of_segments,4).', {'segments': self.segments}, extras={'number_of_segments': self.number_of_segments}) if self.number_of_segments > UPPER_BOUND_SEGMENTS: raise ArgumentsValueError( 'The number of segments must be smaller than the upper bound:' + str(UPPER_BOUND_SEGMENTS), {'segments': self.segments}, extras={'number_of_segments': self.number_of_segments}) self.amplitudes = np.sqrt(np.sum(self.segments[:, 0:3] ** 2, axis=1)) self.segment_durations = self.segments[:, 3] if np.any(self.segment_durations <= 0): raise ArgumentsValueError('Duration of pulse segments must all be greater' + ' than zero.', {'segments': self.segments}, extras={'segment_durations': self.segment_durations}) super(DrivenControls, self).__init__( base_attributes=['segments', 'name']) self.angles = self.amplitudes * self.segment_durations self.directions = np.array([self.segments[i, 0:3] / self.amplitudes[i] if self.amplitudes[i] != 0. else np.zeros([3, ]) for i in range(self.number_of_segments)]) self.segment_times = np.insert( np.cumsum(self.segment_durations), 0, 0.) self.duration = self.segment_times[-1] self.rabi_rates = np.sqrt(np.sum(self.segments[:, 0:2]**2, axis=1)) self.maximum_rabi_rate = np.amax(self.rabi_rates) self.maximum_detuning = np.amax(np.abs(self.segments[:, 2])) self.maximum_amplitude = np.amax(self.amplitudes) self.minimum_duration = np.amin(self.segment_durations) self.maximum_duration = np.amax(self.segment_durations) if self.maximum_rabi_rate > UPPER_BOUND_RABI_RATE: raise ArgumentsValueError( 'Maximum rabi rate of segments must be smaller than the upper bound: ' + str(UPPER_BOUND_RABI_RATE), {'segments': self.segments}, extras={'maximum_rabi_rate': self.maximum_rabi_rate}) if self.maximum_detuning > UPPER_BOUND_DETUNING_RATE: raise ArgumentsValueError( 'Maximum detuning of segments must be smaller than the upper bound: ' + str(UPPER_BOUND_DETUNING_RATE), {'segments': self.segments}, extras={'maximum_detuning': self.maximum_detuning}) if self.maximum_duration > UPPER_BOUND_DURATION: raise ArgumentsValueError( 'Maximum duration of segments must be smaller than the upper bound: ' + str(UPPER_BOUND_DURATION), {'segments': self.segments}, extras={'maximum_duration': self.maximum_duration}) if self.minimum_duration < LOWER_BOUND_DURATION: raise ArgumentsValueError( 'Minimum duration of segments must be larger than the lower bound: ' + str(LOWER_BOUND_DURATION), {'segments': self.segments}, extras={'minimum_duration'})
def new_x_concatenated_sequence(duration=1.0, concatenation_order=None, pre_post_rotation=False, **kwargs): """X-Concatenated Dynamic Decoupling Sequence Concatenation of base sequence C(\tau/2)XC(\tau/2)X Parameters ---------- duration : float, optional defaults to None The total duration of the sequence concatenation_order : int, optional defaults to None The number of concatenation of base sequence pre_post_rotation : bool, optional If True, a :math:`\\pi.2` rotation is added at the start and end of the sequence. kwargs : dict Additional keywords required by qctrlopencontrols.sequences.DynamicDecouplingSequence Returns ------- qctrlopencontrols.sequences.DynamicDecouplingSequence X concatenated sequence Raises ------ ArgumentsValueError Raised when an argument is invalid. """ duration = _check_duration(duration) if concatenation_order is None: concatenation_order = 1 concatenation_order = int(concatenation_order) if concatenation_order <= 0.: raise ArgumentsValueError('Concatenation oder must be above zero:', {'concatenation_order': concatenation_order}, extras={'duration': duration}) unit_spacing = duration / (2**concatenation_order) cumulations = _concatenation_x(concatenation_order) pos_cum = cumulations * unit_spacing pos_cum_sum = np.cumsum(pos_cum) values, counts = np.unique(pos_cum_sum, return_counts=True) offsets = [values[i] for i in range(counts.shape[0]) if counts[i] % 2 == 0] if concatenation_order % 2 == 1: offsets = offsets[0:-1] offsets = np.array(offsets) rabi_rotations = np.zeros(offsets.shape) rabi_rotations[0:] = np.pi if pre_post_rotation: offsets = np.insert(offsets, [0, offsets.shape[0]], [0, duration]) rabi_rotations = np.insert(rabi_rotations, [0, rabi_rotations.shape[0]], [np.pi / 2, np.pi / 2]) azimuthal_angles = np.zeros(offsets.shape) detuning_rotations = np.zeros(offsets.shape) return DynamicDecouplingSequence(duration=duration, offsets=offsets, rabi_rotations=rabi_rotations, azimuthal_angles=azimuthal_angles, detuning_rotations=detuning_rotations, **kwargs)
def convert_dds_to_driven_control( dynamic_decoupling_sequence=None, maximum_rabi_rate=2*np.pi, maximum_detuning_rate=2*np.pi, **kwargs): """Creates a Driven Control based on the supplied DDS and other relevant information Parameters ---------- dynamic_decoupling_sequence : DynamicDecouplingSequence The base DDS; Defaults to None maximum_rabi_rate : float, optional Maximum Rabi Rate; Defaults to 1.0 maximum_detuning_rate : float, optional Maximum Detuning Rate; Defaults to None kwargs : dict, optional options to make the corresponding filter type. I.e. the options for primitive is described in doc for the PrimitivePulse class. Returns ------- DrivenControls The Driven Control that contains the segments corresponding to the Dynamic Decoupling Sequence operation Raises ------ ArgumentsValueError Raised when an argument is invalid or a valid driven control cannot be created from the sequence parameters, maximum rabi rate and maximum detuning rate provided Notes ----- Driven pulse is defined as a sequence of control segments. Each segment performs an operation (rotation around one or more axes). While the dynamic decoupling sequence operation contains ideal instant operations, maximum rabi (detuning) rate defines a minimum time required to perform a given rotation operation. Therefore, each operation in sequence is converted to a flat-topped control segment with a finite duration. Each offset is taken as the mid-point of the control segment and the width of the segment is determined by (rotation/max_rabi(detuning)_rate). If the sequence contains operations at either of the extreme ends :math:`\\tau_0=0` and :math:`\\tau_{n+1}=\\tau`(duration of the sequence), there will be segments outside the boundary (segments starting before :math:`t<0` or finishing after the sequence duration :math:`t>\\tau`. In these cases, the segments on either of the extreme ends are shifted appropriately so that their start/end time falls entirely within the duration of the sequence. Moreover, a check is made to make sure the resulting control segments are non-overlapping. If appropriate control segments cannot be created, the conversion process raises an ArgumentsValueError. """ if dynamic_decoupling_sequence is None: raise ArgumentsValueError('Dynamic decoupling sequence must be of ' 'DynamicDecoupling type.', {'type(dynamic_decoupling_sequence': type(dynamic_decoupling_sequence)}) _check_maximum_rotation_rate(maximum_rabi_rate, maximum_detuning_rate) sequence_duration = dynamic_decoupling_sequence.duration offsets = dynamic_decoupling_sequence.offsets rabi_rotations = dynamic_decoupling_sequence.rabi_rotations azimuthal_angles = dynamic_decoupling_sequence.azimuthal_angles detuning_rotations = dynamic_decoupling_sequence.detuning_rotations # check for valid operation if not _check_valid_operation(rabi_rotations=rabi_rotations, detuning_rotations=detuning_rotations): raise ArgumentsValueError( 'Sequence operation includes rabi rotation and ' 'detuning rotation at the same instance.', {'dynamic_decoupling_sequence': str(dynamic_decoupling_sequence)}, extras={'maximum_rabi_rate': maximum_rabi_rate, 'maximum_detuning_rate': maximum_detuning_rate}) # check if detuning rate is supplied if there is a detuning_rotation > 0 if np.any(detuning_rotations > 0.) and maximum_detuning_rate is None: raise ArgumentsValueError( 'Sequence operation includes detuning rotations. Please supply a valid ' 'maximum_detuning_rate.', {'detuning_rotations': dynamic_decoupling_sequence.detuning_rotations, 'maximum_detuning_rate': maximum_detuning_rate}, extras={'maximum_rabi_rate': maximum_rabi_rate}) if offsets.size == 0: offsets = np.array([0, sequence_duration]) rabi_rotations = np.array([0, 0]) azimuthal_angles = np.array([0, 0]) detuning_rotations = np.array([0, 0]) if offsets[0] != 0: offsets = np.append([0], offsets) rabi_rotations = np.append([0], rabi_rotations) azimuthal_angles = np.append([0], azimuthal_angles) detuning_rotations = np.append([0], detuning_rotations) if offsets[-1] != sequence_duration: offsets = np.append(offsets, [sequence_duration]) rabi_rotations = np.append(rabi_rotations, [0]) azimuthal_angles = np.append(azimuthal_angles, [0]) detuning_rotations = np.append(detuning_rotations, [0]) offsets = offsets[np.newaxis, :] rabi_rotations = rabi_rotations[np.newaxis, :] azimuthal_angles = azimuthal_angles[np.newaxis, :] detuning_rotations = detuning_rotations[np.newaxis, :] operations = np.concatenate((offsets, rabi_rotations, azimuthal_angles, detuning_rotations), axis=0) pulse_mid_points = operations[0, :] pulse_start_ends = np.zeros((operations.shape[1], 2)) for op_idx in range(operations.shape[1]): if np.isclose(np.sum(operations[:, op_idx]), 0.0): continue if operations[3, op_idx] == 0: #no z_rotations if not np.isclose(operations[1, op_idx], 0.): half_pulse_duration = 0.5 * operations[1, op_idx] / maximum_rabi_rate else: half_pulse_duration = 0.5 * operations[2, op_idx] / maximum_rabi_rate pulse_start_ends[op_idx, 0] = pulse_mid_points[op_idx] - half_pulse_duration pulse_start_ends[op_idx, 1] = pulse_mid_points[op_idx] + half_pulse_duration else: pulse_start_ends[op_idx, 0] = pulse_mid_points[op_idx] - \ 0.5 * operations[3, op_idx] / maximum_detuning_rate pulse_start_ends[op_idx, 1] = pulse_mid_points[op_idx] + \ 0.5 * operations[3, op_idx] / maximum_detuning_rate # check if any of the pulses have gone outside the time limit [0, sequence_duration] # if yes, adjust the segment timing if pulse_start_ends[0, 0] < 0.: if np.sum(np.abs(pulse_start_ends[0, :])) == 0: pulse_start_ends[0, 0] = 0 else: translation = 0. - (pulse_start_ends[0, 0]) pulse_start_ends[0, :] = pulse_start_ends[0, :] + translation if pulse_start_ends[-1, 1] > sequence_duration: if np.sum(np.abs(pulse_start_ends[0, :])) == 2 * sequence_duration: pulse_start_ends[-1, 1] = sequence_duration else: translation = pulse_start_ends[-1, 1] - sequence_duration pulse_start_ends[-1, :] = pulse_start_ends[-1, :] - translation # four conditions to check # 1. Control segment start times should be monotonically increasing # 2. Control segment end times should be monotonically increasing # 3. Control segment start time must be less than its end time # 4. Adjacent segments should not be overlapping if (np.any(pulse_start_ends[0:-1, 0] - pulse_start_ends[1:, 0] > 0.) or np.any(pulse_start_ends[0:-1, 1] - pulse_start_ends[1:, 1] > 0.) or np.any(pulse_start_ends[:, 0] - pulse_start_ends[:, 1] > 0.) or np.any(pulse_start_ends[1:, 0]-pulse_start_ends[0:-1, 1] < 0.)): raise ArgumentsValueError('Pulse timing could not be properly deduced from ' 'the sequence operation offsets. Try increasing the ' 'maximum rabi rate or maximum detuning rate.', {'dynamic_decoupling_sequence': dynamic_decoupling_sequence, 'maximum_rabi_rate': maximum_rabi_rate, 'maximum_detuning_rate': maximum_detuning_rate}, extras={'deduced_pulse_start_timing': pulse_start_ends[:, 0], 'deduced_pulse_end_timing': pulse_start_ends[:, 1]}) if np.allclose(pulse_start_ends, 0.0): # the original sequence should be a free evolution return DrivenControl(rabi_rates=[0.], azimuthal_angles=[0.], detunings=[0.], durations=[sequence_duration], **kwargs) control_rabi_rates = np.zeros((operations.shape[1]*2,)) control_azimuthal_angles = np.zeros((operations.shape[1] * 2,)) control_detunings = np.zeros((operations.shape[1] * 2,)) control_durations = np.zeros((operations.shape[1] * 2,)) pulse_segment_idx = 0 for op_idx in range(0, operations.shape[1]): if operations[3, op_idx] == 0.0: control_rabi_rates[pulse_segment_idx] = maximum_rabi_rate control_azimuthal_angles[pulse_segment_idx] = operations[2, op_idx] control_durations[pulse_segment_idx] = (pulse_start_ends[op_idx, 1] - pulse_start_ends[op_idx, 0]) else: control_detunings[pulse_segment_idx] = operations[3, op_idx] control_durations[pulse_segment_idx] = (pulse_start_ends[op_idx, 1] - pulse_start_ends[op_idx, 0]) if op_idx != (operations.shape[1]-1): control_rabi_rates[pulse_segment_idx+1] = 0. control_azimuthal_angles[pulse_segment_idx+1] = 0. control_detunings[pulse_segment_idx+1] = 0. control_durations[pulse_segment_idx+1] = (pulse_start_ends[op_idx+1, 0] - pulse_start_ends[op_idx, 1]) pulse_segment_idx += 2 # almost there; let us check if there is any segments with durations = 0 control_rabi_rates = control_rabi_rates[control_durations > 0.] control_azimuthal_angles = control_azimuthal_angles[control_durations > 0.] control_detunings = control_detunings[control_durations > 0.] control_durations = control_durations[control_durations > 0.] return DrivenControl(rabi_rates=control_rabi_rates, azimuthal_angles=control_azimuthal_angles, detunings=control_detunings, durations=control_durations, **kwargs)
def new_xy_concatenated_sequence(duration=1.0, concatenation_order=None, pre_post_rotation=False, **kwargs): """XY-Concatenated Dynamic Decoupling Sequence Concatenation of base sequence C(\tau/4)XC(\tau/4)YC(\tau/4)XC(\tau/4)Y Parameters ---------- duration : float, optional defaults to None The total duration of the sequence concatenation_order : int, optional defaults to None The number of concatenation of base sequence pre_post_rotation : bool, optional If True, a :math:`\\pi.2` rotation is added at the start and end of the sequence. kwargs : dict Additional keywords required by qctrlopencontrols.sequences.DynamicDecouplingSequence Returns ------- qctrlopencontrols.sequences.DynamicDecouplingSequence XY concatenated sequence Raises ------ ArgumentsValueError Raised when an argument is invalid. """ duration = _check_duration(duration) if concatenation_order is None: concatenation_order = 1 concatenation_order = int(concatenation_order) if concatenation_order <= 0.: raise ArgumentsValueError('Concatenation oder must be above zero:', {'concatenation_order': concatenation_order}, extras={'duration': duration}) unit_spacing = duration / (2**(concatenation_order * 2)) cumulations = _concatenation_xy(concatenation_order) rabi_operations = cumulations[cumulations != -2] rabi_operations = rabi_operations[rabi_operations != -3] rabi_positions = np.zeros(rabi_operations.shape) rabi_positions[rabi_operations != -1] = 1 rabi_positions = rabi_positions * unit_spacing rabi_positions = np.cumsum(rabi_positions) values, counts = np.unique(rabi_positions, return_counts=True) rabi_offsets = [ values[i] for i in range(counts.shape[0]) if counts[i] % 2 == 0 ] azimuthal_operations = cumulations[cumulations != -1] azimuthal_operations = azimuthal_operations[azimuthal_operations != -3] azimuthal_positions = np.zeros(azimuthal_operations.shape) azimuthal_positions[azimuthal_operations != -2] = 1 azimuthal_positions = azimuthal_positions * unit_spacing azimuthal_positions = np.cumsum(azimuthal_positions) values, counts = np.unique(azimuthal_positions, return_counts=True) azimuthal_offsets = [ values[i] for i in range(counts.shape[0]) if counts[i] % 2 == 0 ] detuning_operations = cumulations[cumulations != -2] detuning_operations = detuning_operations[detuning_operations != -1] detuning_positions = np.zeros(detuning_operations.shape) detuning_positions[detuning_operations != -3] = 1 detuning_positions = detuning_positions * unit_spacing detuning_positions = np.cumsum(detuning_positions) values, counts = np.unique(detuning_positions, return_counts=True) detuning_offsets = [ values[i] for i in range(counts.shape[0]) if counts[i] % 2 == 0 ] # right now we have got all the offset positions separately; now have # put then all together offsets = np.zeros( (len(rabi_offsets) + len(azimuthal_offsets) + len(detuning_offsets), )) rabi_rotations = np.zeros(offsets.shape) azimuthal_angles = np.zeros(offsets.shape) detuning_rotations = np.zeros(offsets.shape) rabi_idx = 0 azimuthal_idx = 0 carr_idx = 0 while (rabi_idx < len(rabi_offsets) and azimuthal_idx < len(azimuthal_offsets)): if rabi_offsets[rabi_idx] < azimuthal_offsets[azimuthal_idx]: rabi_rotations[carr_idx] = np.pi offsets[carr_idx] = rabi_offsets[rabi_idx] rabi_idx += 1 else: azimuthal_angles[carr_idx] = np.pi / 2 rabi_rotations[carr_idx] = np.pi offsets[carr_idx] = azimuthal_offsets[azimuthal_idx] azimuthal_idx += 1 carr_idx += 1 if rabi_idx < len(rabi_offsets): while rabi_idx < len(rabi_offsets): rabi_rotations[carr_idx] = np.pi offsets[carr_idx] = rabi_offsets[rabi_idx] carr_idx += 1 rabi_idx += 1 if azimuthal_idx < len(azimuthal_offsets): while azimuthal_idx < len(azimuthal_offsets): azimuthal_angles[carr_idx] = np.pi / 2 rabi_rotations[carr_idx] = np.pi offsets[carr_idx] = azimuthal_offsets[azimuthal_idx] carr_idx += 1 azimuthal_idx += 1 # if there is any z-offset, add those too !!! if detuning_offsets: z_idx = 0 for carr_idx, offset in enumerate(offsets): if offset > detuning_offsets[z_idx]: offsets[carr_idx + 1:] = offsets[carr_idx:-1] rabi_rotations[carr_idx + 1:] = rabi_rotations[carr_idx:-1] azimuthal_angles[carr_idx + 1:] = azimuthal_angles[carr_idx:-1] detuning_rotations[carr_idx] = np.pi rabi_rotations[carr_idx] = 0 azimuthal_angles[carr_idx] = 0 offsets[carr_idx] = detuning_offsets[z_idx] z_idx += 1 if z_idx >= len(detuning_offsets): break if pre_post_rotation: offsets = np.insert(offsets, [0, offsets.shape[0]], [0, duration]) rabi_rotations = np.insert(rabi_rotations, [0, rabi_rotations.shape[0]], [np.pi / 2, np.pi / 2]) azimuthal_angles = np.insert(azimuthal_angles, [0, azimuthal_angles.shape[0]], [0, 0]) detuning_rotations = np.insert(detuning_rotations, [0, detuning_rotations.shape[0]], [0, 0]) return DynamicDecouplingSequence(duration=duration, offsets=offsets, rabi_rotations=rabi_rotations, azimuthal_angles=azimuthal_angles, detuning_rotations=detuning_rotations, **kwargs)
def new_walsh_single_axis_sequence(duration=None, paley_order=None, pre_post_rotation=False, **kwargs): """Welsh Single Axis Sequence. Parameters --------- duration : float Total duration of the sequence. Defaults to None paley_order : int, optional Defaults to 1. The paley order of the walsh sequence. pre_post_rotation : bool, optional If True, a :math:`\\pi.2` rotation is added at the start and end of the sequence. kwargs : dict Additional keywords required by qctrlopencontrols.sequences.DynamicDecouplingSequence Returns ------- qctrlopencontrols.sequences.DynamicDecouplingSequence Walsh (single-axis) sequence Raises ------ ArgumentsValueError Raised when an argument is invalid. """ duration = _check_duration(duration) if paley_order is None: paley_order = 1 paley_order = int(paley_order) if paley_order < 1 or paley_order > 2000: raise ArgumentsValueError('Paley order must be between 1 and 2000.', {'paley_order': paley_order}) hamming_weight = int(np.floor(np.log2(paley_order))) + 1 samples = 2**hamming_weight relative_offset = np.arange(1. / (2 * samples), 1., 1. / samples) binary_string = np.binary_repr(paley_order) binary_order = [int(binary_string[i]) for i in range(hamming_weight)] walsh_array = np.ones([samples]) for i in range(hamming_weight): walsh_array *= np.sign( np.sin(2**(i + 1) * np.pi * relative_offset))**binary_order[hamming_weight - 1 - i] walsh_relative_offsets = [] for i in range(samples - 1): if walsh_array[i] != walsh_array[i + 1]: walsh_relative_offsets.append((i + 1) * (1. / samples)) walsh_relative_offsets = np.array(walsh_relative_offsets, dtype=np.float) offsets = duration * walsh_relative_offsets rabi_rotations = np.zeros(offsets.shape) rabi_rotations[0:] = np.pi if pre_post_rotation: offsets = np.insert(offsets, [0, offsets.shape[0]], [0, duration]) rabi_rotations = np.insert(rabi_rotations, [0, rabi_rotations.shape[0]], [np.pi / 2, np.pi / 2]) azimuthal_angles = np.zeros(offsets.shape) detuning_rotations = np.zeros(offsets.shape) return DynamicDecouplingSequence(duration=duration, offsets=offsets, rabi_rotations=rabi_rotations, azimuthal_angles=azimuthal_angles, detuning_rotations=detuning_rotations, **kwargs)
def __init__(self, duration=1., offsets=None, rabi_rotations=None, azimuthal_angles=None, detuning_rotations=None, name=None): super(DynamicDecouplingSequence, self).__init__([ 'duration', 'offsets', 'rabi_rotations', 'azimuthal_angles', 'detuning_rotations', 'name' ]) self.duration = duration if self.duration <= 0.: raise ArgumentsValueError('Sequence duration must be above zero:', {'duration': self.duration}) if offsets is None: offsets = [0.5] self.offsets = np.array(offsets, dtype=np.float) if self.offsets.shape[0] > UPPER_BOUND_OFFSETS: raise ArgumentsValueError( 'Number of offsets is above the allowed number of maximum offsets. ', { 'number_of_offsets': self.offsets.shape[0], 'allowed_maximum_offsets': UPPER_BOUND_OFFSETS }) if np.any(self.offsets < 0.) or np.any(self.offsets > self.duration): raise ArgumentsValueError( 'Offsets for dynamic decoupling sequence must be between 0 and sequence ' 'duration (inclusive). ', { 'offsets': offsets, 'duration': duration }) if rabi_rotations is None: rabi_rotations = np.pi * np.ones((len(self.offsets), )) if azimuthal_angles is None: azimuthal_angles = np.zeros((len(self.offsets), )) if detuning_rotations is None: detuning_rotations = np.zeros((len(self.offsets), )) self.rabi_rotations = np.array(rabi_rotations, dtype=np.float) self.azimuthal_angles = np.array(azimuthal_angles, dtype=np.float) self.detuning_rotations = np.array(detuning_rotations, dtype=np.float) self.number_of_offsets = len(self.offsets) if len(self.rabi_rotations) != self.number_of_offsets: raise ArgumentsValueError( 'rabi rotations must have the same length as offsets. ', { 'offsets': offsets, 'rabi_rotations': rabi_rotations }) if len(self.azimuthal_angles) != self.number_of_offsets: raise ArgumentsValueError( 'azimuthal angles must have the same length as offsets. ', { 'offsets': offsets, 'azimuthal_angles': azimuthal_angles }) if len(self.detuning_rotations) != self.number_of_offsets: raise ArgumentsValueError( 'detuning rotations must have the same length as offsets. ', { 'offsets': offsets, 'detuning_rotations': detuning_rotations, 'len(detuning_rotations)': len(self.detuning_rotations), 'number_of_offsets': self.number_of_offsets }) self.name = name if self.name is not None: self.name = str(self.name)