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})
Beispiel #3
0
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)
Beispiel #4
0
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)
Beispiel #5
0
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)
Beispiel #6
0
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)
Beispiel #9
0
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})
Beispiel #11
0
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
Beispiel #14
0
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
Beispiel #15
0
    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
Beispiel #19
0
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)
Beispiel #20
0
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)
Beispiel #30
0
    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)