From 5cf2e3527592676e48281c28323558de28e1bd7d Mon Sep 17 00:00:00 2001 From: virginia-m Date: Thu, 18 Apr 2019 18:40:41 +1000 Subject: [PATCH 01/51] minor corrections in doc strings --- qctrlopencontrols/driven_controls/constants.py | 2 +- qctrlopencontrols/driven_controls/driven_controls.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/qctrlopencontrols/driven_controls/constants.py b/qctrlopencontrols/driven_controls/constants.py index bac69616..3eb66137 100644 --- a/qctrlopencontrols/driven_controls/constants.py +++ b/qctrlopencontrols/driven_controls/constants.py @@ -14,7 +14,7 @@ """ ================ -pulses.constants +driven_controls.constants ================ """ diff --git a/qctrlopencontrols/driven_controls/driven_controls.py b/qctrlopencontrols/driven_controls/driven_controls.py index 983da0ab..9d0c53eb 100644 --- a/qctrlopencontrols/driven_controls/driven_controls.py +++ b/qctrlopencontrols/driven_controls/driven_controls.py @@ -14,7 +14,7 @@ """ ====================== -pulses.driven_controls +driven_controls.driven_controls ====================== """ import json @@ -32,7 +32,8 @@ class DrivenControls(QctrlObject): #pylint: disable=too-few-public-methods - """Creates a pulse. A pulse is a set of segments made up of amplitude vectors and durations. + """Creates a driven control. A driven is a set of segments made up of amplitude vectors + and corresponding durations. Parameters ---------- @@ -45,7 +46,7 @@ class DrivenControls(QctrlObject): #pylint: disable=too-few-public-methods The duration is the time of that segment. If None, defaults to a square pi pulse [[np.pi, 0, 0, 1], ]. name : string, optional - Defaults to None. An optional string to name the pulse. + Defaults to None. An optional string to name the driven control. Raises ------ @@ -81,7 +82,7 @@ def __init__(self, self.segment_durations = self.segments[:, 3] if np.any(self.segment_durations <= 0): - raise ArgumentsValueError('Duration of pulse segments must all be greater' + raise ArgumentsValueError('Duration of driven control segments must all be greater' + ' than zero.', {'segments': self.segments}, extras={'segment_durations': self.segment_durations}) From e1a15a15cb93b06ddae8adf643e1101284904156 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Thu, 18 Apr 2019 18:41:38 +1000 Subject: [PATCH 02/51] add get_plot_formatted_data method and helper methods --- .../driven_controls/driven_controls.py | 170 +++++++++++++++++- 1 file changed, 168 insertions(+), 2 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_controls.py b/qctrlopencontrols/driven_controls/driven_controls.py index 9d0c53eb..624bc571 100644 --- a/qctrlopencontrols/driven_controls/driven_controls.py +++ b/qctrlopencontrols/driven_controls/driven_controls.py @@ -31,9 +31,59 @@ UPPER_BOUND_DURATION, LOWER_BOUND_DURATION) +def get_plot_data_from_segments(segments): + """ + Generates arrays that can be used to produce a plot representing the shape of the driven control + constructed from the segments. + + Parameters + ---------- + segments : list + List of segments formatted as described in qctrlopencontrols.driven_controls.DrivenControl + + Returns + ------- + tuple + Tuple made up of arrays for plotting formatted as + (amplitude_x,amplitude_y,amplitude_z,time) where: + - amplitude_k is the amplitude values. + - times the time corresponding to each amplitude_k coordinate. + Note that plot will have repeated times and for amplitudes, this is 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. + + """ + segment_times = np.insert(np.cumsum(segments[:, 3]), 0, 0.) + coords = len(segment_times) + coord_amplitude_x = np.concatenate([[0.], segments[:, 0], [0.]]) + coord_amplitude_y = np.concatenate([[0.], segments[:, 1], [0.]]) + coord_amplitude_z = np.concatenate([[0.], segments[:, 2], [0.]]) + plot_time = [] + plot_amplitude_x = [] + plot_amplitude_y = [] + plot_amplitude_z = [] + for i in range(coords): + plot_time.append(segment_times[i]) + plot_time.append(segment_times[i]) + plot_amplitude_x.append(coord_amplitude_x[i]) + plot_amplitude_x.append(coord_amplitude_x[i + 1]) + plot_amplitude_y.append(coord_amplitude_y[i]) + plot_amplitude_y.append(coord_amplitude_y[i + 1]) + plot_amplitude_z.append(coord_amplitude_z[i]) + plot_amplitude_z.append(coord_amplitude_z[i + 1]) + + return (np.array(plot_amplitude_x), + np.array(plot_amplitude_y), + np.array(plot_amplitude_z), + np.array(plot_time)) + + + class DrivenControls(QctrlObject): #pylint: disable=too-few-public-methods - """Creates a driven control. A driven is a set of segments made up of amplitude vectors - and corresponding durations. + """ + Creates a driven control. A driven is a set of segments made up of amplitude vectors + and corresponding durations. Parameters ---------- @@ -292,6 +342,122 @@ def export_to_file(self, filename=None, file_type=file_type, coordinates=coordinates) + def get_plot_formatted_arrays(self, coordinates=CARTESIAN, dimensionless=True): + """ Gets arrays for plotting a driven control. + + Parameters + ---------- + dimensionless: 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 + ------- + tuple + Tuple made up of arrays for plotting formatted as + (amplitude_x,amplitude_y,amplitude_z,time) where: + - amplitude_k is the amplitude values in rad Hz for the k axis, or k Pauli matrix. + - times the time corresponding to each amplitude_k coordinate. + + 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. + """ + + plot_segments = self.get_transformed_segments(coordinates=CARTESIAN, + dimensionless=dimensionless) + + plot_data = get_plot_data_from_segments(plot_segments) + + if coordinates == CARTESIAN: + (x_amplitudes, y_amplitudes, detunings, times) = plot_data + plot_dictionary = { + 'x_amplitudes': x_amplitudes, + 'y_amplitudes': y_amplitudes, + 'z_amplitudes': detunings, + 'times': times + } + elif coordinates == CYLINDRICAL: + (x_plot, y_plot, detunings, times) = plot_data + 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 = { + 'amplitudes': amplitudes_plot, + 'azimuthal_angles': azimuthal_angles_plot, + 'detunings': detunings, + 'times': times + } + else: + raise ArgumentsValueError( + 'Unsupported coordinates provided: ', + arguments={'coordinates': coordinates}) + + return plot_dictionary + + def get_transformed_segments(self, coordinates=CARTESIAN, dimensionless=True): + """ + Function that transforms standard dimension-full segments of the + driven control into dimensionless segments + + Parameters + ---------- + coordinates : string + Indicated the type of segments that need to be transformed can be 'cartesian' or + 'cylindrical' or 'polar'. + dimensionless : boolean + If True, calculates the dimensionless segments + + Returns + ------- + numpy.ndarray + if dimensionless is True, returns the dimensionless segments of the driven control; + otherwise returns the segments + + Raises + ------ + ArgumentsValueError + Raised when an argument is invalid. + """ + dimensionless = bool(dimensionless) + transformed_segments = self.segments.copy() + + if dimensionless: + transformed_segments[:, 0:2] = transformed_segments[:, 0:2] / self.maximum_rabi_rate + + if coordinates == CARTESIAN: + pass + elif coordinates == CYLINDRICAL: + + temp_amplitudes = np.sqrt(np.abs( + transformed_segments[:, 0]**2 + transformed_segments[:, 1]**2)) + temp_azimuthal_angles = np.arctan2( + transformed_segments[:, 1], transformed_segments[:, 0]) + transformed_segments = np.stack( + (temp_amplitudes, + temp_azimuthal_angles, + transformed_segments[:, 2], + transformed_segments[:, 3]), + axis=1) + else: + raise ArgumentsValueError('Unsupported coordinates provided: ', + arguments={'coordinates': coordinates}) + + return transformed_segments + + + if __name__ == '__main__': pass From 66f259ea2a761efd6f1f6429113364e514ee78d0 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Thu, 18 Apr 2019 19:18:57 +1000 Subject: [PATCH 03/51] add tests for get_plot_formatted_arrays and helper methods --- tests/test_driven_controls.py | 68 +++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/test_driven_controls.py b/tests/test_driven_controls.py index 4c148c84..7cbcf2e4 100644 --- a/tests/test_driven_controls.py +++ b/tests/test_driven_controls.py @@ -23,6 +23,7 @@ from qctrlopencontrols.exceptions import ArgumentsValueError from qctrlopencontrols import DrivenControls +from qctrlopencontrols.globals import CARTESIAN, CYLINDRICAL def _remove_file(filename): @@ -110,3 +111,70 @@ def test_control_export(): _remove_file('driven_control_qctrl_cartesian.csv') _remove_file('driven_control_qctrl_cylindrical.json') _remove_file('driven_control_qctrl_cartesian.json') + +def test_plot_data(): + """ + Test the plot data produced for a driven control. + """ + + segments = [[1., 0., 0., 2.], + [0., 1.5, 1.7, 3.], + [1., 0., 2.1, 0.5]] + x_amplitude = [0., 1., 1., 0., 0., 1., 1., 0.] + y_amplitude = [0., 0., 0., 1.5, 1.5, 0., 0., 0.] + z_amplitude = [0., 0., 0., 1.7, 1.7, 2.1, 2.1, 0.] + times = [0., 0., 2., 2., 5., 5., 5.5, 5.5] + driven_control = DrivenControls(segments=segments) + plot_data = driven_control.get_plot_formatted_arrays(dimensionless=False) + + assert np.allclose(plot_data['times'], times) + assert np.allclose(plot_data['x_amplitudes'], x_amplitude) + assert np.allclose(plot_data['y_amplitudes'], y_amplitude) + assert np.allclose(plot_data['z_amplitudes'], z_amplitude) + +def test_dimensionless_segments(): + """ + Test the dimensionless amplitude and angle segments generated + """ + segments = [[1., 0., 0., np.pi / 2], + [0., 1., 0., np.pi / 2], + [1. / np.sqrt(2.), 0., 1. / np.sqrt(2.), np.pi / 2]] + + _on_resonance_amplitudes = np.array([1., 1., 1. / np.sqrt(2.)]) + _azimuthal_angles = np.array([0., np.pi / 2, 0.]) + _detunings = np.array([0, 0, 1. / np.sqrt(2.)]) + _durations = np.pi / 2. * np.array([1., 1., 1.]) + + amplitude_angle_segments = np.stack((_on_resonance_amplitudes, _azimuthal_angles, + _detunings, _durations), axis=1) + + driven_control = DrivenControls(segments=segments) + _max_rabi = driven_control.maximum_rabi_rate + + dimensionless_euclid = segments.copy() + dimensionless_euclid = np.array(dimensionless_euclid) + dimensionless_euclid[:, 0:2] = dimensionless_euclid[:, 0:2] / _max_rabi + + dimensionless_cylinder = amplitude_angle_segments.copy() + dimensionless_cylinder = np.array(dimensionless_cylinder) + dimensionless_cylinder[:, 0] = dimensionless_cylinder[:, 0] / _max_rabi + + transformed_euclidean = driven_control.get_transformed_segments(coordinates=CARTESIAN, + dimensionless=False) + + assert np.allclose(segments, transformed_euclidean) + + transformed_euclidean = driven_control.get_transformed_segments(coordinates=CARTESIAN, + dimensionless=True) + + assert np.allclose(dimensionless_euclid, transformed_euclidean) + + transformed_cylindrical = driven_control.get_transformed_segments(coordinates=CYLINDRICAL, + dimensionless=False) + + assert np.allclose(amplitude_angle_segments, transformed_cylindrical) + + transformed_cylindrical = driven_control.get_transformed_segments(coordinates=CYLINDRICAL, + dimensionless=True) + + assert np.allclose(amplitude_angle_segments, transformed_cylindrical) From 4535e392e6d20711f1b9b25e0f16df0523e116c5 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Thu, 18 Apr 2019 19:49:47 +1000 Subject: [PATCH 04/51] add predefined driven control types --- qctrlopencontrols/driven_controls/__init__.py | 11 ++++- .../driven_controls/constants.py | 41 +++++++++++++++++++ .../driven_controls/predefined.py | 35 ++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 qctrlopencontrols/driven_controls/predefined.py diff --git a/qctrlopencontrols/driven_controls/__init__.py b/qctrlopencontrols/driven_controls/__init__.py index ef6755d0..38e0f74f 100644 --- a/qctrlopencontrols/driven_controls/__init__.py +++ b/qctrlopencontrols/driven_controls/__init__.py @@ -14,7 +14,7 @@ """ ============= -Pulses module +driven_controls module ============= """ @@ -22,4 +22,11 @@ from .constants import ( UPPER_BOUND_RABI_RATE, UPPER_BOUND_DETUNING_RATE, - UPPER_BOUND_DURATION, LOWER_BOUND_DURATION, UPPER_BOUND_SEGMENTS) + UPPER_BOUND_DURATION, LOWER_BOUND_DURATION, UPPER_BOUND_SEGMENTS, + PRIMITIVE, WIMPERIS_1, SOLOVAY_KITAEV_1, + WALSH_AMPLITUDE_MODULATED_FILTER_1, + COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE, + COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_SOLOVAY_KITAEV, + COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_WIMPERIS, + SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT, + CORPSE_IN_SCROFULOUS_PULSE) diff --git a/qctrlopencontrols/driven_controls/constants.py b/qctrlopencontrols/driven_controls/constants.py index 3eb66137..f58fcdb3 100644 --- a/qctrlopencontrols/driven_controls/constants.py +++ b/qctrlopencontrols/driven_controls/constants.py @@ -38,3 +38,44 @@ UPPER_BOUND_SEGMENTS = 10000 """Maximum number of segments allowed in a control """ + +#Driven control types +PRIMITIVE = 'primitive' +"""Primitive control +""" + +WIMPERIS_1 = 'wimperis_1' +"""First-order Wimperis control, also known as BB1 +""" + +SOLOVAY_KITAEV_1 = 'solovay_kitaev_1' +"""First-order Solovay-Kitaev control +""" + +WALSH_AMPLITUDE_MODULATED_FILTER_1 = 'walsh_amplitude_modulated_filter_1' +"""First-order Walsh sequence control +""" + +COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE = \ + 'compensating_for_off_resonance_with_a_pulse_sequence' +"""Dynamically corrected control - commonly abbreviated as COPRSE +""" + +COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_WIMPERIS = \ + 'compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis' +"""Concatenated dynamically corrected control - Wimperis inside COPRSE +""" + +COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_SOLOVAY_KITAEV = \ + 'compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev' +"""Concatenated dynamically corrected control - Solovay-Kitaev inside COPRSE +""" + +SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT = \ + 'short_composite_rotation_for_undoing_length_over_and_under_shoot' +"""Dynamically corrected control - commonly abbreviated as SCROFULOUS +""" + +CORPSE_IN_SCROFULOUS_PULSE = 'corpse_in_scrofulous_pulse' +"""Concatenated dynamically corrected control - CORPSE inside SCROFULOUS +""" diff --git a/qctrlopencontrols/driven_controls/predefined.py b/qctrlopencontrols/driven_controls/predefined.py new file mode 100644 index 00000000..1cc0b64b --- /dev/null +++ b/qctrlopencontrols/driven_controls/predefined.py @@ -0,0 +1,35 @@ +# Copyright 2019 Q-CTRL Pty Ltd & Q-CTRL Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +=================== +driven_controls.predefined +=================== +""" + +import numpy as np + +from qctrlopencontrols.exceptions import ArgumentsValueError +from qctrlopencontrols import DrivenControls + +from .constants import ( + UPPER_BOUND_RABI_RATE, UPPER_BOUND_DETUNING_RATE, + UPPER_BOUND_DURATION, LOWER_BOUND_DURATION, UPPER_BOUND_SEGMENTS, + PRIMITIVE, WIMPERIS_1, SOLOVAY_KITAEV_1, + WALSH_AMPLITUDE_MODULATED_FILTER_1, + COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE, + COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_SOLOVAY_KITAEV, + COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_WIMPERIS, + SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT, + CORPSE_IN_SCROFULOUS_PULSE) From 5d8bd2ef8f2206004166cd1d78a18753fa30403f Mon Sep 17 00:00:00 2001 From: virginia-m Date: Thu, 18 Apr 2019 20:38:33 +1000 Subject: [PATCH 05/51] add predefined driven controls --- .../driven_controls/predefined.py | 701 +++++++++++++++++- 1 file changed, 699 insertions(+), 2 deletions(-) diff --git a/qctrlopencontrols/driven_controls/predefined.py b/qctrlopencontrols/driven_controls/predefined.py index 1cc0b64b..b29fd0fa 100644 --- a/qctrlopencontrols/driven_controls/predefined.py +++ b/qctrlopencontrols/driven_controls/predefined.py @@ -23,9 +23,9 @@ from qctrlopencontrols.exceptions import ArgumentsValueError from qctrlopencontrols import DrivenControls +from qctrlopencontrols.globals import SQUARE, GAUSSIAN + from .constants import ( - UPPER_BOUND_RABI_RATE, UPPER_BOUND_DETUNING_RATE, - UPPER_BOUND_DURATION, LOWER_BOUND_DURATION, UPPER_BOUND_SEGMENTS, PRIMITIVE, WIMPERIS_1, SOLOVAY_KITAEV_1, WALSH_AMPLITUDE_MODULATED_FILTER_1, COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE, @@ -33,3 +33,700 @@ COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_WIMPERIS, SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT, CORPSE_IN_SCROFULOUS_PULSE) + + +def new_predefined_driven_control( + driven_control_type=PRIMITIVE, + **kwargs): + """ + Create a new driven control + + Parameters + ---------- + driven_control_type : string, optional + Defaults to None. The name of the driven control type, + supported options are: + - 'primitive' + - 'wimperis_1' + - 'solovay_kitaev_1' + - 'compensating_for_off_resonance_with_a_pulse_sequence' + - 'compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis' + - 'compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev' + - 'walsh_amplitude_modulated_filter_1' + - 'short_composite_rotation_for_undoing_length_over_and_under_shoot' + - 'corpse_in_scrofulous' + kwargs : dict, optional + options to make the corresponding control type. + + Returns + ------- + qctrlopencontrols.DrivenControls + Returns a driven control corresponding to the driven_control_type. + + Raises + ------ + ArgumentsValueError + Raised when an argument is invalid. + """ + + # Forced to import here to avoid cyclic imports, need to review + # Raise error if the input driven_control_type is not known + if driven_control_type == PRIMITIVE: + driven_control = new_primitive_control(**kwargs) + elif driven_control_type == WIMPERIS_1: + driven_control = new_wimperis_1_control(**kwargs) + elif driven_control_type == SOLOVAY_KITAEV_1: + driven_control = new_solovay_kitaev_1_control(**kwargs) + elif driven_control_type == WALSH_AMPLITUDE_MODULATED_FILTER_1: + driven_control = new_walsh_amplitude_modulated_filter_1_control(**kwargs) + elif driven_control_type == COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE: + driven_control = new_compensating_for_off_resonance_with_a_pulse_sequence_control( + **kwargs) + elif driven_control_type == COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_WIMPERIS: + driven_control = \ + new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( + **kwargs) + elif driven_control_type == COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_SOLOVAY_KITAEV: + driven_control = \ + new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( + **kwargs) + elif driven_control_type == SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT: + driven_control = \ + new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control(**kwargs) + elif driven_control_type == CORPSE_IN_SCROFULOUS_PULSE: + driven_control = new_corpse_in_scrofulous_control(**kwargs) + else: + raise ArgumentsValueError( + 'Unknown predefined pulse type. See help(new_predefined_driven_control) to display all' + + ' allowed inputs.', + {'pulse_type', driven_control_type}) + return driven_control_type + +def _predefined_common_attributes(maximum_rabi_rate, + rabi_rotation, + shape, + 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. + shape : string + The shape of 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}) + + if shape == SQUARE: + rabi_rate = maximum_rabi_rate + else: # self.shape == GAUSSIAN + rabi_rate = None#gaussian_max_rabi_rate_scale_down(maximum_rabi_rate) # TODO + + 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 (rabi_rate, rabi_rotation, azimuthal_angle) + +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_primitive_control( + rabi_rotation=None, + azimuthal_angle=0., + maximum_rabi_rate=2. * np.pi, + shape=SQUARE, + **kwargs): + """ + Primitive driven control. + + Parameters + ---------- + rabi_rotation : float, optional + The total rabi rotation to be performed by the driven control. + maximum_rabi_rate : float, optional + Defaults to 2.*np.pi + The maximum rabi frequency for the driven control. + shape : str, optional + Shape of the driven control. + azimuthal_angle : float, optional + The azimuthal position of the driven control. + kwargs : dict + Other keywords required to make a qctrlopencontrols.DrivenControls. + + Returns + ------- + qctrlopencontrols.DrivenControls + The driven control. + """ + (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + + segments = [[ + rabi_rate * np.cos(azimuthal_angle), + rabi_rate * np.sin(azimuthal_angle), + 0., + rabi_rotation / rabi_rate], ] + + return DrivenControls(segments=segments, shape=shape, scheme=PRIMITIVE, **kwargs) + + +def new_wimperis_1_control( + rabi_rotation=None, + azimuthal_angle=0., + maximum_rabi_rate=2. * np.pi, + shape=SQUARE, + **kwargs): + """ + Wimperis or BB1 control. + + Parameters + ---------- + rabi_rotation : float, optional + The total rabi rotation to be performed by the control. + maximum_rabi_rate : float, optional + Defaults to 2.*np.pi + The maximum rabi frequency for the control. + shape : str, optional + Shape of the driven control. + azimuthal_angle : float, optional + The azimuthal position of the control. + kwargs : dict + Other keywords required to make a qctrlopencontrols.DrivenControls. + + Returns + ------- + qctrlopencontrols.DrivenControls + The driven control. + """ + (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + + phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) + angles = np.array([ + [rabi_rotation, azimuthal_angle], + [np.pi, phi_p + azimuthal_angle], + [2 * np.pi, 3. * phi_p + azimuthal_angle], + [np.pi, phi_p + azimuthal_angle]]) + + segments = _derive_segments(angles, amplitude=rabi_rate) + + return DrivenControls(segments=segments, shape=shape, scheme=WIMPERIS_1, **kwargs) + + +def new_solovay_kitaev_1_control( + rabi_rotation=None, + azimuthal_angle=0., + maximum_rabi_rate=2. * np.pi, + shape=SQUARE, + **kwargs): + """ + First-order Solovay-Kitaev control, also known as SK1 + + Parameters + ---------- + rabi_rotation : float, optional + The total rabi rotation to be performed by the control. + maximum_rabi_rate : float, optional + Defaults to 2.*np.pi + The maximum rabi frequency for the control. + shape : str, optional + Shape of the driven control. + azimuthal_angle : float, optional + The azimuthal position of the control. + kwargs : dict + Other keywords required to make a qctrlopencontrols.DrivenControls. + + Returns + ------- + qctrlopencontrols.DrivenControls + The driven control. + """ + (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + + phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) + + angles = np.array([ + [rabi_rotation, azimuthal_angle], + [2 * np.pi, -phi_p + azimuthal_angle], + [2 * np.pi, phi_p + azimuthal_angle]]) + + segments = _derive_segments(angles, amplitude=rabi_rate) + + return DrivenControls(segments=segments, shape=shape, scheme=SOLOVAY_KITAEV_1, **kwargs) + + +def new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( # pylint: disable=invalid-name + rabi_rotation=None, + azimuthal_angle=0., + maximum_rabi_rate=2. * np.pi, + shape=SQUARE, + **kwargs): + """ + SCROFULOUS control to compensate for pulse length errors + + Parameters + ---------- + rabi_rotation : float, optional + The total rabi rotation to be performed by the control. + maximum_rabi_rate : float, optional + Defaults to 2.*np.pi + The maximum rabi frequency for the control. + shape : str, optional + Shape of driven control. + azimuthal_angle : float, optional + The azimuthal position of the control. + kwargs : dict + Other keywords required to make a qctrlopencontrols.DrivenControls. + + Returns + ------- + qctrlopencontrols.DrivenControls + The driven control. + + Raises + ------ + ArgumentsValueError + Raised when an argument is invalid. + """ + (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + + # Create a lookup table for rabi rotation and phase angles, taken from the official paper. + # Note: values in the paper are in degrees. + def degrees_to_radians(angle_in_degrees): + return angle_in_degrees / 180 * np.pi + + if np.isclose(rabi_rotation, np.pi): + theta_1 = degrees_to_radians(180.) + phi_1 = np.arccos( + -np.pi * np.cos(theta_1) / 2 / theta_1 / np.sin(rabi_rotation / 2) + ) + phi_2 = phi_1 - np.arccos(- np.pi / 2 / theta_1) + elif np.isclose(rabi_rotation, 0.5 * np.pi): + theta_1 = degrees_to_radians(115.2) + phi_1 = np.arccos( + -np.pi * np.cos(theta_1) / 2 / theta_1 / np.sin(rabi_rotation / 2) + ) + phi_2 = phi_1 - np.arccos(- np.pi / 2 / theta_1) + elif np.isclose(rabi_rotation, 0.25 * np.pi): + theta_1 = degrees_to_radians(96.7) + phi_1 = np.arccos( + -np.pi * np.cos(theta_1) / 2 / theta_1 / np.sin(rabi_rotation / 2) + ) + phi_2 = phi_1 - np.arccos(- np.pi / 2 / theta_1) + else: + raise ArgumentsValueError( + 'rabi_rotation angle must be either pi, pi/2 or pi/4', + {'rabi_rotation': rabi_rotation}) + + theta_3 = theta_1 + phi_3 = phi_1 + theta_2 = np.pi + + angles = np.array([ + [theta_1, phi_1 + azimuthal_angle], + [theta_2, phi_2 + azimuthal_angle], + [theta_3, phi_3 + azimuthal_angle]]) + + segments = _derive_segments(angles, amplitude=rabi_rate) + + return DrivenControls( + segments=segments, + shape=shape, + scheme=SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT, + **kwargs) + + +def new_compensating_for_off_resonance_with_a_pulse_sequence_control( # pylint: disable=invalid-name + rabi_rotation=None, + azimuthal_angle=0., + maximum_rabi_rate=2. * np.pi, + shape=SQUARE, + **kwargs): + """ + Compensating for off resonance with a pulse sequence, often abbreviated as CORPSE. + + Parameters + ---------- + rabi_rotation : float, optional + The total rabi rotation to be performed by the control. + maximum_rabi_rate : float, optional + Defaults to 2.*np.pi + The maximum rabi frequency for the control. + shape : str, optional + Shape of the driven control. + azimuthal_angle : float, optional + The azimuthal position of the control. + kwargs : dict + Other keywords required to make a qctrlopencontrols.DrivenControls. + + Returns + ------- + qctrlopencontrols.DrivenControls + The driven control. + """ + (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + + k = np.arcsin(np.sin(rabi_rotation / 2.) / 2.) + angles = np.array([ + [2. * np.pi + rabi_rotation / 2. - k, azimuthal_angle], + [2. * np.pi - 2. * k, np.pi + azimuthal_angle], + [rabi_rotation / 2. - k, azimuthal_angle]]) + + segments = _derive_segments(angles, amplitude=rabi_rate) + + return DrivenControls( + segments=segments, + shape=shape, + scheme=COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE, + **kwargs) + + +def new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( # pylint: disable=invalid-name + rabi_rotation=None, + azimuthal_angle=0., + maximum_rabi_rate=2. * np.pi, + shape=SQUARE, + **kwargs): + """ + Compensating for off resonance with a pulse sequence with an embedded + Wimperis (or BB1) control, also known as CinBB. + + Parameters + ---------- + rabi_rotation : float, optional + The total rabi rotation to be performed by the control. + maximum_rabi_rate : float, optional + Defaults to 2.*np.pi + The maximum rabi frequency for the control. + shape : str, optional + Shape of the driven control. + azimuthal_angle : float, optional + The azimuthal position of the control. + kwargs : dict + Other keywords required to make a qctrlopencontrols.DrivenControls. + + Returns + ------- + qctrlopencontrols.DrivenControls + The driven control. + """ + (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + + phi_p = _phi_p_function(rabi_rotation) + k = np.arcsin(np.sin(rabi_rotation / 2.) / 2.) + angles = np.array([ + [2. * np.pi + rabi_rotation / 2. - k, azimuthal_angle], + [2. * np.pi - 2. * k, np.pi + azimuthal_angle], + [rabi_rotation / 2. - k, azimuthal_angle], + [np.pi, phi_p + azimuthal_angle], + [2. * np.pi, 3 * phi_p + azimuthal_angle], + [np.pi, phi_p + azimuthal_angle]]) + + segments = _derive_segments(angles, amplitude=rabi_rate) + + return DrivenControls( + segments=segments, + shape=shape, + scheme=COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_WIMPERIS, + **kwargs) + + +def new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( # pylint: disable=invalid-name + rabi_rotation=None, + azimuthal_angle=0., + maximum_rabi_rate=2. * np.pi, + shape=SQUARE, + **kwargs): + """ + Compensating for off resonance with a pulse sequence with an + embedded Solovay Kitaev (or SK1) control, also knowns as CinSK. + + Parameters + ---------- + rabi_rotation : float, optional + The total rabi rotation to be performed by the control. + maximum_rabi_rate : float, optional + Defaults to 2.*np.pi + The maximum rabi frequency for the control. + azimuthal_angle : float, optional + The azimuthal position of the control. + shape : str, optional + Shape of the driven control. + kwargs : dict + Other keywords required to make a qctrlopencontrols.DrivenControls. + + Returns + ------- + qctrlopencontrols.DrivenControls + The driven control. + """ + (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + + phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) + k = np.arcsin(np.sin(rabi_rotation / 2.) / 2.) + angles = np.array([ + [2. * np.pi + rabi_rotation / 2. - k, azimuthal_angle], + [2. * np.pi - 2. * k, np.pi + azimuthal_angle], + [rabi_rotation / 2. - k, azimuthal_angle], + [2. * np.pi, -phi_p + azimuthal_angle], + [2. * np.pi, phi_p + azimuthal_angle]]) + + segments = _derive_segments(angles, amplitude=rabi_rate) + + return DrivenControls( + segments=segments, + shape=shape, + scheme=COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_SOLOVAY_KITAEV, + **kwargs) + + +def new_corpse_in_scrofulous_control( # pylint: disable=invalid-name + rabi_rotation=None, + azimuthal_angle=0., + maximum_rabi_rate=2. * np.pi, + shape=SQUARE, + **kwargs): + """ + CORPSE (Compensating for Off Resonance with a Pulse SEquence) embedded within a + SCROFULOUS (Short Composite ROtation For Undoing Length Over and Under Shoot) control, + also knowns as CinS. + + Parameters + ---------- + rabi_rotation : float, optional + The total rabi rotation to be performed by the control. + maximum_rabi_rate : float, optional + Defaults to 2.*np.pi + The maximum rabi frequency for the control. + azimuthal_angle : float, optional + The azimuthal position of the control. + shape : str, optional + Shape of the driven control. + kwargs : dict + Other keywords required to make a qctrlopencontrols.DrivenControls. + + Returns + ------- + qctrlopencontrols.DrivenControls + The driven control. + + Raises + ------ + ArgumentsValueError + Raised when an argument is invalid. + """ + (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + + # Create a lookup table for rabi rotation and phase angles, taken from + # the Cummings paper. Note: values in the paper are in degrees. + def degrees_to_radians(angle_in_degrees): + return angle_in_degrees / 180 * np.pi + + if np.isclose(rabi_rotation, np.pi): + theta_1 = theta_3 = degrees_to_radians(180.) + phi_1 = phi_3 = np.arccos( + -np.pi * np.cos(theta_1) / 2 / theta_1 / np.sin(rabi_rotation / 2) + ) + phi_2 = phi_1 - np.arccos(- np.pi / 2 / theta_1) + elif np.isclose(rabi_rotation, 0.5 * np.pi): + theta_1 = theta_3 = degrees_to_radians(115.2) + phi_1 = phi_3 = np.arccos( + -np.pi * np.cos(theta_1) / 2 / theta_1 / np.sin(rabi_rotation / 2) + ) + phi_2 = phi_1 - np.arccos(- np.pi / 2 / theta_1) + elif np.isclose(rabi_rotation, 0.25 * np.pi): + theta_1 = theta_3 = degrees_to_radians(96.7) + phi_1 = phi_3 = np.arccos( + -np.pi * np.cos(theta_1) / 2 / theta_1 / np.sin(rabi_rotation / 2) + ) + phi_2 = phi_1 - np.arccos(- np.pi / 2 / theta_1) + else: + raise ArgumentsValueError( + 'rabi_rotation angle must be either pi, pi/2 or pi/4', + {'rabi_rotation': rabi_rotation}) + + theta_2 = np.pi + + total_angles = [] + # Loop over all SCROFULOUS Rabi rotations (theta) and azimuthal angles (phi) + # And make CORPSEs with those. + for theta, phi in zip([theta_1, theta_2, theta_3], [phi_1, phi_2, phi_3]): + k = np.arcsin(np.sin(theta / 2.) / 2.) + angles = np.array([ + [2. * np.pi + theta / 2. - k, phi + azimuthal_angle], + [2. * np.pi - 2. * k, np.pi + phi + azimuthal_angle], + [theta / 2. - k, phi + azimuthal_angle]]) + total_angles.append(angles) + + total_angles = np.vstack(total_angles) + + segments = _derive_segments(total_angles, amplitude=rabi_rate) + + return DrivenControls( + segments=segments, + shape=shape, + scheme=CORPSE_IN_SCROFULOUS_PULSE, + **kwargs) + + +def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-name + rabi_rotation=None, + azimuthal_angle=0., + maximum_rabi_rate=2. * np.pi, + shape=SQUARE, + **kwargs): + """ + First order Walsh control with amplitude modulation. + + Parameters + ---------- + rabi_rotation : float, optional + The total rabi rotation to be performed by the control. + maximum_rabi_rate : float, optional + Defaults to 2.*np.pi + The maximum rabi frequency for the control. + azimuthal_angle : float, optional + The azimuthal position of the control. + shape : str, optional + Shape of the driven control. + kwargs : dict + Other keywords required to make a qctrlopencontrols.DrivenControls. + + Returns + ------- + qctrlopencontrols.DrivenControls + The driven control. + + Raises + ------ + ArgumentsValueError + Raised when an argument is invalid. + """ + (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + + if shape == SQUARE: + if np.isclose(rabi_rotation, np.pi): + theta_plus = np.pi + theta_minus = np.pi / 2. + elif np.isclose(rabi_rotation, 0.5 * np.pi): + theta_plus = np.pi * (2.5 + 0.65667825) / 4. + theta_minus = np.pi * (2.5 - 0.65667825) / 4. + elif np.isclose(rabi_rotation, 0.25 * np.pi): + theta_plus = np.pi * (2.25 + 0.36256159) / 4. + theta_minus = np.pi * (2.25 - 0.36256159) / 4. + else: + raise ArgumentsValueError( + 'rabi_rotation angle must be either pi, pi/2 or pi/4', + {'rabi_rotation': rabi_rotation}) + + # Old on the fly general calc for square + # Need to solve transcendental equation to get modulation depth factor + # Have some precompiled solution, otherwise do it numerically + # init_factor = 1.93296 - 0.220866 * (rabi_rotation / np.pi) + # def factor_func(factor): + # return ( + # ((1 - factor) * np.sin(rabi_rotation / 2.) + # + factor * np.sin((rabi_rotation * (factor - 1)) / (2. * (factor - 2.))))**2 + # ) / ((factor - 1)**2) + # modulation_depth_factor = newton(factor_func, init_factor) + # assert 0. < modulation_depth_factor <= 2. + else: # shape == GAUSSIAN + if np.isclose(rabi_rotation, np.pi): + theta_plus = np.pi * (3 + 0.616016981956213) / 4 + theta_minus = np.pi * (3 - 0.616016981956213) / 4 + elif np.isclose(rabi_rotation, 0.5 * np.pi): + theta_plus = np.pi * (2.5 + 0.4684796993336457) / 4 + theta_minus = np.pi * (2.5 - 0.4684796993336457) / 4 + elif np.isclose(rabi_rotation, 0.25 * np.pi): + theta_plus = np.pi * (2.25 + 0.27723876925525176) / 4 + theta_minus = np.pi * (2.25 - 0.27723876925525176) / 4 + else: + raise ArgumentsValueError( + 'rabi_rotation angle must be either pi, pi/2 or pi/4', + {'rabi_rotation': rabi_rotation}) + + rabi_rate_plus = rabi_rate + time_segment = theta_plus / rabi_rate_plus + rabi_rate_minus = theta_minus / time_segment + + segments = np.array([ + [rabi_rate_plus * np.cos(azimuthal_angle), + rabi_rate_plus * np.sin(azimuthal_angle), + 0., time_segment], + [rabi_rate_minus * np.cos(azimuthal_angle), + rabi_rate_minus * np.sin(azimuthal_angle), + 0., time_segment], + [rabi_rate_minus * np.cos(azimuthal_angle), + rabi_rate_minus * np.sin(azimuthal_angle), + 0., time_segment], + [rabi_rate_plus * np.cos(azimuthal_angle), + rabi_rate_plus * np.sin(azimuthal_angle), + 0., time_segment]]) + + return DrivenControls( + segments=segments, + shape=shape, + scheme=WALSH_AMPLITUDE_MODULATED_FILTER_1, + **kwargs) + From 94b71d66a658ee2b5f5122193c49a87c07a040b8 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Thu, 18 Apr 2019 20:50:51 +1000 Subject: [PATCH 06/51] add shape and scheme parameter to driven controls --- .../driven_controls/driven_controls.py | 24 +++++++++++++++++-- qctrlopencontrols/globals/__init__.py | 11 ++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_controls.py b/qctrlopencontrols/driven_controls/driven_controls.py index 624bc571..d538f3c3 100644 --- a/qctrlopencontrols/driven_controls/driven_controls.py +++ b/qctrlopencontrols/driven_controls/driven_controls.py @@ -24,7 +24,7 @@ from qctrlopencontrols.base import QctrlObject from qctrlopencontrols.globals import ( - QCTRL_EXPANDED, CSV, JSON, CARTESIAN, CYLINDRICAL) + QCTRL_EXPANDED, CSV, JSON, CARTESIAN, CYLINDRICAL, SQUARE, GAUSSIAN) from .constants import ( UPPER_BOUND_SEGMENTS, UPPER_BOUND_RABI_RATE, UPPER_BOUND_DETUNING_RATE, @@ -106,12 +106,23 @@ class DrivenControls(QctrlObject): #pylint: disable=too-few-public-methods def __init__(self, segments=None, + shape=None, + scheme=None, name=None): + self.shape = str(shape) + if not (self.shape == SQUARE or self.shape == GAUSSIAN): + raise ArgumentsValueError('Shape must be "square" or "gaussian".', + {'shape': self.shape}) + self.name = name if self.name is not None: self.name = str(self.name) + self.scheme = scheme + if self.scheme is not None: + self.scheme = str(self.scheme) + if segments is None: segments = [[np.pi, 0, 0, 1], ] @@ -138,7 +149,7 @@ def __init__(self, extras={'segment_durations': self.segment_durations}) super(DrivenControls, self).__init__( - base_attributes=['segments', 'name']) + base_attributes=['segments', 'shape', 'name']) self.angles = self.amplitudes * self.segment_durations self.directions = np.array([self.segments[i, 0:3] / self.amplitudes[i] @@ -183,6 +194,15 @@ def __init__(self, {'segments': self.segments}, extras={'minimum_duration'}) + if self.shape == GAUSSIAN: + pass # TODO + #self.maximum_rabi_rate = gaussian_segment_rabi_rate_scale_up( + # np.amax(self.maximum_rabi_rate)) + #self.maximum_detuning = gaussian_segment_rabi_rate_scale_up( + # np.amax(self.maximum_detuning)) + #self.maximum_amplitude = gaussian_segment_rabi_rate_scale_up( + # np.amax(self.maximum_amplitude)) + def _qctrl_expanded_export_content(self, file_type, coordinates): """Private method to prepare the content to be saved in Q-CTRL expanded format diff --git a/qctrlopencontrols/globals/__init__.py b/qctrlopencontrols/globals/__init__.py index 3e07bab5..106ed10f 100644 --- a/qctrlopencontrols/globals/__init__.py +++ b/qctrlopencontrols/globals/__init__.py @@ -37,5 +37,14 @@ """ CYLINDRICAL = 'cylindrical' -"""Defined Cylindrical coordinate system +"""Defines Cylindrical coordinate system +""" + +#shapes for driven controls +SQUARE = 'square' +"""Defines square segments for a DrivenControls object +""" + +GAUSSIAN = 'gaussian' +"""Defines gaussian segments for a DrivenControls object """ From 14103c854d61ea4d793ddcb05d47c034e3106b02 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Thu, 18 Apr 2019 20:54:37 +1000 Subject: [PATCH 07/51] fix linting errors --- .../driven_controls/predefined.py | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/qctrlopencontrols/driven_controls/predefined.py b/qctrlopencontrols/driven_controls/predefined.py index b29fd0fa..a12c72d1 100644 --- a/qctrlopencontrols/driven_controls/predefined.py +++ b/qctrlopencontrols/driven_controls/predefined.py @@ -86,7 +86,8 @@ def new_predefined_driven_control( driven_control = \ new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( **kwargs) - elif driven_control_type == COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_SOLOVAY_KITAEV: + elif driven_control_type == \ + COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_SOLOVAY_KITAEV: driven_control = \ new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( **kwargs) @@ -99,8 +100,8 @@ def new_predefined_driven_control( raise ArgumentsValueError( 'Unknown predefined pulse type. See help(new_predefined_driven_control) to display all' + ' allowed inputs.', - {'pulse_type', driven_control_type}) - return driven_control_type + {'driven_control_type', driven_control_type}) + return driven_control def _predefined_common_attributes(maximum_rabi_rate, rabi_rotation, @@ -183,6 +184,32 @@ def _get_transformed_rabi_rotation_wimperis(rabi_rotation): {'rabi_rotation': rabi_rotation}) return np.arccos(-rabi_rotation / (4 * np.pi)) +def _derive_segments(angles, amplitude=2. * np.pi): + """ + Derive the driven control segments from a set of rabi_rotations defined in terms of the + spherical polar angles + + Parameters + ---------- + angles : numpy.array + angles is made of a list polar angle 2-lists formatted + as [polar_angle, azimuthal_angle]. + All angles should be greater or equal to 0, and the polar_angles + must be greater than zero. + amplitude : float, optional + Defaults to 1. The total amplitude of each segment in + rad Hz. + + Returns + ------- + list + Segments for the driven control. + + """ + segments = [[amplitude * np.cos(phi), amplitude * np.sin(phi), 0., theta / amplitude] + for (theta, phi) in angles] + return segments + def new_primitive_control( rabi_rotation=None, @@ -470,7 +497,7 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_contr (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) - phi_p = _phi_p_function(rabi_rotation) + phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) k = np.arcsin(np.sin(rabi_rotation / 2.) / 2.) angles = np.array([ [2. * np.pi + rabi_rotation / 2. - k, azimuthal_angle], @@ -729,4 +756,3 @@ def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-n shape=shape, scheme=WALSH_AMPLITUDE_MODULATED_FILTER_1, **kwargs) - From 05f40a1cedd10e189e06a5aad2798790f4f8d1d3 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Thu, 18 Apr 2019 20:55:10 +1000 Subject: [PATCH 08/51] add missing parameter documentation --- qctrlopencontrols/driven_controls/driven_controls.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qctrlopencontrols/driven_controls/driven_controls.py b/qctrlopencontrols/driven_controls/driven_controls.py index d538f3c3..5f3c3031 100644 --- a/qctrlopencontrols/driven_controls/driven_controls.py +++ b/qctrlopencontrols/driven_controls/driven_controls.py @@ -95,6 +95,11 @@ class DrivenControls(QctrlObject): #pylint: disable=too-few-public-methods corresponding pauli matrix, i.e. amplitude_x would correspond to sigma_x. The duration is the time of that segment. If None, defaults to a square pi pulse [[np.pi, 0, 0, 1], ]. + shape : str, optional + Defines the shape used in each segment can be 'square' or 'gaussian'. Defaults to + 'square'. + scheme : string, optional + Defaults to None. The scheme the driven control came from if derived that way. name : string, optional Defaults to None. An optional string to name the driven control. From 2052dce6f468cd656f91782b4849140778a7ed0e Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 07:52:53 +1000 Subject: [PATCH 09/51] add conversion functions for Gaussian segments --- .../driven_controls/constants.py | 4 + .../driven_controls/conversion.py | 154 ++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 qctrlopencontrols/driven_controls/conversion.py diff --git a/qctrlopencontrols/driven_controls/constants.py b/qctrlopencontrols/driven_controls/constants.py index f58fcdb3..0922ff9d 100644 --- a/qctrlopencontrols/driven_controls/constants.py +++ b/qctrlopencontrols/driven_controls/constants.py @@ -18,6 +18,10 @@ ================ """ +GAUSSIAN_STANDARD_DEVIATION_SCALE = 6. +""" +""" + #maximum and minimum values UPPER_BOUND_RABI_RATE = 1e10 """Maximum allowed rabi rate diff --git a/qctrlopencontrols/driven_controls/conversion.py b/qctrlopencontrols/driven_controls/conversion.py new file mode 100644 index 00000000..fb1bd743 --- /dev/null +++ b/qctrlopencontrols/driven_controls/conversion.py @@ -0,0 +1,154 @@ +# Copyright 2019 Q-CTRL Pty Ltd & Q-CTRL Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +================ +driven_controls.conversion +================ +""" + +import numpy as np +from scipy.special import erf + +from qctrlopencontrols.globals import CARTESIAN, CYLINDRICAL +from qctrlopencontrols.exceptions import ArgumentsValueError + +from .constants import GAUSSIAN_STANDARD_DEVIATION_SCALE + + +def convert_to_standard_segments(transformed_segments, maximum_rabi_rate=None, + coordinates=CARTESIAN, dimensionless=True): + """Converts the dimensionless segments of any type into dimension-full Cartesian segments + + Parameters + ---------- + transformed_segments : list + segments of pulse in [number_of_segments, 4] shape + maximum_rabi_rate : float + maximum rabi rate + coordinates : string + 'cartesian' or 'cylindrical' or 'polar' + defines the type of the transformed_segments supplied. + if 'cartesian' - the segments should be in + [amplitude_x, amplitude_y, amplitude_z,segment_duration] format + if 'cylindrical' - the segments should be in + [on_resonance_amplitude, azimuthal_angle, detuning, segment_duration] format + dimensionless : boolean + if True, identifies if the transformed_segments are dimensionless + + Returns + ------ + numpy.ndarray + Same size as the input segment [number_of_segments, 4] + + The returned array will be equal to the dimension-full cartesian segments of the + transformed segments + + Raises + ------ + ArgumentsValueError + Raised when an argument is invalid. + """ + + # making a copy of the segments otherwise during the call from + # get_segments(.) the internal segments will be modified + transformed_segments = np.array(transformed_segments, dtype=np.float) + segments_copy = np.array(transformed_segments, dtype=np.float) + number_of_segments = len(segments_copy) + dimensionless = bool(dimensionless) + + # Raise error if dimensionless is True and maximum_rabi_rate is not a float + if dimensionless: + if maximum_rabi_rate is None: + raise ArgumentsValueError('Maximum rate rate needs to be a valid float', + {'maximum_rabi_rate': maximum_rabi_rate, + 'dimensionless': dimensionless}, + extras={'segments': transformed_segments, + 'number_of_segments': number_of_segments, + 'coordinates': coordinates}) + maximum_rabi_rate = float(maximum_rabi_rate) + + # Raise error if segments are not in [number_of_segments, 4] format + segments_copy = np.array(segments_copy, dtype=np.float) + if segments_copy.shape != (number_of_segments, 4): + raise ArgumentsValueError('Segments must be of shape (number_of_segments,4).', + {'segments': transformed_segments}, + extras={'number_of_segments': number_of_segments}) + + if coordinates == CYLINDRICAL: + + # convert to cartesian + cos_theta = np.cos(transformed_segments[:, 1]) + sin_theta = np.sin(transformed_segments[:, 1]) + radius = transformed_segments[:, 0] + + segments_copy[:, 0] = radius * cos_theta + segments_copy[:, 1] = radius * sin_theta + + # if dimensionless, make the segments dimension-full + if dimensionless: + segments_copy[:, 0:2] = segments_copy[:, 0:2] * maximum_rabi_rate + + return segments_copy + + +def _gaussian_rabi_rate_scale(): + """ + Returns the scale factor for the rabi rate + + Returns + ------- + float + Gaussian scale factor. + """ + rho = GAUSSIAN_STANDARD_DEVIATION_SCALE + return ( + rho * (np.exp(rho**2 / 8.) - 1.) / + (np.exp(rho**2 / 8.) * np.sqrt(2 * np.pi) * erf(rho / (2 * np.sqrt(2))) - rho)) + + +def gaussian_max_rabi_rate_scale_down(maximum_rabi_rate): # pylint: disable=invalid-name + """ + Takes a maximum rabi rate and scales it down to the rabi rate that would be used in each + segments + + Parameters + ---------- + maximum_rabi_rate : float + The maximum rabi rate + + Returns + ------- + float + Rabi rate for segment + """ + return maximum_rabi_rate / _gaussian_rabi_rate_scale() + + +def gaussian_segment_rabi_rate_scale_up(segment_rabi_rate): # pylint: disable=invalid-name + """ + Takes a rabi rate of a segment and scales it up to the actual maximum rabi rate if + it was gaussian. + + Parameters + ---------- + segment_rabi_rate : float + The segment rabi rate + + Returns + ------- + float + Maximum rabi rate for segment + """ + return segment_rabi_rate * _gaussian_rabi_rate_scale() From f150ec4f8d3c8cec7eae3748919e5ced346837cd Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 08:05:47 +1000 Subject: [PATCH 10/51] enable Gaussian segments for driven controls --- .../driven_controls/driven_controls.py | 15 ++++++++------- qctrlopencontrols/driven_controls/predefined.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_controls.py b/qctrlopencontrols/driven_controls/driven_controls.py index 5f3c3031..83b5c397 100644 --- a/qctrlopencontrols/driven_controls/driven_controls.py +++ b/qctrlopencontrols/driven_controls/driven_controls.py @@ -30,6 +30,8 @@ UPPER_BOUND_SEGMENTS, UPPER_BOUND_RABI_RATE, UPPER_BOUND_DETUNING_RATE, UPPER_BOUND_DURATION, LOWER_BOUND_DURATION) +from .conversion import gaussian_segment_rabi_rate_scale_up + def get_plot_data_from_segments(segments): """ @@ -200,13 +202,12 @@ def __init__(self, extras={'minimum_duration'}) if self.shape == GAUSSIAN: - pass # TODO - #self.maximum_rabi_rate = gaussian_segment_rabi_rate_scale_up( - # np.amax(self.maximum_rabi_rate)) - #self.maximum_detuning = gaussian_segment_rabi_rate_scale_up( - # np.amax(self.maximum_detuning)) - #self.maximum_amplitude = gaussian_segment_rabi_rate_scale_up( - # np.amax(self.maximum_amplitude)) + self.maximum_rabi_rate = gaussian_segment_rabi_rate_scale_up( + np.amax(self.maximum_rabi_rate)) + self.maximum_detuning = gaussian_segment_rabi_rate_scale_up( + np.amax(self.maximum_detuning)) + self.maximum_amplitude = gaussian_segment_rabi_rate_scale_up( + np.amax(self.maximum_amplitude)) def _qctrl_expanded_export_content(self, file_type, coordinates): diff --git a/qctrlopencontrols/driven_controls/predefined.py b/qctrlopencontrols/driven_controls/predefined.py index a12c72d1..0887d32e 100644 --- a/qctrlopencontrols/driven_controls/predefined.py +++ b/qctrlopencontrols/driven_controls/predefined.py @@ -34,6 +34,8 @@ SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT, CORPSE_IN_SCROFULOUS_PULSE) +from .conversion import gaussian_max_rabi_rate_scale_down + def new_predefined_driven_control( driven_control_type=PRIMITIVE, @@ -143,8 +145,12 @@ def _predefined_common_attributes(maximum_rabi_rate, if shape == SQUARE: rabi_rate = maximum_rabi_rate - else: # self.shape == GAUSSIAN - rabi_rate = None#gaussian_max_rabi_rate_scale_down(maximum_rabi_rate) # TODO + elif shape == GAUSSIAN: + rabi_rate = gaussian_max_rabi_rate_scale_down(maximum_rabi_rate) + else: + raise ArgumentsValueError( + 'The shape for a driven control must be either "{}" or "{}".'.format(SQUARE, GAUSSIAN), + {'shape': shape}) rabi_rotation = float(rabi_rotation) if rabi_rotation == 0: From 45ea952a89160eec39a09dc7b88c2059f6403a09 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 08:17:05 +1000 Subject: [PATCH 11/51] add predefined driven controls to init --- qctrlopencontrols/driven_controls/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/qctrlopencontrols/driven_controls/__init__.py b/qctrlopencontrols/driven_controls/__init__.py index 38e0f74f..16593570 100644 --- a/qctrlopencontrols/driven_controls/__init__.py +++ b/qctrlopencontrols/driven_controls/__init__.py @@ -30,3 +30,12 @@ COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_WIMPERIS, SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT, CORPSE_IN_SCROFULOUS_PULSE) + +from .predefined import ( + new_primitive_control, new_wimperis_1_control, new_solovay_kitaev_1_control, + new_compensating_for_off_resonance_with_a_pulse_sequence_control, + new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control, + new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control, + new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control, + new_walsh_amplitude_modulated_filter_1_control, + new_corpse_in_scrofulous_control) From 183bd8513efabfb29c9f0f2af54413e9a1115c6d Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 09:07:33 +1000 Subject: [PATCH 12/51] start writing tests for predefined driven control --- tests/__init__.py | 13 ------ tests/test_predefined_driven_controls.py | 59 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 13 deletions(-) delete mode 100644 tests/__init__.py create mode 100644 tests/test_predefined_driven_controls.py diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index a2b61496..00000000 --- a/tests/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 Q-CTRL Pty Ltd & Q-CTRL Inc -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/test_predefined_driven_controls.py b/tests/test_predefined_driven_controls.py new file mode 100644 index 00000000..7667fd3b --- /dev/null +++ b/tests/test_predefined_driven_controls.py @@ -0,0 +1,59 @@ +# Copyright 2019 Q-CTRL Pty Ltd & Q-CTRL Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +==================================== +Tests for Predefined Driven Controls +==================================== +""" + + +import numpy as np +import pytest + +from qctrlopencontrols.exceptions import ArgumentsValueError + +from qctrlopencontrols.driven_controls import ( + new_primitive_control, new_wimperis_1_control, new_solovay_kitaev_1_control, + new_compensating_for_off_resonance_with_a_pulse_sequence_control, + new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control, + new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control, + new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control, + new_walsh_amplitude_modulated_filter_1_control, + new_corpse_in_scrofulous_control +) + +from qctrlopencontrols.globals import SQUARE + + +def test_primitive_control_segments(): + """Test the segments predefined primitive driven control + """ + _rabi_rate = 1 + _rabi_rotation = np.pi + _azimuthal_angle = np.pi/2 + _segments = [[ + _rabi_rate * np.cos(_azimuthal_angle), + _rabi_rate * np.sin(_azimuthal_angle), + 0., + _rabi_rotation / _rabi_rate], ] + + primitive_control = new_primitive_control( + rabi_rotation=_rabi_rotation, + maximum_rabi_rate=_rabi_rate, + azimuthal_angle=_azimuthal_angle, + shape=SQUARE + ) + + assert np.allclose(_segments, primitive_control.segments) From 7a8f07f41e2dcc6aaecfc86e275b7ab85c0143bb Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 11:39:45 +1000 Subject: [PATCH 13/51] fix inter-package imports --- qctrlopencontrols/driven_controls/predefined.py | 2 +- tests/test_predefined_driven_controls.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/qctrlopencontrols/driven_controls/predefined.py b/qctrlopencontrols/driven_controls/predefined.py index 0887d32e..12cedfc9 100644 --- a/qctrlopencontrols/driven_controls/predefined.py +++ b/qctrlopencontrols/driven_controls/predefined.py @@ -21,7 +21,7 @@ import numpy as np from qctrlopencontrols.exceptions import ArgumentsValueError -from qctrlopencontrols import DrivenControls +from .driven_controls import DrivenControls from qctrlopencontrols.globals import SQUARE, GAUSSIAN diff --git a/tests/test_predefined_driven_controls.py b/tests/test_predefined_driven_controls.py index 7667fd3b..3b2b0018 100644 --- a/tests/test_predefined_driven_controls.py +++ b/tests/test_predefined_driven_controls.py @@ -18,6 +18,8 @@ ==================================== """ +import os +os.chdir('/home/virginia/Documents/qctrl/python-open-controls') import numpy as np import pytest From fed20981efb63f6c4989c843da0ec2afe4a1ddf7 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 11:46:20 +1000 Subject: [PATCH 14/51] set default shape parameter --- qctrlopencontrols/driven_controls/driven_controls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_controls.py b/qctrlopencontrols/driven_controls/driven_controls.py index 83b5c397..57335c96 100644 --- a/qctrlopencontrols/driven_controls/driven_controls.py +++ b/qctrlopencontrols/driven_controls/driven_controls.py @@ -113,7 +113,7 @@ class DrivenControls(QctrlObject): #pylint: disable=too-few-public-methods def __init__(self, segments=None, - shape=None, + shape=SQUARE, scheme=None, name=None): @@ -156,7 +156,7 @@ def __init__(self, extras={'segment_durations': self.segment_durations}) super(DrivenControls, self).__init__( - base_attributes=['segments', 'shape', 'name']) + base_attributes=['segments', 'shape', 'scheme', 'name']) self.angles = self.amplitudes * self.segment_durations self.directions = np.array([self.segments[i, 0:3] / self.amplitudes[i] From e9241a4cf078a3bac836d6c008b1162ecf266564 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 12:33:56 +1000 Subject: [PATCH 15/51] add test for BB1 --- tests/test_predefined_driven_controls.py | 43 +++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/tests/test_predefined_driven_controls.py b/tests/test_predefined_driven_controls.py index 3b2b0018..d7c8b1bc 100644 --- a/tests/test_predefined_driven_controls.py +++ b/tests/test_predefined_driven_controls.py @@ -27,6 +27,7 @@ from qctrlopencontrols.exceptions import ArgumentsValueError from qctrlopencontrols.driven_controls import ( + new_predefined_driven_control, new_primitive_control, new_wimperis_1_control, new_solovay_kitaev_1_control, new_compensating_for_off_resonance_with_a_pulse_sequence_control, new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control, @@ -39,17 +40,26 @@ from qctrlopencontrols.globals import SQUARE +def test_new_predefined_driven_control(): + """Test the new_predefined_driven_control function in + qctrlopencontrols.driven_controls.predefined + """ + # Test that an error is raised if supplied with an unknown scheme + with pytest.raises(ArgumentsValueError): + _ = new_predefined_driven_control(driven_control_type='nil') + + def test_primitive_control_segments(): - """Test the segments predefined primitive driven control + """Test the segments of the predefined primitive driven control """ _rabi_rate = 1 _rabi_rotation = np.pi _azimuthal_angle = np.pi/2 _segments = [[ - _rabi_rate * np.cos(_azimuthal_angle), - _rabi_rate * np.sin(_azimuthal_angle), + np.cos(_azimuthal_angle), + np.sin(_azimuthal_angle), 0., - _rabi_rotation / _rabi_rate], ] + _rabi_rotation], ] primitive_control = new_primitive_control( rabi_rotation=_rabi_rotation, @@ -59,3 +69,28 @@ def test_primitive_control_segments(): ) assert np.allclose(_segments, primitive_control.segments) + +def test_new_wimperis_1_control(): + """Test the segments of the Wimperis 1 (BB1) driven control + """ + _rabi_rotation = np.pi + _azimuthal_angle = np.pi/2 + + phi_p = np.arccos(-_rabi_rotation / (4 * np.pi)) + + _segments = [ + [np.cos(_azimuthal_angle), np.sin(_azimuthal_angle), 0., _rabi_rotation], + [np.cos(phi_p + _azimuthal_angle), np.sin(phi_p + _azimuthal_angle), 0., np.pi], + [np.cos(3. * phi_p + _azimuthal_angle), + np.sin(3. * phi_p + _azimuthal_angle), 0., 2 * np.pi], + [np.cos(phi_p + _azimuthal_angle), np.sin(phi_p + _azimuthal_angle), 0., np.pi] + ] + + wimperis_control = new_wimperis_1_control( + rabi_rotation=_rabi_rotation, + azimuthal_angle=_azimuthal_angle, + maximum_rabi_rate=1 + ) + + assert np.allclose(wimperis_control.segments, _segments) + From 63e431884025a6b193c700f37e2f354e641ec2ea Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 12:34:52 +1000 Subject: [PATCH 16/51] fix typo --- qctrlopencontrols/driven_controls/predefined.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qctrlopencontrols/driven_controls/predefined.py b/qctrlopencontrols/driven_controls/predefined.py index 12cedfc9..80cf8f87 100644 --- a/qctrlopencontrols/driven_controls/predefined.py +++ b/qctrlopencontrols/driven_controls/predefined.py @@ -102,7 +102,7 @@ def new_predefined_driven_control( raise ArgumentsValueError( 'Unknown predefined pulse type. See help(new_predefined_driven_control) to display all' + ' allowed inputs.', - {'driven_control_type', driven_control_type}) + {'driven_control_type': driven_control_type}) return driven_control def _predefined_common_attributes(maximum_rabi_rate, From e2f52e7a62d55d7d855c75e5251e20341ab2dc56 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 12:35:29 +1000 Subject: [PATCH 17/51] add import for new_predefined_driven_control --- qctrlopencontrols/driven_controls/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qctrlopencontrols/driven_controls/__init__.py b/qctrlopencontrols/driven_controls/__init__.py index 16593570..fd1db668 100644 --- a/qctrlopencontrols/driven_controls/__init__.py +++ b/qctrlopencontrols/driven_controls/__init__.py @@ -32,6 +32,7 @@ CORPSE_IN_SCROFULOUS_PULSE) from .predefined import ( + new_predefined_driven_control, new_primitive_control, new_wimperis_1_control, new_solovay_kitaev_1_control, new_compensating_for_off_resonance_with_a_pulse_sequence_control, new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control, From f36935a27d250ec0d6a3843a02b84f2753c20f5e Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 12:37:07 +1000 Subject: [PATCH 18/51] clean up imports --- qctrlopencontrols/driven_controls/predefined.py | 3 +-- tests/test_predefined_driven_controls.py | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/qctrlopencontrols/driven_controls/predefined.py b/qctrlopencontrols/driven_controls/predefined.py index 80cf8f87..65d88fc1 100644 --- a/qctrlopencontrols/driven_controls/predefined.py +++ b/qctrlopencontrols/driven_controls/predefined.py @@ -21,9 +21,8 @@ import numpy as np from qctrlopencontrols.exceptions import ArgumentsValueError -from .driven_controls import DrivenControls - from qctrlopencontrols.globals import SQUARE, GAUSSIAN +from .driven_controls import DrivenControls from .constants import ( PRIMITIVE, WIMPERIS_1, SOLOVAY_KITAEV_1, diff --git a/tests/test_predefined_driven_controls.py b/tests/test_predefined_driven_controls.py index d7c8b1bc..bb545e9d 100644 --- a/tests/test_predefined_driven_controls.py +++ b/tests/test_predefined_driven_controls.py @@ -17,10 +17,6 @@ Tests for Predefined Driven Controls ==================================== """ - -import os -os.chdir('/home/virginia/Documents/qctrl/python-open-controls') - import numpy as np import pytest @@ -93,4 +89,3 @@ def test_new_wimperis_1_control(): ) assert np.allclose(wimperis_control.segments, _segments) - From 4a6e555974b6e8c5612552a75a8a2e162b1f4fc0 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 14:24:28 +1000 Subject: [PATCH 19/51] import new_predefined_driven_control --- qctrlopencontrols/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qctrlopencontrols/__init__.py b/qctrlopencontrols/__init__.py index 694090dc..989a3128 100644 --- a/qctrlopencontrols/__init__.py +++ b/qctrlopencontrols/__init__.py @@ -21,5 +21,5 @@ from .dynamic_decoupling_sequences import (DynamicDecouplingSequence, new_predefined_dds, convert_dds_to_driven_controls) -from .driven_controls import DrivenControls +from .driven_controls import DrivenControls, new_predefined_driven_control from .qiskit import convert_dds_to_quantum_circuit From 7c9be7b7d3331b12c167346391892c84e966b51f Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 14:25:20 +1000 Subject: [PATCH 20/51] add example notebook for driven controls --- examples/creating_a_driven_control.ipynb | 433 +++++++++++++++++++++++ 1 file changed, 433 insertions(+) create mode 100644 examples/creating_a_driven_control.ipynb diff --git a/examples/creating_a_driven_control.ipynb b/examples/creating_a_driven_control.ipynb new file mode 100644 index 00000000..8a4a6c5c --- /dev/null +++ b/examples/creating_a_driven_control.ipynb @@ -0,0 +1,433 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Creating a Driven Control\n", + "\n", + "This notebook illustrates how to use Q-CTRL Open Controls to create a driven control.\n", + "\n", + "A driven control represents the physical implementation of a quantum gate and is specified via one more multiple sets of rotation angles and phases with finite durations. Primitive driven controls only consist of one set and implement the desired gate directly, whereas dynamically corrected gates (DCGs) require driven controls made up of multiple segments. DCGs can be used as drop-in replacements to actively suppress errors in quantum circuits and improve the overall gate fidelity.\n", + "\n", + "Q-CTRL Open Controls can be used to create a driven control from a library of well-known control schemes. Once created, it can be printed, plotted, exported in CSV or JSON format for use on a quantum computer or any of [Q-CTRL's products](https://q-ctrl.com/products/)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from qctrlopencontrols import new_predefined_driven_control, DrivenControls" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Predefined Driven Control Schemes\n", + "\n", + "Q-CTRL Open Controls can create driven controls according to the following protocols:\n", + "\n", + "1. Primitive\n", + "2. First-order Wimperis broadband (BB1)\n", + "3. First-order Solovay-Kitaev (SK1)\n", + "4. Compensating for Off-Resonance with a Pulse Sequence (CORPSE)\n", + "5. First-order Walsh Amplitude-Modulated Filter (WAMF1)\n", + "6. Short Composite Rotation for Undoing Length Over and Under Shoot (SCROFULOUS)\n", + "7. COPRSE in BB1\n", + "8. CORPSE in SK1\n", + "9. CORPSE in SCROFULOUS\n", + "\n", + "See the [technical documentation](https://docs.q-ctrl.com/control-library) for details." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating and Printing a Driven Control\n", + "\n", + "A driven control is defined as one or multiple sets of unitary operations, each initialized with by a `rabi_rotation`, an `azimuthal_angle` and a `maximum_rabi_rate`. For some driven controls, you can optionally specify the `shape` of the control to create either square- or Gaussian-shaped controls.\n", + "\n", + "From those inputs, an array of segments is derived where each segment contains the (Cartesian) driving amplitudes in x, y, and z, as well as its duration. The full mathematical description of a driven control is explained in the [technical documentation](https://docs.q-ctrl.com/control-formats#control-coordinates)." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DrivenControls(segments=array([[6.28318531, 0. , 0. , 0.5 ]]),shape='square',scheme='primitive',name='Primitive X-pi')\n" + ] + } + ], + "source": [ + "## Primitive Pi pulse in X\n", + "prim = new_predefined_driven_control(\n", + " rabi_rotation=np.pi,\n", + " azimuthal_angle=0,\n", + " maximum_rabi_rate=2 * np.pi,\n", + " shape='square',\n", + " driven_control_type='primitive',\n", + " name='Primitive X-pi'\n", + ")\n", + "print(prim)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DrivenControls(segments=array([[ 6.28318531, 0. , 0. , 0.5 ],\n", + " [-1.57079633, 6.08366801, 0. , 0.5 ],\n", + " [ 4.3196899 , -4.56275101, 0. , 1. ],\n", + " [-1.57079633, 6.08366801, 0. , 0.5 ]]),shape='square',scheme='wimperis_1',name='BB1 X-pi')\n" + ] + } + ], + "source": [ + "## BB1 Pi pulse in X (implements the same effective operation as above)\n", + "bb1_x = new_predefined_driven_control(\n", + " rabi_rotation=np.pi,\n", + " azimuthal_angle=0,\n", + " maximum_rabi_rate=2 * np.pi,\n", + " driven_control_type='wimperis_1',\n", + " name='BB1 X-pi'\n", + ")\n", + "print(bb1_x)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DrivenControls(segments=array([[ 3.84734139e-16, 6.28318531e+00, 0.00000000e+00,\n", + " 2.50000000e-01],\n", + " [-6.23390466e+00, -7.85398163e-01, 0.00000000e+00,\n", + " 5.00000000e-01],\n", + " [ 5.84428562e+00, 2.30710710e+00, 0.00000000e+00,\n", + " 1.00000000e+00],\n", + " [-6.23390466e+00, -7.85398163e-01, 0.00000000e+00,\n", + " 5.00000000e-01]]),shape='square',scheme='wimperis_1',name='BB1 Y-pi/2')\n" + ] + } + ], + "source": [ + "## BB1 Pi/2 pulse in Y\n", + "bb1_y = new_predefined_driven_control(\n", + " rabi_rotation=np.pi/2,\n", + " azimuthal_angle=np.pi/2,\n", + " maximum_rabi_rate=2 * np.pi,\n", + " driven_control_type='wimperis_1',\n", + " name='BB1 Y-pi/2'\n", + ")\n", + "print(bb1_y)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DrivenControls(segments=array([[ 3.84734139e-16, 6.28318531e+00, 0.00000000e+00,\n", + " 2.50000000e-01],\n", + " [ 6.23390466e+00, -7.85398163e-01, 0.00000000e+00,\n", + " 1.00000000e+00],\n", + " [-6.23390466e+00, -7.85398163e-01, 0.00000000e+00,\n", + " 1.00000000e+00]]),shape='square',scheme='solovay_kitaev_1',name='SK1 Y-pi/2')\n" + ] + } + ], + "source": [ + "## SK1 Pi/2 pulse in Y\n", + "sk1 = new_predefined_driven_control(\n", + " rabi_rotation=np.pi/2,\n", + " azimuthal_angle=np.pi/2,\n", + " maximum_rabi_rate=2 * np.pi,\n", + " driven_control_type='solovay_kitaev_1',\n", + " name='SK1 Y-pi/2'\n", + ")\n", + "print(sk1)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DrivenControls(segments=array([[ 6.28318531e+00, 0.00000000e+00, 0.00000000e+00,\n", + " 1.06748664e+00],\n", + " [-6.28318531e+00, 7.69468277e-16, 0.00000000e+00,\n", + " 8.84973272e-01],\n", + " [ 6.28318531e+00, 0.00000000e+00, 0.00000000e+00,\n", + " 6.74866360e-02]]),shape='square',scheme='compensating_for_off_resonance_with_a_pulse_sequence',name='CORPSE X-pi/2')\n" + ] + } + ], + "source": [ + "## CORPSE Pi/2 pulse in X\n", + "corpse = new_predefined_driven_control(\n", + " rabi_rotation=np.pi/2,\n", + " azimuthal_angle=0,\n", + " maximum_rabi_rate=2 * np.pi,\n", + " driven_control_type='compensating_for_off_resonance_with_a_pulse_sequence',\n", + " name='CORPSE X-pi/2'\n", + ")\n", + "print(corpse)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plotting a driven control\n", + "\n", + "Once created, Q-CTRL Open Controls provides the method `get_plot_formatted_arrays` to create a set of formatted arrays ready to be immediately plotted with Matplotlib. We use the `wimperis_1` (BB1) as a driven control to generate plots of the `x_amplitudes`, `y_amplitudes` and `z_amplitudes`." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "formatted_plot_data = bb1_x.get_plot_formatted_arrays()\n", + "x_amplitudes, y_amplitudes, z_amplitudes, times = (formatted_plot_data['x_amplitudes'],\n", + " formatted_plot_data['y_amplitudes'],\n", + " formatted_plot_data['z_amplitudes'],\n", + " formatted_plot_data['times'])\n", + "# prepare the axes\n", + "figure, (x_axis, y_axis, z_axis) = plt.subplots(1, 3, figsize=(20,5))\n", + "\n", + "x_axis.fill_between(times, x_amplitudes, 0, alpha=0.15, color='C0')\n", + "x_axis.plot(times, x_amplitudes)\n", + "x_axis.set_xlabel('Time (sec)')\n", + "x_axis.set_ylabel('Drive amplitude in X (rad)')\n", + "\n", + "y_axis.fill_between(times, y_amplitudes, 0, alpha=0.15, color='C0')\n", + "y_axis.plot(times, y_amplitudes)\n", + "y_axis.set_xlabel('Time (sec)')\n", + "y_axis.set_ylabel('Drive amplitude in Y (rad)')\n", + "\n", + "z_axis.fill_between(times, z_amplitudes, 0, alpha=0.15, color='C0')\n", + "z_axis.plot(times, z_amplitudes)\n", + "z_axis.set_xlabel('Time (sec)')\n", + "z_axis.set_ylabel('Drive amplitude in Z (rad)')\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exporting the Driven Control\n", + "\n", + "Q-CTRL Open Controls enables exporting driven controls in CSV or JSON format. An exported driven control is [formatted](https://docs.q-ctrl.com/control-formats) to be compatible with [Q-CTRL BLACK OPAL](https://app.q-ctrl.com).\n", + "\n", + "Q-CTRL Open Controls can export a driven control in either `cartesian` or `cylindrical` coordinates. For details, consult the [technical documentation](https://docs.q-ctrl.com/output-data-formats#q-ctrl-hardware).\n", + "\n", + "In the example below, we chose the `bb1_x` control (created above) for exporting to a CSV file." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "file_type='CSV'\n", + "filename='example_driven_control.csv'\n", + "\n", + "bb1_x.export_to_file(\n", + " filename=filename, \n", + " file_type=file_type,\n", + " coordinates='cartesian')" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "amplitude_x,amplitude_y,detuning,duration,maximum_rabi_rate\n", + "\n", + "1.0,0.0,0.0,0.5,6.283185307179586\n", + "\n", + "-0.2500000000000001,0.9682458365518541,0.0,0.5,6.283185307179586\n", + "\n", + "0.6875000000000002,-0.7261843774138904,0.0,1.0,6.283185307179586\n", + "\n", + "-0.2500000000000001,0.9682458365518541,0.0,0.5,6.283185307179586\n" + ] + } + ], + "source": [ + "## Reload the file and check its content to better understand the format\n", + "with open(filename, 'rt') as handle:\n", + " file_content = handle.readlines()\n", + "for line in file_content:\n", + " print(line)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Opening the Exported Sequence in Q-CTRL BLACK OPAL" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The exported CSV files are compatible for analysis by a suite of tools offered by [Q-CTRL BLACK OPAL](https://app.q-ctrl.com). For example, you can upload the exported file in the [1-QUBIT Workspace](https://app.q-ctrl.com/oneQubit) for further analysis. The process to upload a custom control is described in [Uploading and Evaluating Custom Controls](https://help.q-ctrl.com/black-opal/guides/uploading-and-evaluating-custom-controls). For a full capability of BLACK OPAL, consult [Q-CTRL Help](https://help.q-ctrl.com/black-opal)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Custom Definition of a Driven Control\n", + "\n", + "`DrivenControls`, defined in Q-CTRL Open Controls, accepts a `segments` array of x, y and z amplitudes and durations to define any arbitrary Driven Control. This is the most generalized way to create any custom Driven Control." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# defining the required parameters\n", + "x_amplitudes = [np.pi/2, 0, -np.pi, 0]\n", + "y_amplitudes = [0, -np.pi/2, 0, np.pi]\n", + "z_amplitudes = [np.pi/2, 0, -np.pi/2, 0]\n", + "durations = [0.5, 1, 2, 0.5]\n", + "\n", + "_segments = np.vstack([x_amplitudes, y_amplitudes, z_amplitudes, durations]).T\n", + "_name = 'Custon Driven Control'\n", + "_shape = 'square'\n", + "\n", + "custom_driven_control = DrivenControls(segments=_segments, name=_name, shape=_shape)\n", + "\n", + "## let us plot and verify\n", + "formatted_plot_data = custom_driven_control.get_plot_formatted_arrays()\n", + "x_amplitudes, y_amplitudes, z_amplitudes, times = (formatted_plot_data['x_amplitudes'],\n", + " formatted_plot_data['y_amplitudes'],\n", + " formatted_plot_data['z_amplitudes'],\n", + " formatted_plot_data['times'])\n", + "# prepare the axes\n", + "figure, (x_axis, y_axis, z_axis) = plt.subplots(1, 3, figsize=(20,5))\n", + "\n", + "x_axis.fill_between(times, x_amplitudes, 0, alpha=0.15, color='C0')\n", + "x_axis.plot(times, x_amplitudes)\n", + "x_axis.set_xlabel('Time (sec)')\n", + "x_axis.set_ylabel('Drive amplitude in X (rad)')\n", + "\n", + "y_axis.fill_between(times, y_amplitudes, 0, alpha=0.15, color='C0')\n", + "y_axis.plot(times, y_amplitudes)\n", + "y_axis.set_xlabel('Time (sec)')\n", + "y_axis.set_ylabel('Drive amplitude in Y (rad)')\n", + "\n", + "z_axis.fill_between(times, z_amplitudes, 0, alpha=0.15, color='C0')\n", + "z_axis.plot(times, z_amplitudes)\n", + "z_axis.set_xlabel('Time (sec)')\n", + "z_axis.set_ylabel('Drive amplitude in Z (rad)')\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From aba33875a8199f7f71594cccde39cdc9005e4143 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 14:30:04 +1000 Subject: [PATCH 21/51] add reference to Q-CTRL docs --- qctrlopencontrols/driven_controls/predefined.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qctrlopencontrols/driven_controls/predefined.py b/qctrlopencontrols/driven_controls/predefined.py index 65d88fc1..503bde26 100644 --- a/qctrlopencontrols/driven_controls/predefined.py +++ b/qctrlopencontrols/driven_controls/predefined.py @@ -13,9 +13,12 @@ # limitations under the License. """ -=================== +========================== driven_controls.predefined -=================== +========================== + +More information and publication references to all driven controls defined here +can be found at https://docs.q-ctrl.com/control-library """ import numpy as np From 2df52567947b3adea0b7d3a4293d37ff7603f71f Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 16:44:33 +1000 Subject: [PATCH 22/51] add pretty __str__ representation for DrivenControls --- examples/creating_a_driven_control.ipynb | 62 +++++++++---------- .../driven_controls/driven_controls.py | 33 ++++++++++ 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/examples/creating_a_driven_control.ipynb b/examples/creating_a_driven_control.ipynb index 8a4a6c5c..a0491b55 100644 --- a/examples/creating_a_driven_control.ipynb +++ b/examples/creating_a_driven_control.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -65,14 +65,18 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "DrivenControls(segments=array([[6.28318531, 0. , 0. , 0.5 ]]),shape='square',scheme='primitive',name='Primitive X-pi')\n" + "Primitive X-pi:\n", + "X Amplitudes: [+2] x pi\n", + "Y Amplitudes: [+0] x pi\n", + "Z Amplitudes: [+0] x pi\n", + "Durations: [+1] x 0.5s\n" ] } ], @@ -91,17 +95,18 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "DrivenControls(segments=array([[ 6.28318531, 0. , 0. , 0.5 ],\n", - " [-1.57079633, 6.08366801, 0. , 0.5 ],\n", - " [ 4.3196899 , -4.56275101, 0. , 1. ],\n", - " [-1.57079633, 6.08366801, 0. , 0.5 ]]),shape='square',scheme='wimperis_1',name='BB1 X-pi')\n" + "BB1 X-pi:\n", + "X Amplitudes: [+2, -0.5, +1.375, -0.5] x pi\n", + "Y Amplitudes: [+0, +1.936, -1.452, +1.936] x pi\n", + "Z Amplitudes: [+0, +0, +0, +0] x pi\n", + "Durations: [+0.2,+0.2,+0.4,+0.2] x 2.5s\n" ] } ], @@ -119,21 +124,18 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "DrivenControls(segments=array([[ 3.84734139e-16, 6.28318531e+00, 0.00000000e+00,\n", - " 2.50000000e-01],\n", - " [-6.23390466e+00, -7.85398163e-01, 0.00000000e+00,\n", - " 5.00000000e-01],\n", - " [ 5.84428562e+00, 2.30710710e+00, 0.00000000e+00,\n", - " 1.00000000e+00],\n", - " [-6.23390466e+00, -7.85398163e-01, 0.00000000e+00,\n", - " 5.00000000e-01]]),shape='square',scheme='wimperis_1',name='BB1 Y-pi/2')\n" + "BB1 Y-pi/2:\n", + "X Amplitudes: [+0, -1.984, +1.86, -1.984] x pi\n", + "Y Amplitudes: [+2, -0.25, +0.734, -0.25] x pi\n", + "Z Amplitudes: [+0, +0, +0, +0] x pi\n", + "Durations: [+0.111,+0.222,+0.444,+0.222] x 2.25s\n" ] } ], @@ -151,19 +153,18 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "DrivenControls(segments=array([[ 3.84734139e-16, 6.28318531e+00, 0.00000000e+00,\n", - " 2.50000000e-01],\n", - " [ 6.23390466e+00, -7.85398163e-01, 0.00000000e+00,\n", - " 1.00000000e+00],\n", - " [-6.23390466e+00, -7.85398163e-01, 0.00000000e+00,\n", - " 1.00000000e+00]]),shape='square',scheme='solovay_kitaev_1',name='SK1 Y-pi/2')\n" + "SK1 Y-pi/2:\n", + "X Amplitudes: [+0, +1.984, -1.984] x pi\n", + "Y Amplitudes: [+2, -0.25, -0.25] x pi\n", + "Z Amplitudes: [+0, +0, +0] x pi\n", + "Durations: [+0.111,+0.444,+0.444] x 2.25s\n" ] } ], @@ -181,19 +182,18 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "DrivenControls(segments=array([[ 6.28318531e+00, 0.00000000e+00, 0.00000000e+00,\n", - " 1.06748664e+00],\n", - " [-6.28318531e+00, 7.69468277e-16, 0.00000000e+00,\n", - " 8.84973272e-01],\n", - " [ 6.28318531e+00, 0.00000000e+00, 0.00000000e+00,\n", - " 6.74866360e-02]]),shape='square',scheme='compensating_for_off_resonance_with_a_pulse_sequence',name='CORPSE X-pi/2')\n" + "CORPSE X-pi/2:\n", + "X Amplitudes: [+2, -2, +2] x pi\n", + "Y Amplitudes: [+0, +0, +0] x pi\n", + "Z Amplitudes: [+0, +0, +0] x pi\n", + "Durations: [+0.528,+0.438,+0.033] x 2.0199465438373845s\n" ] } ], diff --git a/qctrlopencontrols/driven_controls/driven_controls.py b/qctrlopencontrols/driven_controls/driven_controls.py index 57335c96..962507d6 100644 --- a/qctrlopencontrols/driven_controls/driven_controls.py +++ b/qctrlopencontrols/driven_controls/driven_controls.py @@ -482,6 +482,39 @@ def get_transformed_segments(self, coordinates=CARTESIAN, dimensionless=True): return transformed_segments + def __str__(self): + """Prepares a friendly string format for a Driven Control + """ + driven_control_string = list() + + # Specify the number of decimals for pretty string representation + decimals_format_str = '{:+.3f}' + + if self.name is not None: + driven_control_string.append('{}:'.format(self.name)) + + # Format amplitudes + for i, axis in enumerate('XYZ'): + pretty_amplitudes_str = ', '.join( + [decimals_format_str.format(amplitude/np.pi).rstrip('0').rstrip('.') + for amplitude in self.segments[:, i]] + ) + driven_control_string.append( + '{} Amplitudes: [{}] x pi'.format(axis, pretty_amplitudes_str) + ) + # Format durations + total_duration = np.sum(self.segments[:, 3]) + pretty_durations_str = ','.join( + [decimals_format_str.format(duration/total_duration).rstrip('0').rstrip('.') + for duration in self.segments[:, 3]] + ) + driven_control_string.append( + 'Durations: [{}] x {}s'.format(pretty_durations_str, str(total_duration)) + ) + + driven_control_string = '\n'.join(driven_control_string) + + return driven_control_string From bd94077bece1c87cb41eac31d84619a739eacb79 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 16:56:31 +1000 Subject: [PATCH 23/51] update keyword argument 'scheme' --- examples/creating_a_driven_control.ipynb | 14 ++-- .../driven_controls/driven_controls.py | 7 -- .../driven_controls/predefined.py | 66 ++++++------------- 3 files changed, 28 insertions(+), 59 deletions(-) diff --git a/examples/creating_a_driven_control.ipynb b/examples/creating_a_driven_control.ipynb index a0491b55..c4198f52 100644 --- a/examples/creating_a_driven_control.ipynb +++ b/examples/creating_a_driven_control.ipynb @@ -87,7 +87,7 @@ " azimuthal_angle=0,\n", " maximum_rabi_rate=2 * np.pi,\n", " shape='square',\n", - " driven_control_type='primitive',\n", + " scheme='primitive',\n", " name='Primitive X-pi'\n", ")\n", "print(prim)" @@ -116,7 +116,7 @@ " rabi_rotation=np.pi,\n", " azimuthal_angle=0,\n", " maximum_rabi_rate=2 * np.pi,\n", - " driven_control_type='wimperis_1',\n", + " scheme='wimperis_1',\n", " name='BB1 X-pi'\n", ")\n", "print(bb1_x)" @@ -145,7 +145,7 @@ " rabi_rotation=np.pi/2,\n", " azimuthal_angle=np.pi/2,\n", " maximum_rabi_rate=2 * np.pi,\n", - " driven_control_type='wimperis_1',\n", + " scheme='wimperis_1',\n", " name='BB1 Y-pi/2'\n", ")\n", "print(bb1_y)" @@ -174,7 +174,7 @@ " rabi_rotation=np.pi/2,\n", " azimuthal_angle=np.pi/2,\n", " maximum_rabi_rate=2 * np.pi,\n", - " driven_control_type='solovay_kitaev_1',\n", + " scheme='solovay_kitaev_1',\n", " name='SK1 Y-pi/2'\n", ")\n", "print(sk1)" @@ -203,7 +203,7 @@ " rabi_rotation=np.pi/2,\n", " azimuthal_angle=0,\n", " maximum_rabi_rate=2 * np.pi,\n", - " driven_control_type='compensating_for_off_resonance_with_a_pulse_sequence',\n", + " scheme='compensating_for_off_resonance_with_a_pulse_sequence',\n", " name='CORPSE X-pi/2'\n", ")\n", "print(corpse)" @@ -220,7 +220,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -345,7 +345,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 9, "metadata": {}, "outputs": [ { diff --git a/qctrlopencontrols/driven_controls/driven_controls.py b/qctrlopencontrols/driven_controls/driven_controls.py index 962507d6..f4e8c743 100644 --- a/qctrlopencontrols/driven_controls/driven_controls.py +++ b/qctrlopencontrols/driven_controls/driven_controls.py @@ -100,8 +100,6 @@ class DrivenControls(QctrlObject): #pylint: disable=too-few-public-methods shape : str, optional Defines the shape used in each segment can be 'square' or 'gaussian'. Defaults to 'square'. - scheme : string, optional - Defaults to None. The scheme the driven control came from if derived that way. name : string, optional Defaults to None. An optional string to name the driven control. @@ -114,7 +112,6 @@ class DrivenControls(QctrlObject): #pylint: disable=too-few-public-methods def __init__(self, segments=None, shape=SQUARE, - scheme=None, name=None): self.shape = str(shape) @@ -126,10 +123,6 @@ def __init__(self, if self.name is not None: self.name = str(self.name) - self.scheme = scheme - if self.scheme is not None: - self.scheme = str(self.scheme) - if segments is None: segments = [[np.pi, 0, 0, 1], ] diff --git a/qctrlopencontrols/driven_controls/predefined.py b/qctrlopencontrols/driven_controls/predefined.py index 503bde26..cc1aa76e 100644 --- a/qctrlopencontrols/driven_controls/predefined.py +++ b/qctrlopencontrols/driven_controls/predefined.py @@ -40,14 +40,14 @@ def new_predefined_driven_control( - driven_control_type=PRIMITIVE, + scheme=PRIMITIVE, **kwargs): """ Create a new driven control Parameters ---------- - driven_control_type : string, optional + scheme : string, optional Defaults to None. The name of the driven control type, supported options are: - 'primitive' @@ -75,36 +75,36 @@ def new_predefined_driven_control( # Forced to import here to avoid cyclic imports, need to review # Raise error if the input driven_control_type is not known - if driven_control_type == PRIMITIVE: + if scheme == PRIMITIVE: driven_control = new_primitive_control(**kwargs) - elif driven_control_type == WIMPERIS_1: + elif scheme == WIMPERIS_1: driven_control = new_wimperis_1_control(**kwargs) - elif driven_control_type == SOLOVAY_KITAEV_1: + elif scheme == SOLOVAY_KITAEV_1: driven_control = new_solovay_kitaev_1_control(**kwargs) - elif driven_control_type == WALSH_AMPLITUDE_MODULATED_FILTER_1: + elif scheme == WALSH_AMPLITUDE_MODULATED_FILTER_1: driven_control = new_walsh_amplitude_modulated_filter_1_control(**kwargs) - elif driven_control_type == COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE: + elif scheme == COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE: driven_control = new_compensating_for_off_resonance_with_a_pulse_sequence_control( **kwargs) - elif driven_control_type == COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_WIMPERIS: + elif scheme == COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_WIMPERIS: driven_control = \ new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( **kwargs) - elif driven_control_type == \ + elif scheme == \ COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_SOLOVAY_KITAEV: driven_control = \ new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( **kwargs) - elif driven_control_type == SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT: + elif scheme == SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT: driven_control = \ new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control(**kwargs) - elif driven_control_type == CORPSE_IN_SCROFULOUS_PULSE: + elif scheme == CORPSE_IN_SCROFULOUS_PULSE: driven_control = new_corpse_in_scrofulous_control(**kwargs) else: raise ArgumentsValueError( 'Unknown predefined pulse type. See help(new_predefined_driven_control) to display all' + ' allowed inputs.', - {'driven_control_type': driven_control_type}) + {'scheme': scheme}) return driven_control def _predefined_common_attributes(maximum_rabi_rate, @@ -256,7 +256,7 @@ def new_primitive_control( 0., rabi_rotation / rabi_rate], ] - return DrivenControls(segments=segments, shape=shape, scheme=PRIMITIVE, **kwargs) + return DrivenControls(segments=segments, shape=shape, **kwargs) def new_wimperis_1_control( @@ -299,7 +299,7 @@ def new_wimperis_1_control( segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, shape=shape, scheme=WIMPERIS_1, **kwargs) + return DrivenControls(segments=segments, shape=shape, **kwargs) def new_solovay_kitaev_1_control( @@ -342,7 +342,7 @@ def new_solovay_kitaev_1_control( segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, shape=shape, scheme=SOLOVAY_KITAEV_1, **kwargs) + return DrivenControls(segments=segments, shape=shape, **kwargs) def new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( # pylint: disable=invalid-name @@ -420,11 +420,7 @@ def degrees_to_radians(angle_in_degrees): segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls( - segments=segments, - shape=shape, - scheme=SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT, - **kwargs) + return DrivenControls(segments=segments, shape=shape, **kwargs) def new_compensating_for_off_resonance_with_a_pulse_sequence_control( # pylint: disable=invalid-name @@ -466,11 +462,7 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_control( # pylint: segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls( - segments=segments, - shape=shape, - scheme=COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE, - **kwargs) + return DrivenControls(segments=segments, shape=shape, **kwargs) def new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( # pylint: disable=invalid-name @@ -517,11 +509,7 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_contr segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls( - segments=segments, - shape=shape, - scheme=COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_WIMPERIS, - **kwargs) + return DrivenControls(segments=segments, shape=shape, **kwargs) def new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( # pylint: disable=invalid-name @@ -567,11 +555,7 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls( - segments=segments, - shape=shape, - scheme=COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_SOLOVAY_KITAEV, - **kwargs) + return DrivenControls(segments=segments, shape=shape, **kwargs) def new_corpse_in_scrofulous_control( # pylint: disable=invalid-name @@ -657,11 +641,7 @@ def degrees_to_radians(angle_in_degrees): segments = _derive_segments(total_angles, amplitude=rabi_rate) - return DrivenControls( - segments=segments, - shape=shape, - scheme=CORPSE_IN_SCROFULOUS_PULSE, - **kwargs) + return DrivenControls(segments=segments, shape=shape, **kwargs) def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-name @@ -759,8 +739,4 @@ def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-n rabi_rate_plus * np.sin(azimuthal_angle), 0., time_segment]]) - return DrivenControls( - segments=segments, - shape=shape, - scheme=WALSH_AMPLITUDE_MODULATED_FILTER_1, - **kwargs) + return DrivenControls(segments=segments, shape=shape, **kwargs) From 962e691ed5cb8f4ebc8a56e4a33cc691034704c6 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Fri, 19 Apr 2019 17:02:17 +1000 Subject: [PATCH 24/51] add check to test_primitive_control --- tests/test_predefined_driven_controls.py | 30 ++++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/tests/test_predefined_driven_controls.py b/tests/test_predefined_driven_controls.py index bb545e9d..a1fcc4bc 100644 --- a/tests/test_predefined_driven_controls.py +++ b/tests/test_predefined_driven_controls.py @@ -24,13 +24,13 @@ from qctrlopencontrols.driven_controls import ( new_predefined_driven_control, - new_primitive_control, new_wimperis_1_control, new_solovay_kitaev_1_control, - new_compensating_for_off_resonance_with_a_pulse_sequence_control, - new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control, - new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control, - new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control, - new_walsh_amplitude_modulated_filter_1_control, - new_corpse_in_scrofulous_control + new_primitive_control, new_wimperis_1_control#, new_solovay_kitaev_1_control, + #new_compensating_for_off_resonance_with_a_pulse_sequence_control, + #new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control, + #new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control, + #new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control, + #new_walsh_amplitude_modulated_filter_1_control, + #new_corpse_in_scrofulous_control ) from qctrlopencontrols.globals import SQUARE @@ -42,7 +42,7 @@ def test_new_predefined_driven_control(): """ # Test that an error is raised if supplied with an unknown scheme with pytest.raises(ArgumentsValueError): - _ = new_predefined_driven_control(driven_control_type='nil') + _ = new_predefined_driven_control(scheme='nil') def test_primitive_control_segments(): @@ -57,14 +57,24 @@ def test_primitive_control_segments(): 0., _rabi_rotation], ] - primitive_control = new_primitive_control( + primitive_control_1 = new_primitive_control( rabi_rotation=_rabi_rotation, maximum_rabi_rate=_rabi_rate, azimuthal_angle=_azimuthal_angle, shape=SQUARE ) - assert np.allclose(_segments, primitive_control.segments) + # Test the new_predefined_driven_control function also + primitive_control_2 = new_predefined_driven_control( + rabi_rotation=_rabi_rotation, + maximum_rabi_rate=_rabi_rate, + azimuthal_angle=_azimuthal_angle, + shape=SQUARE, + scheme='primitive' + ) + + assert np.allclose(_segments, primitive_control_1.segments) + assert np.allclose(_segments, primitive_control_2.segments) def test_new_wimperis_1_control(): """Test the segments of the Wimperis 1 (BB1) driven control From 3e9d2a04a6184108cbb1b10d852659499ba4a8d5 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Sat, 20 Apr 2019 08:11:32 +1000 Subject: [PATCH 25/51] add tests for SK1 and SCROFULOUS --- tests/test_predefined_driven_controls.py | 114 +++++++++++++++++++++-- 1 file changed, 108 insertions(+), 6 deletions(-) diff --git a/tests/test_predefined_driven_controls.py b/tests/test_predefined_driven_controls.py index a1fcc4bc..d5ef86d8 100644 --- a/tests/test_predefined_driven_controls.py +++ b/tests/test_predefined_driven_controls.py @@ -24,11 +24,12 @@ from qctrlopencontrols.driven_controls import ( new_predefined_driven_control, - new_primitive_control, new_wimperis_1_control#, new_solovay_kitaev_1_control, + new_primitive_control, new_wimperis_1_control, new_solovay_kitaev_1_control, + PRIMITIVE, WIMPERIS_1, SOLOVAY_KITAEV_1, + new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control #new_compensating_for_off_resonance_with_a_pulse_sequence_control, #new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control, #new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control, - #new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control, #new_walsh_amplitude_modulated_filter_1_control, #new_corpse_in_scrofulous_control ) @@ -44,6 +45,22 @@ def test_new_predefined_driven_control(): with pytest.raises(ArgumentsValueError): _ = new_predefined_driven_control(scheme='nil') +def test_predefined_common_attributes(): + """Test that expected exceptions are raised correctly for invalid parameters + """ + # Test negative maximum Rabi rate + with pytest.raises(ArgumentsValueError): + _ = new_predefined_driven_control( + maximum_rabi_rate=-1, shape='PRIMITIVE', rabi_rotation=1, azimuthal_angle=0) + # Test invalid shape + with pytest.raises(ArgumentsValueError): + _ = new_predefined_driven_control( + maximum_rabi_rate=1, shape='-', rabi_rotation=1, azimuthal_angle=0) + # Test zero Rabi rotation + with pytest.raises(ArgumentsValueError): + _ = new_predefined_driven_control( + maximum_rabi_rate=1, shape='PRIMITIVE', rabi_rotation=0, azimuthal_angle=0) + def test_primitive_control_segments(): """Test the segments of the predefined primitive driven control @@ -70,13 +87,14 @@ def test_primitive_control_segments(): maximum_rabi_rate=_rabi_rate, azimuthal_angle=_azimuthal_angle, shape=SQUARE, - scheme='primitive' + scheme=PRIMITIVE ) assert np.allclose(_segments, primitive_control_1.segments) assert np.allclose(_segments, primitive_control_2.segments) -def test_new_wimperis_1_control(): + +def test_wimperis_1_control(): """Test the segments of the Wimperis 1 (BB1) driven control """ _rabi_rotation = np.pi @@ -92,10 +110,94 @@ def test_new_wimperis_1_control(): [np.cos(phi_p + _azimuthal_angle), np.sin(phi_p + _azimuthal_angle), 0., np.pi] ] - wimperis_control = new_wimperis_1_control( + wimperis_control_1 = new_wimperis_1_control( rabi_rotation=_rabi_rotation, azimuthal_angle=_azimuthal_angle, maximum_rabi_rate=1 ) + wimperis_control_2 = new_predefined_driven_control( + rabi_rotation=_rabi_rotation, + azimuthal_angle=_azimuthal_angle, + maximum_rabi_rate=1, + scheme=WIMPERIS_1 + ) + + assert np.allclose(wimperis_control_1.segments, _segments) + assert np.allclose(wimperis_control_2.segments, _segments) + + +def test_solovay_kitaev_1_control(): + """Test the segments of the Solovay-Kitaev 1 (SK1) driven control + """ + _rabi_rotation = np.pi + _azimuthal_angle = np.pi/2 + + phi_p = np.arccos(-_rabi_rotation / (4 * np.pi)) + + _segments = [ + [np.cos(_azimuthal_angle), np.sin(_azimuthal_angle), 0., _rabi_rotation], + [np.cos(-phi_p + _azimuthal_angle), np.sin(-phi_p + _azimuthal_angle), 0., 2 * np.pi], + [np.cos(phi_p + _azimuthal_angle), np.sin(phi_p + _azimuthal_angle), 0., 2 * np.pi] + ] + + sk1_control_1 = new_solovay_kitaev_1_control( + rabi_rotation=_rabi_rotation, + azimuthal_angle=_azimuthal_angle, + maximum_rabi_rate=1 + ) + + sk1_control_2 = new_predefined_driven_control( + scheme=SOLOVAY_KITAEV_1, + rabi_rotation=_rabi_rotation, + azimuthal_angle=_azimuthal_angle, + maximum_rabi_rate=1 + ) + + assert np.allclose(sk1_control_1.segments, _segments) + assert np.allclose(sk1_control_2.segments, _segments) + +def test_scofulous_control(): + """Test the segments of the SCROFULOUS driven control. + Note: here we test against numerical pulse segments since the angles are + defined numerically as well. + """ + # Test that exceptions are raised upon wrong inputs for rabi_rotation + # (SCROFULOUS is only defined for pi/4, pi/2 and pi pulses) + with pytest.raises(ArgumentsValueError): + _ = new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( + rabi_rotation=0.3 + ) + + # Construct SCROFULOUS controls for target rotations pi/4, pi/2 and pi + pi_segments = new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( + rabi_rotation=np.pi, azimuthal_angle=0.5, maximum_rabi_rate=2*np.pi + ).segments + + _pi_segments = np.array([ + [0.14826172, 6.28143583, 0., 0.5], + [5.36575214, -3.26911633, 0., 0.5], + [0.14826172, 6.28143583, 0., 0.5]]) + + assert np.allclose(pi_segments, _pi_segments) + + pi_on_2_segments = new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( + rabi_rotation=np.pi/2, azimuthal_angle=-0.5, maximum_rabi_rate=2*np.pi + ).segments + + _pi_on_2_segments = np.array([ + [5.25211762, 3.44872124, 0., 0.32], + [-1.95046211, -5.97278119, 0., 0.5], + [5.25211762, 3.44872124, 0., 0.32]]) + + assert np.allclose(pi_on_2_segments, _pi_on_2_segments) + + pi_on_4_segments = new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( + rabi_rotation=np.pi/4, azimuthal_angle=0, maximum_rabi_rate=2*np.pi + ).segments + + _pi_on_4_segments = np.array([ + [1.78286387, 6.0249327, 0., 0.26861111], + [0.54427724, -6.25956707, 0., 0.5], + [1.78286387, 6.0249327, 0., 0.26861111]]) - assert np.allclose(wimperis_control.segments, _segments) + assert np.allclose(pi_on_4_segments, _pi_on_4_segments) From f77478f65b8ae8348ad96af6781dd3feb258f139 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Sat, 20 Apr 2019 14:05:05 +1000 Subject: [PATCH 26/51] add remaining test cases for all predefined driven controls --- tests/__init__.py | 13 ++ tests/test_predefined_driven_controls.py | 196 ++++++++++++++++++++++- 2 files changed, 202 insertions(+), 7 deletions(-) create mode 100644 tests/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..a2b61496 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2019 Q-CTRL Pty Ltd & Q-CTRL Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tests/test_predefined_driven_controls.py b/tests/test_predefined_driven_controls.py index d5ef86d8..25568a32 100644 --- a/tests/test_predefined_driven_controls.py +++ b/tests/test_predefined_driven_controls.py @@ -25,13 +25,13 @@ from qctrlopencontrols.driven_controls import ( new_predefined_driven_control, new_primitive_control, new_wimperis_1_control, new_solovay_kitaev_1_control, - PRIMITIVE, WIMPERIS_1, SOLOVAY_KITAEV_1, - new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control - #new_compensating_for_off_resonance_with_a_pulse_sequence_control, - #new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control, - #new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control, - #new_walsh_amplitude_modulated_filter_1_control, - #new_corpse_in_scrofulous_control + PRIMITIVE, WIMPERIS_1, SOLOVAY_KITAEV_1, COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE, + new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control, + new_corpse_in_scrofulous_control, + new_compensating_for_off_resonance_with_a_pulse_sequence_control, + new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control, + new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control, + new_walsh_amplitude_modulated_filter_1_control ) from qctrlopencontrols.globals import SQUARE @@ -201,3 +201,185 @@ def test_scofulous_control(): [1.78286387, 6.0249327, 0., 0.26861111]]) assert np.allclose(pi_on_4_segments, _pi_on_4_segments) + +def test_corpse_in_scrofulous_control(): + """Test the segments of the CORPSE in SCROFULOUS driven control. + Note: here we test against numerical pulse segments since the SCROFULOUS angles are + defined numerically as well. + """ + # Test pi and pi/2 rotations + pi_segments = new_corpse_in_scrofulous_control( + rabi_rotation=np.pi, azimuthal_angle=0.5, maximum_rabi_rate=2*np.pi + ).segments + + _pi_segments = np.array([ + [0.14826172, 6.28143583, 0., 1.16666667], + [-0.14826172, -6.28143583, 0., 0.83333333], + [0.14826172, 6.28143583, 0., 0.16666667], + [5.36575214, -3.26911633, 0., 1.16666667], + [-5.36575214, 3.26911633, 0., 0.83333333], + [5.36575214, -3.26911633, 0., 0.16666667], + [0.14826172, 6.28143583, 0., 1.16666667], + [-0.14826172, -6.28143583, 0., 0.83333333], + [0.14826172, 6.28143583, 0., 0.16666667]]) + + assert np.allclose(pi_segments, _pi_segments) + + pi_on_2_segments = new_corpse_in_scrofulous_control( + rabi_rotation=np.pi/2, azimuthal_angle=0.25, maximum_rabi_rate=np.pi + ).segments + + _pi_on_2_segments = np.array([ + [0.74606697, 3.05171894, 0., 2.18127065], + [-0.74606697, -3.05171894, 0., 1.7225413], + [0.74606697, 3.05171894, 0., 0.18127065], + [1.32207387, -2.84986405, 0., 2.33333333], + [-1.32207387, 2.84986405, 0., 1.66666667], + [1.32207387, -2.84986405, 0., 0.33333333], + [0.74606697, 3.05171894, 0., 2.18127065], + [-0.74606697, -3.05171894, 0., 1.7225413], + [0.74606697, 3.05171894, 0., 0.18127065]]) + + assert np.allclose(pi_on_2_segments, _pi_on_2_segments) + + +def test_corpse_control(): + """Test the segments of the CORPSE driven control + """ + _rabi_rotation = np.pi + _azimuthal_angle = np.pi/4 + + k = np.arcsin(np.sin(_rabi_rotation / 2.) / 2.) + + _segments = [ + [np.cos(_azimuthal_angle), np.sin(_azimuthal_angle), 0., + 2. * np.pi + _rabi_rotation / 2. - k], + [np.cos(np.pi + _azimuthal_angle), np.sin(np.pi + _azimuthal_angle), 0., + 2. * np.pi - 2. * k], + [np.cos(_azimuthal_angle), np.sin(_azimuthal_angle), 0., _rabi_rotation / 2. - k] + ] + + corpse_control_1 = new_compensating_for_off_resonance_with_a_pulse_sequence_control( + rabi_rotation=_rabi_rotation, + azimuthal_angle=_azimuthal_angle, + maximum_rabi_rate=1 + ) + + corpse_control_2 = new_predefined_driven_control( + scheme=COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE, + rabi_rotation=_rabi_rotation, + azimuthal_angle=_azimuthal_angle, + maximum_rabi_rate=1 + ) + + assert np.allclose(corpse_control_1.segments, _segments) + assert np.allclose(corpse_control_2.segments, _segments) + +def test_cinbb_control(): + """Test the segments of the CinBB (BB1 made up of CORPSEs) driven control + """ + segments = new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( + rabi_rotation=np.pi/3, azimuthal_angle=0.25, maximum_rabi_rate=np.pi + ).segments + + _segments = np.array([ + [3.04392815, 0.77724246, 0., 2.08623604], + [-3.04392815, -0.77724246, 0., 1.83913875], + [3.04392815, 0.77724246, 0., 0.08623604], + [-1.02819968, 2.96857033, 0., 1.], + [1.50695993, -2.75656964, 0., 2.], + [-1.02819968, 2.96857033, 0., 1.]]) + + assert np.allclose(segments, _segments) + + segments = new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( + rabi_rotation=np.pi/5, azimuthal_angle=-0.25, maximum_rabi_rate=np.pi + ).segments + + _segments = np.array([ + [3.04392815, -0.77724246, 0., 2.0506206], + [-3.04392815, 0.77724246, 0., 1.9012412], + [3.04392815, -0.77724246, 0., 0.0506206], + [0.62407389, 3.07898298, 0., 1.], + [-0.31344034, -3.12591739, 0., 2.], + [0.62407389, 3.07898298, 0., 1.]]) + + assert np.allclose(segments, _segments) + + +def test_cinsk1_control(): + """Test the segments of the CinSK1 (SK1 made up of CORPSEs) driven control + """ + segments = new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( + rabi_rotation=np.pi/2, azimuthal_angle=0.5, maximum_rabi_rate=2*np.pi + ).segments + + _segments = np.array([ + [5.51401386, 3.0123195, 0., 1.06748664], + [-5.51401386, -3.0123195, 0., 0.88497327], + [5.51401386, 3.0123195, 0., 0.06748664], + [2.29944137, -5.84730596, 0., 1.], + [-3.67794483, 5.09422609, 0., 1.]]) + + assert np.allclose(segments, _segments) + + segments = new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( + rabi_rotation=2*np.pi, azimuthal_angle=-0.5, maximum_rabi_rate=2*np.pi + ).segments + + _segments = np.array([ + [5.51401386, -3.0123195, 0., 1.5], + [-5.51401386, 3.0123195, 0., 1.], + [5.51401386, -3.0123195, 0., 0.5], + [-5.36575214, -3.26911633, 0., 1.], + [-0.14826172, 6.28143583, 0., 1.]]) + + assert np.allclose(segments, _segments) + +def test_walsh_control(): + """Test the segments of the first order Walsh driven control + """ + # Test that exceptions are raised upon wrong inputs for rabi_rotation + # (WALSH control is only defined for pi/4, pi/2 and pi pulses) + with pytest.raises(ArgumentsValueError): + _ = new_walsh_amplitude_modulated_filter_1_control( + rabi_rotation=0.3 + ) + # test pi rotation + pi_segments = new_walsh_amplitude_modulated_filter_1_control( + rabi_rotation=np.pi, azimuthal_angle=-0.35, maximum_rabi_rate=2*np.pi + ).segments + + _pi_segments = np.array([ + [5.90225283, -2.15449047, 0., 0.5], + [2.95112641, -1.07724523, 0., 0.5], + [2.95112641, -1.07724523, 0., 0.5], + [5.90225283, -2.15449047, 0., 0.5]]) + + assert np.allclose(pi_segments, _pi_segments) + + # test pi/2 rotation + pi_on_2_segments = new_walsh_amplitude_modulated_filter_1_control( + rabi_rotation=np.pi/2, azimuthal_angle=0.57, maximum_rabi_rate=2*np.pi + ).segments + + _pi_on_2_segments = np.array([ + [5.28981984, 3.39060816, 0., 0.39458478], + [3.08895592, 1.9799236, 0., 0.39458478], + [3.08895592, 1.9799236, 0., 0.39458478], + [5.28981984, 3.39060816, 0., 0.39458478]]) + + assert np.allclose(pi_on_2_segments, _pi_on_2_segments) + + # test pi/4 rotation + pi_on_4_segments = new_walsh_amplitude_modulated_filter_1_control( + rabi_rotation=np.pi/4, azimuthal_angle=-0.273, maximum_rabi_rate=2*np.pi + ).segments + + _pi_on_4_segments = np.array([ + [6.05049612, -1.69408213, 0., 0.3265702], + [4.37116538, -1.22388528, 0., 0.3265702], + [4.37116538, -1.22388528, 0., 0.3265702], + [6.05049612, -1.69408213, 0., 0.3265702]]) + + assert np.allclose(pi_on_4_segments, _pi_on_4_segments) From 1b00aa1e7a69845c9d992088f87f1d4cc1f87e4a Mon Sep 17 00:00:00 2001 From: virginia-m Date: Tue, 23 Apr 2019 14:41:09 +1000 Subject: [PATCH 27/51] change plot colours --- examples/creating_a_driven_control.ipynb | 54 ++++++++++++------------ 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/examples/creating_a_driven_control.ipynb b/examples/creating_a_driven_control.ipynb index c4198f52..60cdbed6 100644 --- a/examples/creating_a_driven_control.ipynb +++ b/examples/creating_a_driven_control.ipynb @@ -8,7 +8,7 @@ "\n", "This notebook illustrates how to use Q-CTRL Open Controls to create a driven control.\n", "\n", - "A driven control represents the physical implementation of a quantum gate and is specified via one more multiple sets of rotation angles and phases with finite durations. Primitive driven controls only consist of one set and implement the desired gate directly, whereas dynamically corrected gates (DCGs) require driven controls made up of multiple segments. DCGs can be used as drop-in replacements to actively suppress errors in quantum circuits and improve the overall gate fidelity.\n", + "A driven control represents the physical implementation of a quantum gate and is specified via one or multiple sets of rotation angles and phases with finite durations. Primitive driven controls only consist of one set and implement the desired gate directly, whereas dynamically corrected gates (DCGs) require driven controls made up of multiple segments. DCGs can be used as drop-in replacements to actively suppress errors in quantum circuits and improve the overall gate fidelity.\n", "\n", "Q-CTRL Open Controls can be used to create a driven control from a library of well-known control schemes. Once created, it can be printed, plotted, exported in CSV or JSON format for use on a quantum computer or any of [Q-CTRL's products](https://q-ctrl.com/products/)." ] @@ -22,13 +22,18 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", - "from qctrlopencontrols import new_predefined_driven_control, DrivenControls" + "from qctrlopencontrols import new_predefined_driven_control, DrivenControls\n", + "\n", + "plt.rcParams['axes.prop_cycle'] = plt.cycler(\n", + " color=['#231e21', '#381e42', '#c80a64', '#680cea', '#5b02c1',\n", + " '#ffffff', '#faf7fc', '#ae9fb4', '#6c5b72', '250d2e']\n", + ")" ] }, { @@ -65,7 +70,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -95,7 +100,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -220,12 +225,12 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 24, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -245,18 +250,18 @@ "# prepare the axes\n", "figure, (x_axis, y_axis, z_axis) = plt.subplots(1, 3, figsize=(20,5))\n", "\n", - "x_axis.fill_between(times, x_amplitudes, 0, alpha=0.15, color='C0')\n", - "x_axis.plot(times, x_amplitudes)\n", + "x_axis.fill_between(times, x_amplitudes, 0, alpha=0.15, color='C3')\n", + "x_axis.plot(times, x_amplitudes, color='C3')\n", "x_axis.set_xlabel('Time (sec)')\n", "x_axis.set_ylabel('Drive amplitude in X (rad)')\n", "\n", - "y_axis.fill_between(times, y_amplitudes, 0, alpha=0.15, color='C0')\n", - "y_axis.plot(times, y_amplitudes)\n", + "y_axis.fill_between(times, y_amplitudes, 0, alpha=0.15, color='C3')\n", + "y_axis.plot(times, y_amplitudes, color='C3')\n", "y_axis.set_xlabel('Time (sec)')\n", "y_axis.set_ylabel('Drive amplitude in Y (rad)')\n", "\n", - "z_axis.fill_between(times, z_amplitudes, 0, alpha=0.15, color='C0')\n", - "z_axis.plot(times, z_amplitudes)\n", + "z_axis.fill_between(times, z_amplitudes, 0, alpha=0.15, color='C3')\n", + "z_axis.plot(times, z_amplitudes, color='C3')\n", "z_axis.set_xlabel('Time (sec)')\n", "z_axis.set_ylabel('Drive amplitude in Z (rad)')\n", "\n", @@ -345,12 +350,12 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 25, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -383,30 +388,23 @@ "# prepare the axes\n", "figure, (x_axis, y_axis, z_axis) = plt.subplots(1, 3, figsize=(20,5))\n", "\n", - "x_axis.fill_between(times, x_amplitudes, 0, alpha=0.15, color='C0')\n", - "x_axis.plot(times, x_amplitudes)\n", + "x_axis.fill_between(times, x_amplitudes, 0, alpha=0.15, color='C3')\n", + "x_axis.plot(times, x_amplitudes, color='C3')\n", "x_axis.set_xlabel('Time (sec)')\n", "x_axis.set_ylabel('Drive amplitude in X (rad)')\n", "\n", - "y_axis.fill_between(times, y_amplitudes, 0, alpha=0.15, color='C0')\n", - "y_axis.plot(times, y_amplitudes)\n", + "y_axis.fill_between(times, y_amplitudes, 0, alpha=0.15, color='C3')\n", + "y_axis.plot(times, y_amplitudes, color='C3')\n", "y_axis.set_xlabel('Time (sec)')\n", "y_axis.set_ylabel('Drive amplitude in Y (rad)')\n", "\n", - "z_axis.fill_between(times, z_amplitudes, 0, alpha=0.15, color='C0')\n", - "z_axis.plot(times, z_amplitudes)\n", + "z_axis.fill_between(times, z_amplitudes, 0, alpha=0.15, color='C3')\n", + "z_axis.plot(times, z_amplitudes, color='C3')\n", "z_axis.set_xlabel('Time (sec)')\n", "z_axis.set_ylabel('Drive amplitude in Z (rad)')\n", "\n", "plt.show()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From 0e78d4be824bc8830b4e9d2f65b0f48490e3b238 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Mon, 6 May 2019 12:32:30 +1000 Subject: [PATCH 28/51] remove Gaussian pulse shapes --- .../driven_controls/constants.py | 4 - .../driven_controls/conversion.py | 55 ------- .../driven_controls/driven_controls.py | 22 +-- .../driven_controls/predefined.py | 135 +++++------------- qctrlopencontrols/globals/__init__.py | 9 -- tests/test_predefined_driven_controls.py | 10 +- 6 files changed, 35 insertions(+), 200 deletions(-) diff --git a/qctrlopencontrols/driven_controls/constants.py b/qctrlopencontrols/driven_controls/constants.py index 0922ff9d..f58fcdb3 100644 --- a/qctrlopencontrols/driven_controls/constants.py +++ b/qctrlopencontrols/driven_controls/constants.py @@ -18,10 +18,6 @@ ================ """ -GAUSSIAN_STANDARD_DEVIATION_SCALE = 6. -""" -""" - #maximum and minimum values UPPER_BOUND_RABI_RATE = 1e10 """Maximum allowed rabi rate diff --git a/qctrlopencontrols/driven_controls/conversion.py b/qctrlopencontrols/driven_controls/conversion.py index fb1bd743..2b19d72f 100644 --- a/qctrlopencontrols/driven_controls/conversion.py +++ b/qctrlopencontrols/driven_controls/conversion.py @@ -19,14 +19,10 @@ """ import numpy as np -from scipy.special import erf from qctrlopencontrols.globals import CARTESIAN, CYLINDRICAL from qctrlopencontrols.exceptions import ArgumentsValueError -from .constants import GAUSSIAN_STANDARD_DEVIATION_SCALE - - def convert_to_standard_segments(transformed_segments, maximum_rabi_rate=None, coordinates=CARTESIAN, dimensionless=True): """Converts the dimensionless segments of any type into dimension-full Cartesian segments @@ -101,54 +97,3 @@ def convert_to_standard_segments(transformed_segments, maximum_rabi_rate=None, segments_copy[:, 0:2] = segments_copy[:, 0:2] * maximum_rabi_rate return segments_copy - - -def _gaussian_rabi_rate_scale(): - """ - Returns the scale factor for the rabi rate - - Returns - ------- - float - Gaussian scale factor. - """ - rho = GAUSSIAN_STANDARD_DEVIATION_SCALE - return ( - rho * (np.exp(rho**2 / 8.) - 1.) / - (np.exp(rho**2 / 8.) * np.sqrt(2 * np.pi) * erf(rho / (2 * np.sqrt(2))) - rho)) - - -def gaussian_max_rabi_rate_scale_down(maximum_rabi_rate): # pylint: disable=invalid-name - """ - Takes a maximum rabi rate and scales it down to the rabi rate that would be used in each - segments - - Parameters - ---------- - maximum_rabi_rate : float - The maximum rabi rate - - Returns - ------- - float - Rabi rate for segment - """ - return maximum_rabi_rate / _gaussian_rabi_rate_scale() - - -def gaussian_segment_rabi_rate_scale_up(segment_rabi_rate): # pylint: disable=invalid-name - """ - Takes a rabi rate of a segment and scales it up to the actual maximum rabi rate if - it was gaussian. - - Parameters - ---------- - segment_rabi_rate : float - The segment rabi rate - - Returns - ------- - float - Maximum rabi rate for segment - """ - return segment_rabi_rate * _gaussian_rabi_rate_scale() diff --git a/qctrlopencontrols/driven_controls/driven_controls.py b/qctrlopencontrols/driven_controls/driven_controls.py index f4e8c743..c6c73eae 100644 --- a/qctrlopencontrols/driven_controls/driven_controls.py +++ b/qctrlopencontrols/driven_controls/driven_controls.py @@ -24,15 +24,12 @@ from qctrlopencontrols.base import QctrlObject from qctrlopencontrols.globals import ( - QCTRL_EXPANDED, CSV, JSON, CARTESIAN, CYLINDRICAL, SQUARE, GAUSSIAN) + QCTRL_EXPANDED, CSV, JSON, CARTESIAN, CYLINDRICAL) from .constants import ( UPPER_BOUND_SEGMENTS, UPPER_BOUND_RABI_RATE, UPPER_BOUND_DETUNING_RATE, UPPER_BOUND_DURATION, LOWER_BOUND_DURATION) -from .conversion import gaussian_segment_rabi_rate_scale_up - - def get_plot_data_from_segments(segments): """ Generates arrays that can be used to produce a plot representing the shape of the driven control @@ -97,9 +94,6 @@ class DrivenControls(QctrlObject): #pylint: disable=too-few-public-methods corresponding pauli matrix, i.e. amplitude_x would correspond to sigma_x. The duration is the time of that segment. If None, defaults to a square pi pulse [[np.pi, 0, 0, 1], ]. - shape : str, optional - Defines the shape used in each segment can be 'square' or 'gaussian'. Defaults to - 'square'. name : string, optional Defaults to None. An optional string to name the driven control. @@ -111,14 +105,8 @@ class DrivenControls(QctrlObject): #pylint: disable=too-few-public-methods def __init__(self, segments=None, - shape=SQUARE, name=None): - self.shape = str(shape) - if not (self.shape == SQUARE or self.shape == GAUSSIAN): - raise ArgumentsValueError('Shape must be "square" or "gaussian".', - {'shape': self.shape}) - self.name = name if self.name is not None: self.name = str(self.name) @@ -194,14 +182,6 @@ def __init__(self, {'segments': self.segments}, extras={'minimum_duration'}) - if self.shape == GAUSSIAN: - self.maximum_rabi_rate = gaussian_segment_rabi_rate_scale_up( - np.amax(self.maximum_rabi_rate)) - self.maximum_detuning = gaussian_segment_rabi_rate_scale_up( - np.amax(self.maximum_detuning)) - self.maximum_amplitude = gaussian_segment_rabi_rate_scale_up( - np.amax(self.maximum_amplitude)) - def _qctrl_expanded_export_content(self, file_type, coordinates): """Private method to prepare the content to be saved in Q-CTRL expanded format diff --git a/qctrlopencontrols/driven_controls/predefined.py b/qctrlopencontrols/driven_controls/predefined.py index cc1aa76e..c37f7d55 100644 --- a/qctrlopencontrols/driven_controls/predefined.py +++ b/qctrlopencontrols/driven_controls/predefined.py @@ -24,7 +24,6 @@ import numpy as np from qctrlopencontrols.exceptions import ArgumentsValueError -from qctrlopencontrols.globals import SQUARE, GAUSSIAN from .driven_controls import DrivenControls from .constants import ( @@ -36,8 +35,6 @@ SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT, CORPSE_IN_SCROFULOUS_PULSE) -from .conversion import gaussian_max_rabi_rate_scale_down - def new_predefined_driven_control( scheme=PRIMITIVE, @@ -109,7 +106,6 @@ def new_predefined_driven_control( def _predefined_common_attributes(maximum_rabi_rate, rabi_rotation, - shape, azimuthal_angle): """ Adds some checks etc for all the predefined pulses @@ -122,8 +118,6 @@ def _predefined_common_attributes(maximum_rabi_rate, maximum_rabi_rate : float Defaults to 2.*np.pi The maximum rabi frequency for the pulse. - shape : string - The shape of the pulse. azimuthal_angle : float The azimuthal position of the pulse. @@ -145,15 +139,6 @@ def _predefined_common_attributes(maximum_rabi_rate, 'Maximum rabi angular frequency should be greater than zero.', {'maximum_rabi_rate': maximum_rabi_rate}) - if shape == SQUARE: - rabi_rate = maximum_rabi_rate - elif shape == GAUSSIAN: - rabi_rate = gaussian_max_rabi_rate_scale_down(maximum_rabi_rate) - else: - raise ArgumentsValueError( - 'The shape for a driven control must be either "{}" or "{}".'.format(SQUARE, GAUSSIAN), - {'shape': shape}) - rabi_rotation = float(rabi_rotation) if rabi_rotation == 0: raise ArgumentsValueError( @@ -163,7 +148,7 @@ def _predefined_common_attributes(maximum_rabi_rate, azimuthal_angle = float(azimuthal_angle) - return (rabi_rate, rabi_rotation, azimuthal_angle) + return (maximum_rabi_rate, rabi_rotation, azimuthal_angle) def _get_transformed_rabi_rotation_wimperis(rabi_rotation): """ @@ -223,7 +208,6 @@ def new_primitive_control( rabi_rotation=None, azimuthal_angle=0., maximum_rabi_rate=2. * np.pi, - shape=SQUARE, **kwargs): """ Primitive driven control. @@ -235,8 +219,6 @@ def new_primitive_control( maximum_rabi_rate : float, optional Defaults to 2.*np.pi The maximum rabi frequency for the driven control. - shape : str, optional - Shape of the driven control. azimuthal_angle : float, optional The azimuthal position of the driven control. kwargs : dict @@ -248,7 +230,7 @@ def new_primitive_control( The driven control. """ (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( - maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + maximum_rabi_rate, rabi_rotation, azimuthal_angle) segments = [[ rabi_rate * np.cos(azimuthal_angle), @@ -256,14 +238,13 @@ def new_primitive_control( 0., rabi_rotation / rabi_rate], ] - return DrivenControls(segments=segments, shape=shape, **kwargs) + return DrivenControls(segments=segments, **kwargs) def new_wimperis_1_control( rabi_rotation=None, azimuthal_angle=0., maximum_rabi_rate=2. * np.pi, - shape=SQUARE, **kwargs): """ Wimperis or BB1 control. @@ -275,8 +256,6 @@ def new_wimperis_1_control( maximum_rabi_rate : float, optional Defaults to 2.*np.pi The maximum rabi frequency for the control. - shape : str, optional - Shape of the driven control. azimuthal_angle : float, optional The azimuthal position of the control. kwargs : dict @@ -288,7 +267,7 @@ def new_wimperis_1_control( The driven control. """ (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( - maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + maximum_rabi_rate, rabi_rotation, azimuthal_angle) phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) angles = np.array([ @@ -299,14 +278,13 @@ def new_wimperis_1_control( segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, shape=shape, **kwargs) + return DrivenControls(segments=segments, **kwargs) def new_solovay_kitaev_1_control( rabi_rotation=None, azimuthal_angle=0., maximum_rabi_rate=2. * np.pi, - shape=SQUARE, **kwargs): """ First-order Solovay-Kitaev control, also known as SK1 @@ -318,8 +296,6 @@ def new_solovay_kitaev_1_control( maximum_rabi_rate : float, optional Defaults to 2.*np.pi The maximum rabi frequency for the control. - shape : str, optional - Shape of the driven control. azimuthal_angle : float, optional The azimuthal position of the control. kwargs : dict @@ -331,7 +307,7 @@ def new_solovay_kitaev_1_control( The driven control. """ (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( - maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + maximum_rabi_rate, rabi_rotation, azimuthal_angle) phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) @@ -342,14 +318,13 @@ def new_solovay_kitaev_1_control( segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, shape=shape, **kwargs) + return DrivenControls(segments=segments, **kwargs) def new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( # pylint: disable=invalid-name rabi_rotation=None, azimuthal_angle=0., maximum_rabi_rate=2. * np.pi, - shape=SQUARE, **kwargs): """ SCROFULOUS control to compensate for pulse length errors @@ -361,8 +336,6 @@ def new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control maximum_rabi_rate : float, optional Defaults to 2.*np.pi The maximum rabi frequency for the control. - shape : str, optional - Shape of driven control. azimuthal_angle : float, optional The azimuthal position of the control. kwargs : dict @@ -379,7 +352,7 @@ def new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control Raised when an argument is invalid. """ (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( - maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + maximum_rabi_rate, rabi_rotation, azimuthal_angle) # Create a lookup table for rabi rotation and phase angles, taken from the official paper. # Note: values in the paper are in degrees. @@ -420,14 +393,13 @@ def degrees_to_radians(angle_in_degrees): segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, shape=shape, **kwargs) + return DrivenControls(segments=segments, **kwargs) def new_compensating_for_off_resonance_with_a_pulse_sequence_control( # pylint: disable=invalid-name rabi_rotation=None, azimuthal_angle=0., maximum_rabi_rate=2. * np.pi, - shape=SQUARE, **kwargs): """ Compensating for off resonance with a pulse sequence, often abbreviated as CORPSE. @@ -439,8 +411,6 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_control( # pylint: maximum_rabi_rate : float, optional Defaults to 2.*np.pi The maximum rabi frequency for the control. - shape : str, optional - Shape of the driven control. azimuthal_angle : float, optional The azimuthal position of the control. kwargs : dict @@ -452,7 +422,7 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_control( # pylint: The driven control. """ (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( - maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + maximum_rabi_rate, rabi_rotation, azimuthal_angle) k = np.arcsin(np.sin(rabi_rotation / 2.) / 2.) angles = np.array([ @@ -462,14 +432,13 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_control( # pylint: segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, shape=shape, **kwargs) + return DrivenControls(segments=segments, **kwargs) def new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( # pylint: disable=invalid-name rabi_rotation=None, azimuthal_angle=0., maximum_rabi_rate=2. * np.pi, - shape=SQUARE, **kwargs): """ Compensating for off resonance with a pulse sequence with an embedded @@ -482,8 +451,6 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_contr maximum_rabi_rate : float, optional Defaults to 2.*np.pi The maximum rabi frequency for the control. - shape : str, optional - Shape of the driven control. azimuthal_angle : float, optional The azimuthal position of the control. kwargs : dict @@ -495,7 +462,7 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_contr The driven control. """ (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( - maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + maximum_rabi_rate, rabi_rotation, azimuthal_angle) phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) k = np.arcsin(np.sin(rabi_rotation / 2.) / 2.) @@ -509,14 +476,13 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_contr segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, shape=shape, **kwargs) + return DrivenControls(segments=segments, **kwargs) def new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( # pylint: disable=invalid-name rabi_rotation=None, azimuthal_angle=0., maximum_rabi_rate=2. * np.pi, - shape=SQUARE, **kwargs): """ Compensating for off resonance with a pulse sequence with an @@ -531,8 +497,6 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev The maximum rabi frequency for the control. azimuthal_angle : float, optional The azimuthal position of the control. - shape : str, optional - Shape of the driven control. kwargs : dict Other keywords required to make a qctrlopencontrols.DrivenControls. @@ -542,7 +506,7 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev The driven control. """ (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( - maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + maximum_rabi_rate, rabi_rotation, azimuthal_angle) phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) k = np.arcsin(np.sin(rabi_rotation / 2.) / 2.) @@ -555,14 +519,13 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, shape=shape, **kwargs) + return DrivenControls(segments=segments, **kwargs) def new_corpse_in_scrofulous_control( # pylint: disable=invalid-name rabi_rotation=None, azimuthal_angle=0., maximum_rabi_rate=2. * np.pi, - shape=SQUARE, **kwargs): """ CORPSE (Compensating for Off Resonance with a Pulse SEquence) embedded within a @@ -578,8 +541,6 @@ def new_corpse_in_scrofulous_control( # pylint: disable=invalid-name The maximum rabi frequency for the control. azimuthal_angle : float, optional The azimuthal position of the control. - shape : str, optional - Shape of the driven control. kwargs : dict Other keywords required to make a qctrlopencontrols.DrivenControls. @@ -594,7 +555,7 @@ def new_corpse_in_scrofulous_control( # pylint: disable=invalid-name Raised when an argument is invalid. """ (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( - maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) + maximum_rabi_rate, rabi_rotation, azimuthal_angle) # Create a lookup table for rabi rotation and phase angles, taken from # the Cummings paper. Note: values in the paper are in degrees. @@ -641,14 +602,13 @@ def degrees_to_radians(angle_in_degrees): segments = _derive_segments(total_angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, shape=shape, **kwargs) + return DrivenControls(segments=segments, **kwargs) def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-name rabi_rotation=None, azimuthal_angle=0., maximum_rabi_rate=2. * np.pi, - shape=SQUARE, **kwargs): """ First order Walsh control with amplitude modulation. @@ -662,8 +622,6 @@ def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-n The maximum rabi frequency for the control. azimuthal_angle : float, optional The azimuthal position of the control. - shape : str, optional - Shape of the driven control. kwargs : dict Other keywords required to make a qctrlopencontrols.DrivenControls. @@ -678,48 +636,21 @@ def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-n Raised when an argument is invalid. """ (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( - maximum_rabi_rate, rabi_rotation, shape, azimuthal_angle) - - if shape == SQUARE: - if np.isclose(rabi_rotation, np.pi): - theta_plus = np.pi - theta_minus = np.pi / 2. - elif np.isclose(rabi_rotation, 0.5 * np.pi): - theta_plus = np.pi * (2.5 + 0.65667825) / 4. - theta_minus = np.pi * (2.5 - 0.65667825) / 4. - elif np.isclose(rabi_rotation, 0.25 * np.pi): - theta_plus = np.pi * (2.25 + 0.36256159) / 4. - theta_minus = np.pi * (2.25 - 0.36256159) / 4. - else: - raise ArgumentsValueError( - 'rabi_rotation angle must be either pi, pi/2 or pi/4', - {'rabi_rotation': rabi_rotation}) - - # Old on the fly general calc for square - # Need to solve transcendental equation to get modulation depth factor - # Have some precompiled solution, otherwise do it numerically - # init_factor = 1.93296 - 0.220866 * (rabi_rotation / np.pi) - # def factor_func(factor): - # return ( - # ((1 - factor) * np.sin(rabi_rotation / 2.) - # + factor * np.sin((rabi_rotation * (factor - 1)) / (2. * (factor - 2.))))**2 - # ) / ((factor - 1)**2) - # modulation_depth_factor = newton(factor_func, init_factor) - # assert 0. < modulation_depth_factor <= 2. - else: # shape == GAUSSIAN - if np.isclose(rabi_rotation, np.pi): - theta_plus = np.pi * (3 + 0.616016981956213) / 4 - theta_minus = np.pi * (3 - 0.616016981956213) / 4 - elif np.isclose(rabi_rotation, 0.5 * np.pi): - theta_plus = np.pi * (2.5 + 0.4684796993336457) / 4 - theta_minus = np.pi * (2.5 - 0.4684796993336457) / 4 - elif np.isclose(rabi_rotation, 0.25 * np.pi): - theta_plus = np.pi * (2.25 + 0.27723876925525176) / 4 - theta_minus = np.pi * (2.25 - 0.27723876925525176) / 4 - else: - raise ArgumentsValueError( - 'rabi_rotation angle must be either pi, pi/2 or pi/4', - {'rabi_rotation': rabi_rotation}) + maximum_rabi_rate, rabi_rotation, azimuthal_angle) + + if np.isclose(rabi_rotation, np.pi): + theta_plus = np.pi + theta_minus = np.pi / 2. + elif np.isclose(rabi_rotation, 0.5 * np.pi): + theta_plus = np.pi * (2.5 + 0.65667825) / 4. + theta_minus = np.pi * (2.5 - 0.65667825) / 4. + elif np.isclose(rabi_rotation, 0.25 * np.pi): + theta_plus = np.pi * (2.25 + 0.36256159) / 4. + theta_minus = np.pi * (2.25 - 0.36256159) / 4. + else: + raise ArgumentsValueError( + 'rabi_rotation angle must be either pi, pi/2 or pi/4', + {'rabi_rotation': rabi_rotation}) rabi_rate_plus = rabi_rate time_segment = theta_plus / rabi_rate_plus @@ -739,4 +670,4 @@ def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-n rabi_rate_plus * np.sin(azimuthal_angle), 0., time_segment]]) - return DrivenControls(segments=segments, shape=shape, **kwargs) + return DrivenControls(segments=segments, **kwargs) diff --git a/qctrlopencontrols/globals/__init__.py b/qctrlopencontrols/globals/__init__.py index 106ed10f..78ccdc81 100644 --- a/qctrlopencontrols/globals/__init__.py +++ b/qctrlopencontrols/globals/__init__.py @@ -39,12 +39,3 @@ CYLINDRICAL = 'cylindrical' """Defines Cylindrical coordinate system """ - -#shapes for driven controls -SQUARE = 'square' -"""Defines square segments for a DrivenControls object -""" - -GAUSSIAN = 'gaussian' -"""Defines gaussian segments for a DrivenControls object -""" diff --git a/tests/test_predefined_driven_controls.py b/tests/test_predefined_driven_controls.py index 25568a32..372871f5 100644 --- a/tests/test_predefined_driven_controls.py +++ b/tests/test_predefined_driven_controls.py @@ -34,8 +34,6 @@ new_walsh_amplitude_modulated_filter_1_control ) -from qctrlopencontrols.globals import SQUARE - def test_new_predefined_driven_control(): """Test the new_predefined_driven_control function in @@ -52,10 +50,6 @@ def test_predefined_common_attributes(): with pytest.raises(ArgumentsValueError): _ = new_predefined_driven_control( maximum_rabi_rate=-1, shape='PRIMITIVE', rabi_rotation=1, azimuthal_angle=0) - # Test invalid shape - with pytest.raises(ArgumentsValueError): - _ = new_predefined_driven_control( - maximum_rabi_rate=1, shape='-', rabi_rotation=1, azimuthal_angle=0) # Test zero Rabi rotation with pytest.raises(ArgumentsValueError): _ = new_predefined_driven_control( @@ -77,8 +71,7 @@ def test_primitive_control_segments(): primitive_control_1 = new_primitive_control( rabi_rotation=_rabi_rotation, maximum_rabi_rate=_rabi_rate, - azimuthal_angle=_azimuthal_angle, - shape=SQUARE + azimuthal_angle=_azimuthal_angle ) # Test the new_predefined_driven_control function also @@ -86,7 +79,6 @@ def test_primitive_control_segments(): rabi_rotation=_rabi_rotation, maximum_rabi_rate=_rabi_rate, azimuthal_angle=_azimuthal_angle, - shape=SQUARE, scheme=PRIMITIVE ) From d27c9c4547296e67bbc8fb6104fc01f2d52efa33 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Mon, 6 May 2019 13:59:51 +1000 Subject: [PATCH 29/51] change driven control naming conventions --- qctrlopencontrols/__init__.py | 2 +- qctrlopencontrols/driven_controls/__init__.py | 16 +++--- .../driven_controls/constants.py | 29 +++++------ .../{driven_controls.py => driven_control.py} | 4 +- .../driven_controls/predefined.py | 50 +++++++++---------- .../driven_controls.py | 6 +-- tests/test_driven_controls.py | 18 +++---- tests/test_predefined_driven_controls.py | 8 +-- 8 files changed, 65 insertions(+), 68 deletions(-) rename qctrlopencontrols/driven_controls/{driven_controls.py => driven_control.py} (99%) diff --git a/qctrlopencontrols/__init__.py b/qctrlopencontrols/__init__.py index 989a3128..b7268b97 100644 --- a/qctrlopencontrols/__init__.py +++ b/qctrlopencontrols/__init__.py @@ -21,5 +21,5 @@ from .dynamic_decoupling_sequences import (DynamicDecouplingSequence, new_predefined_dds, convert_dds_to_driven_controls) -from .driven_controls import DrivenControls, new_predefined_driven_control +from .driven_controls import DrivenControl, new_predefined_driven_control from .qiskit import convert_dds_to_quantum_circuit diff --git a/qctrlopencontrols/driven_controls/__init__.py b/qctrlopencontrols/driven_controls/__init__.py index fd1db668..779ff29a 100644 --- a/qctrlopencontrols/driven_controls/__init__.py +++ b/qctrlopencontrols/driven_controls/__init__.py @@ -18,18 +18,18 @@ ============= """ -from .driven_controls import DrivenControls +from .driven_control import DrivenControl from .constants import ( UPPER_BOUND_RABI_RATE, UPPER_BOUND_DETUNING_RATE, UPPER_BOUND_DURATION, LOWER_BOUND_DURATION, UPPER_BOUND_SEGMENTS, - PRIMITIVE, WIMPERIS_1, SOLOVAY_KITAEV_1, - WALSH_AMPLITUDE_MODULATED_FILTER_1, - COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE, - COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_SOLOVAY_KITAEV, - COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_WIMPERIS, - SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT, - CORPSE_IN_SCROFULOUS_PULSE) + PRIMITIVE, BB1, SK1, + WAMF1, + CORPSE, + CORPSE_IN_SK1, + CORPSE_IN_BB1, + SCROFULOUS, + CORPSE_IN_SCROFULOUS) from .predefined import ( new_predefined_driven_control, diff --git a/qctrlopencontrols/driven_controls/constants.py b/qctrlopencontrols/driven_controls/constants.py index f58fcdb3..5ef657a8 100644 --- a/qctrlopencontrols/driven_controls/constants.py +++ b/qctrlopencontrols/driven_controls/constants.py @@ -44,38 +44,35 @@ """Primitive control """ -WIMPERIS_1 = 'wimperis_1' +BB1 = 'BB1' """First-order Wimperis control, also known as BB1 """ -SOLOVAY_KITAEV_1 = 'solovay_kitaev_1' +SK1 = 'SK1' """First-order Solovay-Kitaev control """ -WALSH_AMPLITUDE_MODULATED_FILTER_1 = 'walsh_amplitude_modulated_filter_1' +WAMF1 = 'WAMF1' """First-order Walsh sequence control """ -COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE = \ - 'compensating_for_off_resonance_with_a_pulse_sequence' -"""Dynamically corrected control - commonly abbreviated as COPRSE +CORPSE = 'CORPSE' +"""Dynamically corrected control - Compensating for Off-Resonance with a Pulse Sequence (COPRSE) """ -COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_WIMPERIS = \ - 'compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis' -"""Concatenated dynamically corrected control - Wimperis inside COPRSE +CORPSE_IN_BB1 = 'CORPSE in BB1' +"""Concatenated dynamically corrected control - BB1 inside COPRSE """ -COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_SOLOVAY_KITAEV = \ - 'compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev' -"""Concatenated dynamically corrected control - Solovay-Kitaev inside COPRSE +CORPSE_IN_SK1 = 'CORPSE in SK1' +"""Concatenated dynamically corrected control - First order Solovay-Kitaev inside COPRSE """ -SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT = \ - 'short_composite_rotation_for_undoing_length_over_and_under_shoot' -"""Dynamically corrected control - commonly abbreviated as SCROFULOUS +SCROFULOUS = 'SCROFULOUS' +"""Dynamically corrected control - + Short Composite Rotation For Undoing Length Over and Under Shoot (SCROFULOUS) """ -CORPSE_IN_SCROFULOUS_PULSE = 'corpse_in_scrofulous_pulse' +CORPSE_IN_SCROFULOUS = 'CORPSE in SCROFULOUS' """Concatenated dynamically corrected control - CORPSE inside SCROFULOUS """ diff --git a/qctrlopencontrols/driven_controls/driven_controls.py b/qctrlopencontrols/driven_controls/driven_control.py similarity index 99% rename from qctrlopencontrols/driven_controls/driven_controls.py rename to qctrlopencontrols/driven_controls/driven_control.py index c6c73eae..42326870 100644 --- a/qctrlopencontrols/driven_controls/driven_controls.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -79,7 +79,7 @@ def get_plot_data_from_segments(segments): -class DrivenControls(QctrlObject): #pylint: disable=too-few-public-methods +class DrivenControl(QctrlObject): #pylint: disable=too-few-public-methods """ Creates a driven control. A driven is a set of segments made up of amplitude vectors and corresponding durations. @@ -136,7 +136,7 @@ def __init__(self, {'segments': self.segments}, extras={'segment_durations': self.segment_durations}) - super(DrivenControls, self).__init__( + super(DrivenControl, self).__init__( base_attributes=['segments', 'shape', 'scheme', 'name']) self.angles = self.amplitudes * self.segment_durations diff --git a/qctrlopencontrols/driven_controls/predefined.py b/qctrlopencontrols/driven_controls/predefined.py index c37f7d55..05e5c60f 100644 --- a/qctrlopencontrols/driven_controls/predefined.py +++ b/qctrlopencontrols/driven_controls/predefined.py @@ -24,16 +24,16 @@ import numpy as np from qctrlopencontrols.exceptions import ArgumentsValueError -from .driven_controls import DrivenControls +from .driven_control import DrivenControl from .constants import ( - PRIMITIVE, WIMPERIS_1, SOLOVAY_KITAEV_1, - WALSH_AMPLITUDE_MODULATED_FILTER_1, - COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE, - COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_SOLOVAY_KITAEV, - COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_WIMPERIS, - SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT, - CORPSE_IN_SCROFULOUS_PULSE) + PRIMITIVE, BB1, SK1, + WAMF1, + CORPSE, + CORPSE_IN_SK1, + CORPSE_IN_BB1, + SCROFULOUS, + CORPSE_IN_SCROFULOUS) def new_predefined_driven_control( @@ -74,28 +74,28 @@ def new_predefined_driven_control( # Raise error if the input driven_control_type is not known if scheme == PRIMITIVE: driven_control = new_primitive_control(**kwargs) - elif scheme == WIMPERIS_1: + elif scheme == BB1: driven_control = new_wimperis_1_control(**kwargs) - elif scheme == SOLOVAY_KITAEV_1: + elif scheme == SK1: driven_control = new_solovay_kitaev_1_control(**kwargs) - elif scheme == WALSH_AMPLITUDE_MODULATED_FILTER_1: + elif scheme == WAMF1: driven_control = new_walsh_amplitude_modulated_filter_1_control(**kwargs) - elif scheme == COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE: + elif scheme == CORPSE: driven_control = new_compensating_for_off_resonance_with_a_pulse_sequence_control( **kwargs) - elif scheme == COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_WIMPERIS: + elif scheme == CORPSE_IN_BB1: driven_control = \ new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( **kwargs) elif scheme == \ - COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE_WITH_SOLOVAY_KITAEV: + CORPSE_IN_SK1: driven_control = \ new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( **kwargs) - elif scheme == SHORT_COMPOSITE_ROTATION_FOR_UNDOING_LENGTH_OVER_AND_UNDER_SHOOT: + elif scheme == SCROFULOUS: driven_control = \ new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control(**kwargs) - elif scheme == CORPSE_IN_SCROFULOUS_PULSE: + elif scheme == CORPSE_IN_SCROFULOUS: driven_control = new_corpse_in_scrofulous_control(**kwargs) else: raise ArgumentsValueError( @@ -238,7 +238,7 @@ def new_primitive_control( 0., rabi_rotation / rabi_rate], ] - return DrivenControls(segments=segments, **kwargs) + return DrivenControl(segments=segments, **kwargs) def new_wimperis_1_control( @@ -278,7 +278,7 @@ def new_wimperis_1_control( segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, **kwargs) + return DrivenControl(segments=segments, **kwargs) def new_solovay_kitaev_1_control( @@ -318,7 +318,7 @@ def new_solovay_kitaev_1_control( segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, **kwargs) + return DrivenControl(segments=segments, **kwargs) def new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( # pylint: disable=invalid-name @@ -393,7 +393,7 @@ def degrees_to_radians(angle_in_degrees): segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, **kwargs) + return DrivenControl(segments=segments, **kwargs) def new_compensating_for_off_resonance_with_a_pulse_sequence_control( # pylint: disable=invalid-name @@ -432,7 +432,7 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_control( # pylint: segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, **kwargs) + return DrivenControl(segments=segments, **kwargs) def new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( # pylint: disable=invalid-name @@ -476,7 +476,7 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_contr segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, **kwargs) + return DrivenControl(segments=segments, **kwargs) def new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( # pylint: disable=invalid-name @@ -519,7 +519,7 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, **kwargs) + return DrivenControl(segments=segments, **kwargs) def new_corpse_in_scrofulous_control( # pylint: disable=invalid-name @@ -602,7 +602,7 @@ def degrees_to_radians(angle_in_degrees): segments = _derive_segments(total_angles, amplitude=rabi_rate) - return DrivenControls(segments=segments, **kwargs) + return DrivenControl(segments=segments, **kwargs) def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-name @@ -670,4 +670,4 @@ def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-n rabi_rate_plus * np.sin(azimuthal_angle), 0., time_segment]]) - return DrivenControls(segments=segments, **kwargs) + return DrivenControl(segments=segments, **kwargs) diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py b/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py index cfcf3c7e..95d1c0e8 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py @@ -22,7 +22,7 @@ from qctrlopencontrols.exceptions import ArgumentsValueError from qctrlopencontrols.driven_controls import ( - UPPER_BOUND_RABI_RATE, UPPER_BOUND_DETUNING_RATE, DrivenControls) + UPPER_BOUND_RABI_RATE, UPPER_BOUND_DETUNING_RATE, DrivenControl) def _check_valid_operation(rabi_rotations, detuning_rotations): @@ -260,7 +260,7 @@ def convert_dds_to_driven_controls( # the original sequence should be a free evolution control_segments = np.reshape( np.array([0., 0., 0., sequence_duration]), (1, 4)) - return DrivenControls(segments=control_segments, **kwargs) + return DrivenControl(segments=control_segments, **kwargs) control_segments = np.zeros((operations.shape[1]*2, 4)) pulse_segment_idx = 0 @@ -287,7 +287,7 @@ def convert_dds_to_driven_controls( # almost there; let us check if there is any segments with durations = 0 segment_durations = control_segments[:, 3] control_segments = control_segments[segment_durations != 0] - return DrivenControls(segments=control_segments, **kwargs) + return DrivenControl(segments=control_segments, **kwargs) if __name__ == '__main__': diff --git a/tests/test_driven_controls.py b/tests/test_driven_controls.py index 7cbcf2e4..ac35fb14 100644 --- a/tests/test_driven_controls.py +++ b/tests/test_driven_controls.py @@ -22,7 +22,7 @@ import pytest from qctrlopencontrols.exceptions import ArgumentsValueError -from qctrlopencontrols import DrivenControls +from qctrlopencontrols import DrivenControl from qctrlopencontrols.globals import CARTESIAN, CYLINDRICAL @@ -47,7 +47,7 @@ def test_driven_controls(): _name = 'driven_control' - driven_control = DrivenControls( + driven_control = DrivenControl( segments=_segments, name=_name) assert np.allclose(driven_control.segments, _segments) @@ -59,10 +59,10 @@ def test_driven_controls(): with pytest.raises(ArgumentsValueError): - _ = DrivenControls(segments=[[1e12, 0., 3, 1.]]) - _ = DrivenControls(segments=[[3., 0., 1e12, 1.]]) - _ = DrivenControls(segments=[[3., 0., 1e12, -1.]]) - _ = DrivenControls(segments=[[0., 0., 0., 0.]]) + _ = DrivenControl(segments=[[1e12, 0., 3, 1.]]) + _ = DrivenControl(segments=[[3., 0., 1e12, 1.]]) + _ = DrivenControl(segments=[[3., 0., 1e12, -1.]]) + _ = DrivenControl(segments=[[0., 0., 0., 0.]]) def test_control_export(): @@ -76,7 +76,7 @@ def test_control_export(): [0., 0., np.pi, 1.]] _name = 'driven_controls' - driven_control = DrivenControls( + driven_control = DrivenControl( segments=_segments, name=_name) _filename = 'driven_control_qctrl_cylindrical.csv' @@ -124,7 +124,7 @@ def test_plot_data(): y_amplitude = [0., 0., 0., 1.5, 1.5, 0., 0., 0.] z_amplitude = [0., 0., 0., 1.7, 1.7, 2.1, 2.1, 0.] times = [0., 0., 2., 2., 5., 5., 5.5, 5.5] - driven_control = DrivenControls(segments=segments) + driven_control = DrivenControl(segments=segments) plot_data = driven_control.get_plot_formatted_arrays(dimensionless=False) assert np.allclose(plot_data['times'], times) @@ -148,7 +148,7 @@ def test_dimensionless_segments(): amplitude_angle_segments = np.stack((_on_resonance_amplitudes, _azimuthal_angles, _detunings, _durations), axis=1) - driven_control = DrivenControls(segments=segments) + driven_control = DrivenControl(segments=segments) _max_rabi = driven_control.maximum_rabi_rate dimensionless_euclid = segments.copy() diff --git a/tests/test_predefined_driven_controls.py b/tests/test_predefined_driven_controls.py index 372871f5..d30ce6a8 100644 --- a/tests/test_predefined_driven_controls.py +++ b/tests/test_predefined_driven_controls.py @@ -25,7 +25,7 @@ from qctrlopencontrols.driven_controls import ( new_predefined_driven_control, new_primitive_control, new_wimperis_1_control, new_solovay_kitaev_1_control, - PRIMITIVE, WIMPERIS_1, SOLOVAY_KITAEV_1, COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE, + PRIMITIVE, BB1, SK1, CORPSE, new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control, new_corpse_in_scrofulous_control, new_compensating_for_off_resonance_with_a_pulse_sequence_control, @@ -111,7 +111,7 @@ def test_wimperis_1_control(): rabi_rotation=_rabi_rotation, azimuthal_angle=_azimuthal_angle, maximum_rabi_rate=1, - scheme=WIMPERIS_1 + scheme=BB1 ) assert np.allclose(wimperis_control_1.segments, _segments) @@ -139,7 +139,7 @@ def test_solovay_kitaev_1_control(): ) sk1_control_2 = new_predefined_driven_control( - scheme=SOLOVAY_KITAEV_1, + scheme=SK1, rabi_rotation=_rabi_rotation, azimuthal_angle=_azimuthal_angle, maximum_rabi_rate=1 @@ -258,7 +258,7 @@ def test_corpse_control(): ) corpse_control_2 = new_predefined_driven_control( - scheme=COMPENSATING_FOR_OFF_RESONANCE_WITH_A_PULSE_SEQUENCE, + scheme=CORPSE, rabi_rotation=_rabi_rotation, azimuthal_angle=_azimuthal_angle, maximum_rabi_rate=1 From 8f775d5e6532d2d3e5d0f8c90646ace7cadc210a Mon Sep 17 00:00:00 2001 From: virginia-m Date: Mon, 6 May 2019 17:51:47 +1000 Subject: [PATCH 30/51] change keywords of plot data dictionary --- examples/creating_a_driven_control.ipynb | 51 ++++++++++--------- .../driven_controls/driven_control.py | 21 ++++---- tests/test_driven_controls.py | 6 +-- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/examples/creating_a_driven_control.ipynb b/examples/creating_a_driven_control.ipynb index 60cdbed6..29db3d8a 100644 --- a/examples/creating_a_driven_control.ipynb +++ b/examples/creating_a_driven_control.ipynb @@ -22,13 +22,13 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", - "from qctrlopencontrols import new_predefined_driven_control, DrivenControls\n", + "from qctrlopencontrols import new_predefined_driven_control, DrivenControl\n", "\n", "plt.rcParams['axes.prop_cycle'] = plt.cycler(\n", " color=['#231e21', '#381e42', '#c80a64', '#680cea', '#5b02c1',\n", @@ -91,7 +91,6 @@ " rabi_rotation=np.pi,\n", " azimuthal_angle=0,\n", " maximum_rabi_rate=2 * np.pi,\n", - " shape='square',\n", " scheme='primitive',\n", " name='Primitive X-pi'\n", ")\n", @@ -100,7 +99,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -121,7 +120,7 @@ " rabi_rotation=np.pi,\n", " azimuthal_angle=0,\n", " maximum_rabi_rate=2 * np.pi,\n", - " scheme='wimperis_1',\n", + " scheme='BB1',\n", " name='BB1 X-pi'\n", ")\n", "print(bb1_x)" @@ -129,7 +128,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -150,7 +149,7 @@ " rabi_rotation=np.pi/2,\n", " azimuthal_angle=np.pi/2,\n", " maximum_rabi_rate=2 * np.pi,\n", - " scheme='wimperis_1',\n", + " scheme='BB1',\n", " name='BB1 Y-pi/2'\n", ")\n", "print(bb1_y)" @@ -158,7 +157,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -179,7 +178,7 @@ " rabi_rotation=np.pi/2,\n", " azimuthal_angle=np.pi/2,\n", " maximum_rabi_rate=2 * np.pi,\n", - " scheme='solovay_kitaev_1',\n", + " scheme='SK1',\n", " name='SK1 Y-pi/2'\n", ")\n", "print(sk1)" @@ -187,7 +186,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -208,7 +207,7 @@ " rabi_rotation=np.pi/2,\n", " azimuthal_angle=0,\n", " maximum_rabi_rate=2 * np.pi,\n", - " scheme='compensating_for_off_resonance_with_a_pulse_sequence',\n", + " scheme='CORPSE',\n", " name='CORPSE X-pi/2'\n", ")\n", "print(corpse)" @@ -225,7 +224,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -242,10 +241,10 @@ } ], "source": [ - "formatted_plot_data = bb1_x.get_plot_formatted_arrays()\n", - "x_amplitudes, y_amplitudes, z_amplitudes, times = (formatted_plot_data['x_amplitudes'],\n", - " formatted_plot_data['y_amplitudes'],\n", - " formatted_plot_data['z_amplitudes'],\n", + "formatted_plot_data = bb1_x.get_plot_formatted_arrays(coordinates='cartesian')\n", + "x_amplitudes, y_amplitudes, z_amplitudes, times = (formatted_plot_data['amplitude_x'],\n", + " formatted_plot_data['amplitude_y'],\n", + " formatted_plot_data['detuning'],\n", " formatted_plot_data['times'])\n", "# prepare the axes\n", "figure, (x_axis, y_axis, z_axis) = plt.subplots(1, 3, figsize=(20,5))\n", @@ -350,7 +349,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -375,15 +374,14 @@ "\n", "_segments = np.vstack([x_amplitudes, y_amplitudes, z_amplitudes, durations]).T\n", "_name = 'Custon Driven Control'\n", - "_shape = 'square'\n", "\n", - "custom_driven_control = DrivenControls(segments=_segments, name=_name, shape=_shape)\n", + "custom_driven_control = DrivenControl(segments=_segments, name=_name)\n", "\n", "## let us plot and verify\n", - "formatted_plot_data = custom_driven_control.get_plot_formatted_arrays()\n", - "x_amplitudes, y_amplitudes, z_amplitudes, times = (formatted_plot_data['x_amplitudes'],\n", - " formatted_plot_data['y_amplitudes'],\n", - " formatted_plot_data['z_amplitudes'],\n", + "formatted_plot_data = custom_driven_control.get_plot_formatted_arrays(coordinates='cartesian')\n", + "x_amplitudes, y_amplitudes, z_amplitudes, times = (formatted_plot_data['amplitude_x'],\n", + " formatted_plot_data['amplitude_y'],\n", + " formatted_plot_data['detuning'],\n", " formatted_plot_data['times'])\n", "# prepare the axes\n", "figure, (x_axis, y_axis, z_axis) = plt.subplots(1, 3, figsize=(20,5))\n", @@ -405,6 +403,13 @@ "\n", "plt.show()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index 42326870..700dff6f 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -354,11 +354,10 @@ def get_plot_formatted_arrays(self, coordinates=CARTESIAN, dimensionless=True): Returns ------- - tuple - Tuple made up of arrays for plotting formatted as - (amplitude_x,amplitude_y,amplitude_z,time) where: - - amplitude_k is the amplitude values in rad Hz for the k axis, or k Pauli matrix. - - times the time corresponding to each amplitude_k coordinate. + 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 ----- @@ -381,9 +380,9 @@ def get_plot_formatted_arrays(self, coordinates=CARTESIAN, dimensionless=True): if coordinates == CARTESIAN: (x_amplitudes, y_amplitudes, detunings, times) = plot_data plot_dictionary = { - 'x_amplitudes': x_amplitudes, - 'y_amplitudes': y_amplitudes, - 'z_amplitudes': detunings, + 'amplitude_x': x_amplitudes, + 'amplitude_y': y_amplitudes, + 'detuning': detunings, 'times': times } elif coordinates == CYLINDRICAL: @@ -393,9 +392,9 @@ def get_plot_formatted_arrays(self, coordinates=CARTESIAN, dimensionless=True): azimuthal_angles_plot = np.arctan2(y_plot, x_plot) amplitudes_plot = np.sqrt(np.abs(x_plot**2 + y_plot**2)) plot_dictionary = { - 'amplitudes': amplitudes_plot, - 'azimuthal_angles': azimuthal_angles_plot, - 'detunings': detunings, + 'rabi_rate': amplitudes_plot, + 'azimuthal_angle': azimuthal_angles_plot, + 'detuning': detunings, 'times': times } else: diff --git a/tests/test_driven_controls.py b/tests/test_driven_controls.py index ac35fb14..dbd3cd05 100644 --- a/tests/test_driven_controls.py +++ b/tests/test_driven_controls.py @@ -128,9 +128,9 @@ def test_plot_data(): plot_data = driven_control.get_plot_formatted_arrays(dimensionless=False) assert np.allclose(plot_data['times'], times) - assert np.allclose(plot_data['x_amplitudes'], x_amplitude) - assert np.allclose(plot_data['y_amplitudes'], y_amplitude) - assert np.allclose(plot_data['z_amplitudes'], z_amplitude) + assert np.allclose(plot_data['amplitude_x'], x_amplitude) + assert np.allclose(plot_data['amplitude_y'], y_amplitude) + assert np.allclose(plot_data['detuning'], z_amplitude) def test_dimensionless_segments(): """ From 96d84026313d0e515334967aabfccdc1fadd51c4 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Mon, 6 May 2019 18:02:02 +1000 Subject: [PATCH 31/51] change keyword arg dimensionless -> dimensionless_rabi_rate --- qctrlopencontrols/driven_controls/conversion.py | 12 ++++++------ .../driven_controls/driven_control.py | 16 ++++++++-------- tests/test_driven_controls.py | 10 +++++----- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/qctrlopencontrols/driven_controls/conversion.py b/qctrlopencontrols/driven_controls/conversion.py index 2b19d72f..1be90689 100644 --- a/qctrlopencontrols/driven_controls/conversion.py +++ b/qctrlopencontrols/driven_controls/conversion.py @@ -24,7 +24,7 @@ from qctrlopencontrols.exceptions import ArgumentsValueError def convert_to_standard_segments(transformed_segments, maximum_rabi_rate=None, - coordinates=CARTESIAN, dimensionless=True): + coordinates=CARTESIAN, dimensionless_rabi_rate=True): """Converts the dimensionless segments of any type into dimension-full Cartesian segments Parameters @@ -40,7 +40,7 @@ def convert_to_standard_segments(transformed_segments, maximum_rabi_rate=None, [amplitude_x, amplitude_y, amplitude_z,segment_duration] format if 'cylindrical' - the segments should be in [on_resonance_amplitude, azimuthal_angle, detuning, segment_duration] format - dimensionless : boolean + dimensionless_rabi_rate : boolean if True, identifies if the transformed_segments are dimensionless Returns @@ -62,14 +62,14 @@ def convert_to_standard_segments(transformed_segments, maximum_rabi_rate=None, transformed_segments = np.array(transformed_segments, dtype=np.float) segments_copy = np.array(transformed_segments, dtype=np.float) number_of_segments = len(segments_copy) - dimensionless = bool(dimensionless) + dimensionless_rabi_rate = bool(dimensionless_rabi_rate) # Raise error if dimensionless is True and maximum_rabi_rate is not a float - if dimensionless: + if dimensionless_rabi_rate: if maximum_rabi_rate is None: raise ArgumentsValueError('Maximum rate rate needs to be a valid float', {'maximum_rabi_rate': maximum_rabi_rate, - 'dimensionless': dimensionless}, + 'dimensionless_rabi_rate': dimensionless_rabi_rate}, extras={'segments': transformed_segments, 'number_of_segments': number_of_segments, 'coordinates': coordinates}) @@ -93,7 +93,7 @@ def convert_to_standard_segments(transformed_segments, maximum_rabi_rate=None, segments_copy[:, 1] = radius * sin_theta # if dimensionless, make the segments dimension-full - if dimensionless: + if dimensionless_rabi_rate: segments_copy[:, 0:2] = segments_copy[:, 0:2] * maximum_rabi_rate return segments_copy diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index 700dff6f..4c0b6aab 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -341,12 +341,12 @@ def export_to_file(self, filename=None, file_type=file_type, coordinates=coordinates) - def get_plot_formatted_arrays(self, coordinates=CARTESIAN, dimensionless=True): + def get_plot_formatted_arrays(self, coordinates=CARTESIAN, dimensionless_rabi_rate=True): """ Gets arrays for plotting a driven control. Parameters ---------- - dimensionless: boolean + 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 @@ -372,8 +372,8 @@ def get_plot_formatted_arrays(self, coordinates=CARTESIAN, dimensionless=True): Raised when an argument is invalid. """ - plot_segments = self.get_transformed_segments(coordinates=CARTESIAN, - dimensionless=dimensionless) + plot_segments = self.get_transformed_segments( + coordinates=CARTESIAN, dimensionless_rabi_rate=dimensionless_rabi_rate) plot_data = get_plot_data_from_segments(plot_segments) @@ -404,7 +404,7 @@ def get_plot_formatted_arrays(self, coordinates=CARTESIAN, dimensionless=True): return plot_dictionary - def get_transformed_segments(self, coordinates=CARTESIAN, dimensionless=True): + def get_transformed_segments(self, coordinates=CARTESIAN, dimensionless_rabi_rate=True): """ Function that transforms standard dimension-full segments of the driven control into dimensionless segments @@ -414,7 +414,7 @@ def get_transformed_segments(self, coordinates=CARTESIAN, dimensionless=True): coordinates : string Indicated the type of segments that need to be transformed can be 'cartesian' or 'cylindrical' or 'polar'. - dimensionless : boolean + dimensionless_rabi_rate : boolean If True, calculates the dimensionless segments Returns @@ -428,10 +428,10 @@ def get_transformed_segments(self, coordinates=CARTESIAN, dimensionless=True): ArgumentsValueError Raised when an argument is invalid. """ - dimensionless = bool(dimensionless) + dimensionless_rabi_rate = bool(dimensionless_rabi_rate) transformed_segments = self.segments.copy() - if dimensionless: + if dimensionless_rabi_rate: transformed_segments[:, 0:2] = transformed_segments[:, 0:2] / self.maximum_rabi_rate if coordinates == CARTESIAN: diff --git a/tests/test_driven_controls.py b/tests/test_driven_controls.py index dbd3cd05..bcb38288 100644 --- a/tests/test_driven_controls.py +++ b/tests/test_driven_controls.py @@ -125,7 +125,7 @@ def test_plot_data(): z_amplitude = [0., 0., 0., 1.7, 1.7, 2.1, 2.1, 0.] times = [0., 0., 2., 2., 5., 5., 5.5, 5.5] driven_control = DrivenControl(segments=segments) - plot_data = driven_control.get_plot_formatted_arrays(dimensionless=False) + plot_data = driven_control.get_plot_formatted_arrays(dimensionless_rabi_rate=False) assert np.allclose(plot_data['times'], times) assert np.allclose(plot_data['amplitude_x'], x_amplitude) @@ -160,21 +160,21 @@ def test_dimensionless_segments(): dimensionless_cylinder[:, 0] = dimensionless_cylinder[:, 0] / _max_rabi transformed_euclidean = driven_control.get_transformed_segments(coordinates=CARTESIAN, - dimensionless=False) + dimensionless_rabi_rate=False) assert np.allclose(segments, transformed_euclidean) transformed_euclidean = driven_control.get_transformed_segments(coordinates=CARTESIAN, - dimensionless=True) + dimensionless_rabi_rate=True) assert np.allclose(dimensionless_euclid, transformed_euclidean) transformed_cylindrical = driven_control.get_transformed_segments(coordinates=CYLINDRICAL, - dimensionless=False) + dimensionless_rabi_rate=False) assert np.allclose(amplitude_angle_segments, transformed_cylindrical) transformed_cylindrical = driven_control.get_transformed_segments(coordinates=CYLINDRICAL, - dimensionless=True) + dimensionless_rabi_rate=True) assert np.allclose(amplitude_angle_segments, transformed_cylindrical) From d75d4634c8d6d02b090d824c210d0223be584fa9 Mon Sep 17 00:00:00 2001 From: Rajib Chakravorty Date: Thu, 9 May 2019 10:51:18 +1000 Subject: [PATCH 32/51] driven control constructor changed --- .../driven_controls/driven_control.py | 109 +++++++++++++++--- tests/test_driven_controls.py | 48 +++++--- 2 files changed, 125 insertions(+), 32 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index 4c0b6aab..d5ec8a3f 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -86,16 +86,20 @@ class DrivenControl(QctrlObject): #pylint: disable=too-few-public-methods Parameters ---------- - segments : list, optional - Defaults to None. A list of amplitude vector components - and durations. Each element of the list should be formatted as - [amplitude_x,amplitude_y,amplitude_z,duration] where amplitude_i is the angular - rabi frequency to be multiplied by the - corresponding pauli matrix, i.e. amplitude_x would correspond to sigma_x. - The duration is the time of that segment. - If None, defaults to a square pi pulse [[np.pi, 0, 0, 1], ]. + rabi_rates : numpy.ndarray, optional + 1-D array of size nx1 where n is number of segments; + Each entry is the rabi rate for the segment. Defaults to None + azimuthal_angles : numpy.ndarray, optional + 1-D array of size nx1 where n is the number of segments; + Each entry is the azimuthal angle for the segment; Defaults to None + detunings : numpy.ndarray, optional + 1-D array of size nx1 where n is the number of segments; + Each entry is the detuning angle for the segment; Defaults to None + durations : numpy.ndarray, optional + 1-D array of size nx1 where n is the number of segments; + Each entry is the duration of the segment (in seconds); Defaults to None name : string, optional - Defaults to None. An optional string to name the driven control. + An optional string to name the driven control. Defaults to None. Raises ------ @@ -104,17 +108,88 @@ class DrivenControl(QctrlObject): #pylint: disable=too-few-public-methods """ def __init__(self, - segments=None, + 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) - if segments is None: - segments = [[np.pi, 0, 0, 1], ] + check_none_values = [(rabi_rates is None), (azimuthal_angles is None), + (detunings is None), (durations is None)] - self.segments = np.array(segments, dtype=np.float) + all_are_none = all(value is None 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]: + input_array_lengths.append(len(rabi_rates)) + + if not check_none_values[1]: + input_array_lengths.append(len(azimuthal_angles)) + + if not check_none_values[2]: + input_array_lengths.append(len(detunings)) + + if not check_none_values[3]: + 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', + {'len(rabi_rates)': len(rabi_rates), + 'len(azimuthal_angles)': len(azimuthal_angles), + 'len(detunings)': len(detunings), + 'len(durations)': len(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,)) + + # 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': durations}) + + if len(rabi_rates.shape) == 1: + rabi_rates = rabi_rates[:, np.newaxis] + if len(azimuthal_angles.shape) == 1: + azimuthal_angles = azimuthal_angles[:, np.newaxis] + if len(detunings.shape) == 1: + detunings = detunings[:, np.newaxis] + if len(durations.shape) == 1: + durations = durations[:, np.newaxis] + + self.segments = np.hstack((rabi_rates * np.cos(azimuthal_angles), + rabi_rates * np.sin(azimuthal_angles), + detunings, + durations)) 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).', @@ -130,14 +205,10 @@ def __init__(self, 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 driven control segments must all be greater' - + ' than zero.', - {'segments': self.segments}, - extras={'segment_durations': self.segment_durations}) super(DrivenControl, self).__init__( - base_attributes=['segments', 'shape', 'scheme', 'name']) + base_attributes=['rabi_rates', 'azimuthal_angles', 'detunings', + 'durations', 'name']) self.angles = self.amplitudes * self.segment_durations self.directions = np.array([self.segments[i, 0:3] / self.amplitudes[i] diff --git a/tests/test_driven_controls.py b/tests/test_driven_controls.py index bcb38288..152f9bb6 100644 --- a/tests/test_driven_controls.py +++ b/tests/test_driven_controls.py @@ -41,14 +41,18 @@ def test_driven_controls(): """Tests the construction of driven controls """ - _segments = [[np.pi, 0., 0., 1.], - [np.pi, np.pi/2, 0., 2.], - [0., 0., np.pi, 3.]] - + _rabi_rates = np.array([np.pi, 0., 0.]) + _azimuthal_angles = np.array([0., np.pi/2, np.pi]) + _detunings = np.array([0., 0., np.pi]) + _durations = np.array([1., 2., 3.]) _name = 'driven_control' + _segments = np.hstack((_rabi_rates, _azimuthal_angles, _detunings, _durations)) + driven_control = DrivenControl( - segments=_segments, name=_name) + rabi_rates = _rabi_rates, azimuthal_angles = _azimuthal_angles, + detunings = _detunings, durations = _durations, + name=_name) assert np.allclose(driven_control.segments, _segments) assert driven_control.number_of_segments == 3 @@ -71,13 +75,24 @@ def test_control_export(): """ _maximum_rabi_rate = 5*np.pi - _segments = [[_maximum_rabi_rate*np.cos(np.pi/4), _maximum_rabi_rate*np.sin(np.pi/4), 0., 2.], - [_maximum_rabi_rate*np.cos(np.pi/3), _maximum_rabi_rate*np.sin(np.pi/3), 0., 2.], - [0., 0., np.pi, 1.]] + _rabi_rates = np.array([_maximum_rabi_rate*np.cos(np.pi/4), + _maximum_rabi_rate * np.cos(np.pi / 3), + 0.]) + _azimuthal_angles = np.array([_maximum_rabi_rate*np.sin(np.pi/4), + _maximum_rabi_rate * np.sin(np.pi / 3), + 0.]) + _detunings = np.array([0., 0., np.pi]) + _durations = np.array([2., 2., 1.]) + #_segments = np.array( + # [[_maximum_rabi_rate*np.cos(np.pi/4), _maximum_rabi_rate*np.sin(np.pi/4), 0., 2.], + # [_maximum_rabi_rate*np.cos(np.pi/3), _maximum_rabi_rate*np.sin(np.pi/3), 0., 2.], + # [0., 0., np.pi, 1.]]) + _name = 'driven_controls' driven_control = DrivenControl( - segments=_segments, name=_name) + rabi_rates=_rabi_rates, azimuthal_angles=_azimuthal_angles, + detunings=_detunings, durations=_durations, name=_name) _filename = 'driven_control_qctrl_cylindrical.csv' driven_control.export_to_file( @@ -116,15 +131,18 @@ def test_plot_data(): """ Test the plot data produced for a driven control. """ + _rabi_rates = np.array([1., 0. 1.]) + _azimuthal_angles = np.array([0., 1.5, 0.]) + _detunings = np.array([0., 1.7, 2.1]) + _durations = np.array([2., 3., 0.5]) - segments = [[1., 0., 0., 2.], - [0., 1.5, 1.7, 3.], - [1., 0., 2.1, 0.5]] x_amplitude = [0., 1., 1., 0., 0., 1., 1., 0.] y_amplitude = [0., 0., 0., 1.5, 1.5, 0., 0., 0.] z_amplitude = [0., 0., 0., 1.7, 1.7, 2.1, 2.1, 0.] times = [0., 0., 2., 2., 5., 5., 5.5, 5.5] - driven_control = DrivenControl(segments=segments) + driven_control = DrivenControl( + rabi_rates=_rabi_rates, azimuthal_angles=_azimuthal_angles, + detunings=_detunings, durations=_durations) plot_data = driven_control.get_plot_formatted_arrays(dimensionless_rabi_rate=False) assert np.allclose(plot_data['times'], times) @@ -136,6 +154,10 @@ def test_dimensionless_segments(): """ Test the dimensionless amplitude and angle segments generated """ + _rabi_rates = np.array([1., 0., 1. / np.sqrt(2.)]) + _azimuthal_angles = np.array([0., 1., 0. ]) + _detunings = np.array([0., 0., 1. / np.sqrt(2.)]) + _durations = np.array([]) segments = [[1., 0., 0., np.pi / 2], [0., 1., 0., np.pi / 2], [1. / np.sqrt(2.), 0., 1. / np.sqrt(2.), np.pi / 2]] From 43cf7d277a9f5aa36007440610f00284a2dfc447 Mon Sep 17 00:00:00 2001 From: Rajib Chakravorty Date: Thu, 9 May 2019 10:53:22 +1000 Subject: [PATCH 33/51] tests reverted --- tests/test_driven_controls.py | 48 ++++++++++------------------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/tests/test_driven_controls.py b/tests/test_driven_controls.py index 152f9bb6..bcb38288 100644 --- a/tests/test_driven_controls.py +++ b/tests/test_driven_controls.py @@ -41,18 +41,14 @@ def test_driven_controls(): """Tests the construction of driven controls """ - _rabi_rates = np.array([np.pi, 0., 0.]) - _azimuthal_angles = np.array([0., np.pi/2, np.pi]) - _detunings = np.array([0., 0., np.pi]) - _durations = np.array([1., 2., 3.]) - _name = 'driven_control' + _segments = [[np.pi, 0., 0., 1.], + [np.pi, np.pi/2, 0., 2.], + [0., 0., np.pi, 3.]] - _segments = np.hstack((_rabi_rates, _azimuthal_angles, _detunings, _durations)) + _name = 'driven_control' driven_control = DrivenControl( - rabi_rates = _rabi_rates, azimuthal_angles = _azimuthal_angles, - detunings = _detunings, durations = _durations, - name=_name) + segments=_segments, name=_name) assert np.allclose(driven_control.segments, _segments) assert driven_control.number_of_segments == 3 @@ -75,24 +71,13 @@ def test_control_export(): """ _maximum_rabi_rate = 5*np.pi - _rabi_rates = np.array([_maximum_rabi_rate*np.cos(np.pi/4), - _maximum_rabi_rate * np.cos(np.pi / 3), - 0.]) - _azimuthal_angles = np.array([_maximum_rabi_rate*np.sin(np.pi/4), - _maximum_rabi_rate * np.sin(np.pi / 3), - 0.]) - _detunings = np.array([0., 0., np.pi]) - _durations = np.array([2., 2., 1.]) - #_segments = np.array( - # [[_maximum_rabi_rate*np.cos(np.pi/4), _maximum_rabi_rate*np.sin(np.pi/4), 0., 2.], - # [_maximum_rabi_rate*np.cos(np.pi/3), _maximum_rabi_rate*np.sin(np.pi/3), 0., 2.], - # [0., 0., np.pi, 1.]]) - + _segments = [[_maximum_rabi_rate*np.cos(np.pi/4), _maximum_rabi_rate*np.sin(np.pi/4), 0., 2.], + [_maximum_rabi_rate*np.cos(np.pi/3), _maximum_rabi_rate*np.sin(np.pi/3), 0., 2.], + [0., 0., np.pi, 1.]] _name = 'driven_controls' driven_control = DrivenControl( - rabi_rates=_rabi_rates, azimuthal_angles=_azimuthal_angles, - detunings=_detunings, durations=_durations, name=_name) + segments=_segments, name=_name) _filename = 'driven_control_qctrl_cylindrical.csv' driven_control.export_to_file( @@ -131,18 +116,15 @@ def test_plot_data(): """ Test the plot data produced for a driven control. """ - _rabi_rates = np.array([1., 0. 1.]) - _azimuthal_angles = np.array([0., 1.5, 0.]) - _detunings = np.array([0., 1.7, 2.1]) - _durations = np.array([2., 3., 0.5]) + segments = [[1., 0., 0., 2.], + [0., 1.5, 1.7, 3.], + [1., 0., 2.1, 0.5]] x_amplitude = [0., 1., 1., 0., 0., 1., 1., 0.] y_amplitude = [0., 0., 0., 1.5, 1.5, 0., 0., 0.] z_amplitude = [0., 0., 0., 1.7, 1.7, 2.1, 2.1, 0.] times = [0., 0., 2., 2., 5., 5., 5.5, 5.5] - driven_control = DrivenControl( - rabi_rates=_rabi_rates, azimuthal_angles=_azimuthal_angles, - detunings=_detunings, durations=_durations) + driven_control = DrivenControl(segments=segments) plot_data = driven_control.get_plot_formatted_arrays(dimensionless_rabi_rate=False) assert np.allclose(plot_data['times'], times) @@ -154,10 +136,6 @@ def test_dimensionless_segments(): """ Test the dimensionless amplitude and angle segments generated """ - _rabi_rates = np.array([1., 0., 1. / np.sqrt(2.)]) - _azimuthal_angles = np.array([0., 1., 0. ]) - _detunings = np.array([0., 0., 1. / np.sqrt(2.)]) - _durations = np.array([]) segments = [[1., 0., 0., np.pi / 2], [0., 1., 0., np.pi / 2], [1. / np.sqrt(2.), 0., 1. / np.sqrt(2.), np.pi / 2]] From 5dfaae991bedf76c2ea3edcfb8d83fa2899c8435 Mon Sep 17 00:00:00 2001 From: Rajib Chakravorty Date: Thu, 9 May 2019 11:04:57 +1000 Subject: [PATCH 34/51] explicit conversion of inputs to numpy array --- qctrlopencontrols/driven_controls/driven_control.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index d5ec8a3f..0384c0f0 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -162,6 +162,12 @@ def __init__(self, if check_none_values[3]: durations = np.ones((valid_input_length,)) + # time to convert to numpy array + rabi_rates = np.array(rabi_rates) + azimuthal_angles = np.array(azimuthal_angles) + detunings = np.array(detunings) + durations = np.array(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.', From 9dafdcef3ac44c70171189655987836d5d6d1f50 Mon Sep 17 00:00:00 2001 From: Rajib Chakravorty Date: Thu, 9 May 2019 12:03:07 +1000 Subject: [PATCH 35/51] attributes are removed or changed to property --- .../driven_controls/driven_control.py | 240 +++++++++++------- 1 file changed, 143 insertions(+), 97 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index 0384c0f0..7983385e 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -147,10 +147,10 @@ def __init__(self, if max(input_array_lengths) != min(input_array_lengths): raise ArgumentsValueError('Rabi rates, Azimuthal angles, Detunings and Durations ' 'must be of same length', - {'len(rabi_rates)': len(rabi_rates), - 'len(azimuthal_angles)': len(azimuthal_angles), - 'len(detunings)': len(detunings), - 'len(durations)': len(durations)}) + {'rabi_rates': rabi_rates, + 'azimuthal_angles': azimuthal_angles, + 'detunings': detunings, + 'durations': durations}) valid_input_length = max(input_array_lengths) if check_none_values[0]: @@ -163,10 +163,15 @@ def __init__(self, durations = np.ones((valid_input_length,)) # time to convert to numpy array - rabi_rates = np.array(rabi_rates) - azimuthal_angles = np.array(azimuthal_angles) - detunings = np.array(detunings) - durations = np.array(durations) + rabi_rates = np.array(rabi_rates, dtype=np.float) + azimuthal_angles = np.array(azimuthal_angles, dtype=np.float) + detunings = np.array(detunings, dtype=np.float) + durations = np.array(durations, dtype=np.float) + + 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.): @@ -183,24 +188,7 @@ def __init__(self, + ' than zero.', {'durations': durations}) - if len(rabi_rates.shape) == 1: - rabi_rates = rabi_rates[:, np.newaxis] - if len(azimuthal_angles.shape) == 1: - azimuthal_angles = azimuthal_angles[:, np.newaxis] - if len(detunings.shape) == 1: - detunings = detunings[:, np.newaxis] - if len(durations.shape) == 1: - durations = durations[:, np.newaxis] - - self.segments = np.hstack((rabi_rates * np.cos(azimuthal_angles), - rabi_rates * np.sin(azimuthal_angles), - detunings, - durations)) - 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}) + 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:' @@ -208,31 +196,10 @@ def __init__(self, {'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] - super(DrivenControl, self).__init__( base_attributes=['rabi_rates', 'azimuthal_angles', 'detunings', 'durations', '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: ' @@ -259,6 +226,135 @@ def __init__(self, {'segments': self.segments}, extras={'minimum_duration'}) + @property + def maximum_rabi_rate(self): + """Returns the maximum rabi rate of the control + + Returns + ------- + float + The maximum rabi rate of the control + """ + + return np.amax(self.rabi_rates) + + @property + def maximum_detuning_rate(self): + """Returns the maximum detuning rate of the control + + Returns + ------- + float + The maximum detuning rate of the control + """ + return np.amax(self.detunings) + + @property + def amplitude_x(self): + """Return the X-Amplitude + + Returns + ------- + numpy.ndarray + X-Amplitude of each segment + """ + + return (self.rabi_rates * np.cos(self.azimuthal_angles))/self.maximum_rabi_rate + + @property + def amplitude_y(self): + """Return the Y-Amplitude + + Returns + ------- + numpy.ndarray + Y-Amplitude of each segment + """ + + return (self.rabi_rates * np.sin(self.azimuthal_angles))/self.maximum_rabi_rate + + @property + def angles(self): + """Returns the angles + + Returns + ------- + numpy.darray + Angles as 1-D array of floats + """ + + amplitudes = np.sqrt(self.amplitude_x ** 2 + + self.amplitude_y ** 2 + + self.detunings ** 2) + angles = amplitudes * self.durations + + return angles + + @property + def directions(self): + + """Returns the directions + + Returns + ------- + numpy.ndarray + Directions as 1-D array of floats + """ + amplitudes = np.sqrt(self.amplitude_x ** 2 + + self.amplitude_y ** 2 + + self.detunings ** 2) + normalized_amplitude_x = self.amplitude_x/amplitudes + normalized_amplitude_y = self.amplitude_y/amplitudes + normalized_detunings = self.detunings/amplitudes + + normalized_amplitudes = np.hstack((normalized_amplitude_x[:, np.newaxis], + normalized_amplitude_y[:, np.newaxis], + normalized_detunings[:, np.newaxis])) + + directions = np.array([normalized_amplitudes if amplitudes[i] != 0. else + np.zeros([3, ]) for i in range(self.number_of_segments)]) + + return directions + + @property + def times(self): + """Returns the time of each segment within the duration + of the control + + Returns + ------ + numpy.ndarray + Segment times as 1-D array of floats + """ + + return np.insert(np.cumsum(self.durations), 0, 0.) + + @property + def maximum_duration(self): + """Returns the maximum duration of all the control segments + + Returns + ------- + float + The maximum duration of all the control segments + """ + + return np.amax(self.durations) + + @property + def minimum_duration(self): + """Returns the minimum duration of all the control segments + + Returns + ------- + float + The minimum duration of all the controls segments + """ + + return np.amin(self.durations) + + + def _qctrl_expanded_export_content(self, file_type, coordinates): """Private method to prepare the content to be saved in Q-CTRL expanded format @@ -481,56 +577,6 @@ def get_plot_formatted_arrays(self, coordinates=CARTESIAN, dimensionless_rabi_ra return plot_dictionary - def get_transformed_segments(self, coordinates=CARTESIAN, dimensionless_rabi_rate=True): - """ - Function that transforms standard dimension-full segments of the - driven control into dimensionless segments - - Parameters - ---------- - coordinates : string - Indicated the type of segments that need to be transformed can be 'cartesian' or - 'cylindrical' or 'polar'. - dimensionless_rabi_rate : boolean - If True, calculates the dimensionless segments - - Returns - ------- - numpy.ndarray - if dimensionless is True, returns the dimensionless segments of the driven control; - otherwise returns the segments - - Raises - ------ - ArgumentsValueError - Raised when an argument is invalid. - """ - dimensionless_rabi_rate = bool(dimensionless_rabi_rate) - transformed_segments = self.segments.copy() - - if dimensionless_rabi_rate: - transformed_segments[:, 0:2] = transformed_segments[:, 0:2] / self.maximum_rabi_rate - - if coordinates == CARTESIAN: - pass - elif coordinates == CYLINDRICAL: - - temp_amplitudes = np.sqrt(np.abs( - transformed_segments[:, 0]**2 + transformed_segments[:, 1]**2)) - temp_azimuthal_angles = np.arctan2( - transformed_segments[:, 1], transformed_segments[:, 0]) - transformed_segments = np.stack( - (temp_amplitudes, - temp_azimuthal_angles, - transformed_segments[:, 2], - transformed_segments[:, 3]), - axis=1) - else: - raise ArgumentsValueError('Unsupported coordinates provided: ', - arguments={'coordinates': coordinates}) - - return transformed_segments - def __str__(self): """Prepares a friendly string format for a Driven Control """ From 8ac877a6c299a68644e488f505b16dea403c285d Mon Sep 17 00:00:00 2001 From: Rajib Chakravorty Date: Thu, 9 May 2019 12:23:58 +1000 Subject: [PATCH 36/51] maximum detuning rate changed to maximum_detuning --- qctrlopencontrols/driven_controls/driven_control.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index 7983385e..66e1bb6e 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -239,13 +239,13 @@ def maximum_rabi_rate(self): return np.amax(self.rabi_rates) @property - def maximum_detuning_rate(self): - """Returns the maximum detuning rate of the control + def maximum_detuning(self): + """Returns the maximum detuning of the control Returns ------- float - The maximum detuning rate of the control + The maximum detuning of the control """ return np.amax(self.detunings) From 97b6f21d5884bc2e2dd790dfa472051c896564e2 Mon Sep 17 00:00:00 2001 From: Rajib Chakravorty Date: Thu, 9 May 2019 12:30:53 +1000 Subject: [PATCH 37/51] segements removed from error messages --- .../driven_controls/driven_control.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index 66e1bb6e..c37ca1c8 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -186,15 +186,14 @@ def __init__(self, if np.any(durations <= 0): raise ArgumentsValueError('Duration of driven control segments must all be greater' + ' than zero.', - {'durations': durations}) + {'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), - {'segments': self.segments}, - extras={'number_of_segments': self.number_of_segments}) + {'number_of_segments': self.number_of_segments}) super(DrivenControl, self).__init__( base_attributes=['rabi_rates', 'azimuthal_angles', 'detunings', @@ -204,27 +203,23 @@ def __init__(self, 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}) + {'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}) + {'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}) + {'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'}) + {'minimum_duration': self.minimum_duration}) @property def maximum_rabi_rate(self): From 1c1b9b856224bc215f47c37a9b34c0af134172cd Mon Sep 17 00:00:00 2001 From: Rajib Chakravorty Date: Thu, 9 May 2019 14:51:00 +1000 Subject: [PATCH 38/51] partial linting --- .../driven_controls/driven_control.py | 57 +++++++++---------- .../driven_controls.py | 44 ++++++++------ 2 files changed, 55 insertions(+), 46 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index c37ca1c8..9b1288ad 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -30,6 +30,7 @@ UPPER_BOUND_SEGMENTS, UPPER_BOUND_RABI_RATE, UPPER_BOUND_DETUNING_RATE, UPPER_BOUND_DURATION, LOWER_BOUND_DURATION) + def get_plot_data_from_segments(segments): """ Generates arrays that can be used to produce a plot representing the shape of the driven control @@ -78,7 +79,6 @@ def get_plot_data_from_segments(segments): np.array(plot_time)) - class DrivenControl(QctrlObject): #pylint: disable=too-few-public-methods """ Creates a driven control. A driven is a set of segments made up of amplitude vectors @@ -132,15 +132,19 @@ def __init__(self, # some may be None while others are not input_array_lengths = [] if not check_none_values[0]: - input_array_lengths.append(len(rabi_rates)) + 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 @@ -162,12 +166,6 @@ def __init__(self, if check_none_values[3]: durations = np.ones((valid_input_length,)) - # time to convert to numpy array - rabi_rates = np.array(rabi_rates, dtype=np.float) - azimuthal_angles = np.array(azimuthal_angles, dtype=np.float) - detunings = np.array(detunings, dtype=np.float) - durations = np.array(durations, dtype=np.float) - self.rabi_rates = rabi_rates self.azimuthal_angles = azimuthal_angles self.detunings = detunings @@ -303,8 +301,8 @@ def directions(self): normalized_detunings = self.detunings/amplitudes normalized_amplitudes = np.hstack((normalized_amplitude_x[:, np.newaxis], - normalized_amplitude_y[:, np.newaxis], - normalized_detunings[:, np.newaxis])) + normalized_amplitude_y[:, np.newaxis], + normalized_detunings[:, np.newaxis])) directions = np.array([normalized_amplitudes if amplitudes[i] != 0. else np.zeros([3, ]) for i in range(self.number_of_segments)]) @@ -348,8 +346,6 @@ def minimum_duration(self): return np.amin(self.durations) - - def _qctrl_expanded_export_content(self, file_type, coordinates): """Private method to prepare the content to be saved in Q-CTRL expanded format @@ -369,18 +365,19 @@ def _qctrl_expanded_export_content(self, file_type, coordinates): """ control_info = None + amplitude_x = self.amplitude_x + amplitude_y = self.amplitude_y if coordinates == CARTESIAN: - if file_type == CSV: control_info = list() control_info.append('amplitude_x,amplitude_y,detuning,duration,maximum_rabi_rate') - for segment_idx in range(self.segments.shape[0]): + for segment_idx in range(self.number_of_segments): control_info.append('{},{},{},{},{}'.format( - self.segments[segment_idx, 0] / self.maximum_rabi_rate, - self.segments[segment_idx, 1] / self.maximum_rabi_rate, - self.segments[segment_idx, 2], - self.segments[segment_idx, 3], + amplitude_x[segment_idx], + amplitude_y[segment_idx], + self.detunings[segment_idx], + self.durations[segment_idx], self.maximum_rabi_rate )) else: @@ -388,23 +385,23 @@ def _qctrl_expanded_export_content(self, file_type, coordinates): if self.name is not None: control_info['name'] = self.name control_info['maximum_rabi_rate'] = self.maximum_rabi_rate - control_info['amplitude_x'] = list(self.segments[:, 0]/self.maximum_rabi_rate) - control_info['amplitude_y'] = list(self.segments[:, 1] / self.maximum_rabi_rate) - control_info['detuning'] = list(self.segments[:, 2]) - control_info['duration'] = list(self.segments[:, 3]) + control_info['amplitude_x'] = list(amplitude_x) + control_info['amplitude_y'] = list(amplitude_y) + control_info['detuning'] = list(self.detunings) + control_info['duration'] = list(self.durations) else: if file_type == CSV: control_info = list() control_info.append('rabi_rate,azimuthal_angle,detuning,duration,maximum_rabi_rate') - for segment_idx in range(self.segments.shape[0]): + for segment_idx in range(self.number_of_segments): control_info.append('{},{},{},{},{}'.format( self.rabi_rates[segment_idx]/self.maximum_rabi_rate, - np.arctan2(self.segments[segment_idx, 1], - self.segments[segment_idx, 0]), - self.segments[segment_idx, 2], - self.segments[segment_idx, 3], + np.arctan2(amplitude_y[segment_idx], + amplitude_x[segment_idx]), + self.detunings[segment_idx], + self.durations[segment_idx], self.maximum_rabi_rate )) @@ -415,9 +412,9 @@ def _qctrl_expanded_export_content(self, file_type, coordinates): control_info['maximum_rabi_rate'] = self.maximum_rabi_rate control_info['rabi_rates'] = list(self.rabi_rates / self.maximum_rabi_rate) control_info['azimuthal_angles'] = list(np.arctan2( - self.segments[:, 1], self.segments[:, 0])) - control_info['detuning'] = list(self.segments[:, 2]) - control_info['duration'] = list(self.segments[:, 3]) + amplitude_y, amplitude_x)) + control_info['detuning'] = list(self.detunings) + control_info['duration'] = list(self.durations) return control_info diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py b/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py index 95d1c0e8..9aaeb6bf 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py @@ -262,32 +262,44 @@ def convert_dds_to_driven_controls( np.array([0., 0., 0., sequence_duration]), (1, 4)) return DrivenControl(segments=control_segments, **kwargs) - control_segments = np.zeros((operations.shape[1]*2, 4)) + 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_segments[pulse_segment_idx, 0:3] = [ - maximum_rabi_rate * np.cos(operations[2, op_idx]), - maximum_rabi_rate * np.sin(operations[2, op_idx]), - 0.] - control_segments[pulse_segment_idx, 3] = (pulse_start_ends[op_idx, 1] - - pulse_start_ends[op_idx, 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_segments[pulse_segment_idx, 0:3] = [0., 0., operations[3, op_idx]] - control_segments[pulse_segment_idx, 3] = (pulse_start_ends[op_idx, 1] - - pulse_start_ends[op_idx, 0]) + 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_segments[pulse_segment_idx+1, 0:3] = np.array([0, 0, 0]) - control_segments[pulse_segment_idx+1, 3] = (pulse_start_ends[op_idx+1, 0] - - pulse_start_ends[op_idx, 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 - segment_durations = control_segments[:, 3] - control_segments = control_segments[segment_durations != 0] - return DrivenControl(segments=control_segments, **kwargs) + 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) if __name__ == '__main__': From 2d0d8acd5506162a097a4284f0539a7e7a72f33b Mon Sep 17 00:00:00 2001 From: virginia-m Date: Thu, 9 May 2019 15:00:33 +1000 Subject: [PATCH 39/51] fix export and plot methods for new attributes --- .../driven_controls/driven_control.py | 67 ++++++++++--------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index c37ca1c8..e319435e 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -367,7 +367,6 @@ def _qctrl_expanded_export_content(self, file_type, coordinates): list or dict Based on file_type; list if 'csv', dict if 'json' """ - control_info = None if coordinates == CARTESIAN: @@ -375,12 +374,12 @@ def _qctrl_expanded_export_content(self, file_type, coordinates): control_info = list() control_info.append('amplitude_x,amplitude_y,detuning,duration,maximum_rabi_rate') - for segment_idx in range(self.segments.shape[0]): + for segment_idx in range(self.number_of_segments): control_info.append('{},{},{},{},{}'.format( - self.segments[segment_idx, 0] / self.maximum_rabi_rate, - self.segments[segment_idx, 1] / self.maximum_rabi_rate, - self.segments[segment_idx, 2], - self.segments[segment_idx, 3], + self.amplitude_x / self.maximum_rabi_rate, + self.amplitude_y / self.maximum_rabi_rate, + self.detunings, + self.durations, self.maximum_rabi_rate )) else: @@ -388,23 +387,23 @@ def _qctrl_expanded_export_content(self, file_type, coordinates): if self.name is not None: control_info['name'] = self.name control_info['maximum_rabi_rate'] = self.maximum_rabi_rate - control_info['amplitude_x'] = list(self.segments[:, 0]/self.maximum_rabi_rate) - control_info['amplitude_y'] = list(self.segments[:, 1] / self.maximum_rabi_rate) - control_info['detuning'] = list(self.segments[:, 2]) - control_info['duration'] = list(self.segments[:, 3]) + control_info['amplitude_x'] = list(self.amplitude_x / self.maximum_rabi_rate) + control_info['amplitude_y'] = list(self.amplitude_y / self.maximum_rabi_rate) + control_info['detuning'] = list(self.detunings) + control_info['duration'] = list(self.durations) else: if file_type == CSV: control_info = list() control_info.append('rabi_rate,azimuthal_angle,detuning,duration,maximum_rabi_rate') - for segment_idx in range(self.segments.shape[0]): + for segment_idx in range(self.number_of_segments): control_info.append('{},{},{},{},{}'.format( self.rabi_rates[segment_idx]/self.maximum_rabi_rate, - np.arctan2(self.segments[segment_idx, 1], - self.segments[segment_idx, 0]), - self.segments[segment_idx, 2], - self.segments[segment_idx, 3], + np.arctan2(self.amplitude_y[segment_idx], + self.amplitude_x[segment_idx]), + self.detunings[segment_idx], + self.durations[segment_idx], self.maximum_rabi_rate )) @@ -415,9 +414,9 @@ def _qctrl_expanded_export_content(self, file_type, coordinates): control_info['maximum_rabi_rate'] = self.maximum_rabi_rate control_info['rabi_rates'] = list(self.rabi_rates / self.maximum_rabi_rate) control_info['azimuthal_angles'] = list(np.arctan2( - self.segments[:, 1], self.segments[:, 0])) - control_info['detuning'] = list(self.segments[:, 2]) - control_info['duration'] = list(self.segments[:, 3]) + self.amplitude_y, self.amplitude_x)) + control_info['detuning'] = list(self.detunings) + control_info['duration'] = list(self.durations) return control_info @@ -539,22 +538,28 @@ def get_plot_formatted_arrays(self, coordinates=CARTESIAN, dimensionless_rabi_ra ArgumentsValueError Raised when an argument is invalid. """ - - plot_segments = self.get_transformed_segments( - coordinates=CARTESIAN, dimensionless_rabi_rate=dimensionless_rabi_rate) - - plot_data = get_plot_data_from_segments(plot_segments) + if dimensionless_rabi_rate: + normalizer = self.maximum_rabi_rate + else: + normalizer = 1 if coordinates == CARTESIAN: - (x_amplitudes, y_amplitudes, detunings, times) = plot_data + (x_amplitudes, y_amplitudes, detunings, times) = get_plot_data_from_segments( + np.vstack((self.amplitude_x / normalizer, self.amplitude_y / normalizer, + self.detunings, self.durations)).T + ) plot_dictionary = { 'amplitude_x': x_amplitudes, 'amplitude_y': y_amplitudes, 'detuning': detunings, 'times': times } + elif coordinates == CYLINDRICAL: - (x_plot, y_plot, detunings, times) = plot_data + (x_plot, y_plot, detunings, times) = get_plot_data_from_segments( + np.vstack((self.rabi_rates / normalizer, self.azimuthal_angles, + self.detunings, self.durations)).T + ) 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) @@ -584,22 +589,22 @@ def __str__(self): driven_control_string.append('{}:'.format(self.name)) # Format amplitudes - for i, axis in enumerate('XYZ'): + for axis in 'XYZ': pretty_amplitudes_str = ', '.join( [decimals_format_str.format(amplitude/np.pi).rstrip('0').rstrip('.') - for amplitude in self.segments[:, i]] + for amplitude in [self.amplitude_x, self.amplitude_y, self.detunings]] ) driven_control_string.append( - '{} Amplitudes: [{}] x pi'.format(axis, pretty_amplitudes_str) + '{} Amplitudes = [{}] x pi'.format(axis, pretty_amplitudes_str) ) # Format durations - total_duration = np.sum(self.segments[:, 3]) + total_duration = np.sum(self.durations) pretty_durations_str = ','.join( [decimals_format_str.format(duration/total_duration).rstrip('0').rstrip('.') - for duration in self.segments[:, 3]] + for duration in self.durations] ) driven_control_string.append( - 'Durations: [{}] x {}s'.format(pretty_durations_str, str(total_duration)) + 'Durations = [{}] x {}s'.format(pretty_durations_str, str(total_duration)) ) driven_control_string = '\n'.join(driven_control_string) From 438aeac4f12ca59a3ba2ca22fcbe5dfaecf593d3 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Thu, 9 May 2019 15:03:54 +1000 Subject: [PATCH 40/51] modify DrivenControl constructor args and adapt tests --- .../driven_controls/predefined.py | 165 ++++++++++++------ tests/test_driven_controls.py | 86 ++++++--- tests/test_predefined_driven_controls.py | 13 +- 3 files changed, 179 insertions(+), 85 deletions(-) diff --git a/qctrlopencontrols/driven_controls/predefined.py b/qctrlopencontrols/driven_controls/predefined.py index 05e5c60f..fd4b7fd6 100644 --- a/qctrlopencontrols/driven_controls/predefined.py +++ b/qctrlopencontrols/driven_controls/predefined.py @@ -226,19 +226,18 @@ def new_primitive_control( Returns ------- - qctrlopencontrols.DrivenControls + qctrlopencontrols.DrivenControl The driven control. """ - (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + (maximum_rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( maximum_rabi_rate, rabi_rotation, azimuthal_angle) - segments = [[ - rabi_rate * np.cos(azimuthal_angle), - rabi_rate * np.sin(azimuthal_angle), - 0., - rabi_rotation / rabi_rate], ] - - return DrivenControl(segments=segments, **kwargs) + return DrivenControl( + rabi_rates=[rabi_rotation], + azimuthal_angles=[azimuthal_angle], + detunings=[0], + durations=[rabi_rotation/maximum_rabi_rate], + **kwargs) def new_wimperis_1_control( @@ -263,23 +262,26 @@ def new_wimperis_1_control( Returns ------- - qctrlopencontrols.DrivenControls + qctrlopencontrols.DrivenControl The driven control. """ - (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + (maximum_rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( maximum_rabi_rate, rabi_rotation, azimuthal_angle) phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) - angles = np.array([ - [rabi_rotation, azimuthal_angle], - [np.pi, phi_p + azimuthal_angle], - [2 * np.pi, 3. * phi_p + azimuthal_angle], - [np.pi, phi_p + azimuthal_angle]]) - - segments = _derive_segments(angles, amplitude=rabi_rate) - return DrivenControl(segments=segments, **kwargs) + rabi_rates = [rabi_rotation, np.pi, 2 * np.pi, np.pi] + azimuthal_angles = [azimuthal_angle, azimuthal_angle + phi_p, + azimuthal_angle + 3 * phi_p, azimuthal_angle + phi_p] + detunings = [0] * 4 + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] + return DrivenControl( + rabi_rates=rabi_rates, + azimuthal_angles=azimuthal_angles, + detunings=detunings, + durations=durations, + **kwargs) def new_solovay_kitaev_1_control( rabi_rotation=None, @@ -303,22 +305,25 @@ def new_solovay_kitaev_1_control( Returns ------- - qctrlopencontrols.DrivenControls + qctrlopencontrols.DrivenControl The driven control. """ - (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + (maximum_rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( maximum_rabi_rate, rabi_rotation, azimuthal_angle) phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) - angles = np.array([ - [rabi_rotation, azimuthal_angle], - [2 * np.pi, -phi_p + azimuthal_angle], - [2 * np.pi, phi_p + azimuthal_angle]]) + rabi_rates = [rabi_rotation, 2 * np.pi, 2 * np.pi] + azimuthal_angles = [azimuthal_angle, azimuthal_angle - phi_p, azimuthal_angle + phi_p] + detunings = [0] * 3 + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] - segments = _derive_segments(angles, amplitude=rabi_rate) - - return DrivenControl(segments=segments, **kwargs) + return DrivenControl( + rabi_rates=rabi_rates, + azimuthal_angles=azimuthal_angles, + detunings=detunings, + durations=durations, + **kwargs) def new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( # pylint: disable=invalid-name @@ -343,7 +348,7 @@ def new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control Returns ------- - qctrlopencontrols.DrivenControls + qctrlopencontrols.DrivenControl The driven control. Raises @@ -351,7 +356,7 @@ def new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control ArgumentsValueError Raised when an argument is invalid. """ - (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + (maximum_rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( maximum_rabi_rate, rabi_rotation, azimuthal_angle) # Create a lookup table for rabi rotation and phase angles, taken from the official paper. @@ -391,9 +396,17 @@ def degrees_to_radians(angle_in_degrees): [theta_2, phi_2 + azimuthal_angle], [theta_3, phi_3 + azimuthal_angle]]) - segments = _derive_segments(angles, amplitude=rabi_rate) + rabi_rates = [theta_1, theta_2, theta_3] + azimuthal_angles = [azimuthal_angle + phi_1, azimuthal_angle + phi_2, azimuthal_angle + phi_1] + detunings = [0] * 3 + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] - return DrivenControl(segments=segments, **kwargs) + return DrivenControl( + rabi_rates=rabi_rates, + azimuthal_angles=azimuthal_angles, + detunings=detunings, + durations=durations, + **kwargs) def new_compensating_for_off_resonance_with_a_pulse_sequence_control( # pylint: disable=invalid-name @@ -418,10 +431,10 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_control( # pylint: Returns ------- - qctrlopencontrols.DrivenControls + qctrlopencontrols.DrivenControl The driven control. """ - (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + (maximum_rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( maximum_rabi_rate, rabi_rotation, azimuthal_angle) k = np.arcsin(np.sin(rabi_rotation / 2.) / 2.) @@ -430,9 +443,17 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_control( # pylint: [2. * np.pi - 2. * k, np.pi + azimuthal_angle], [rabi_rotation / 2. - k, azimuthal_angle]]) - segments = _derive_segments(angles, amplitude=rabi_rate) + rabi_rates = [rabi_rotation / 2. + 2 * np.pi - k, 2 * np.pi - 2 * k, rabi_rotation / 2. - k] + azimuthal_angles = [azimuthal_angle, azimuthal_angle + np.pi, azimuthal_angle] + detunings = [0] * 3 + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] - return DrivenControl(segments=segments, **kwargs) + return DrivenControl( + rabi_rates=rabi_rates, + azimuthal_angles=azimuthal_angles, + detunings=detunings, + durations=durations, + **kwargs) def new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( # pylint: disable=invalid-name @@ -458,10 +479,10 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_contr Returns ------- - qctrlopencontrols.DrivenControls + qctrlopencontrols.DrivenControl The driven control. """ - (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + (maximum_rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( maximum_rabi_rate, rabi_rotation, azimuthal_angle) phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) @@ -474,9 +495,20 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_contr [2. * np.pi, 3 * phi_p + azimuthal_angle], [np.pi, phi_p + azimuthal_angle]]) - segments = _derive_segments(angles, amplitude=rabi_rate) + rabi_rates = [2 * np.pi + rabi_rotation / 2. - k, 2 * np.pi - 2 * k, + rabi_rotation / 2. - k, np.pi, 2 * np.pi, np.pi] + azimuthal_angles = [azimuthal_angle, azimuthal_angle + np.pi, azimuthal_angle, + azimuthal_angle + phi_p, azimuthal_angle + 3 * phi_p, + azimuthal_angle + phi_p] + detunings = [0] * 6 + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] - return DrivenControl(segments=segments, **kwargs) + return DrivenControl( + rabi_rates=rabi_rates, + azimuthal_angles=azimuthal_angles, + detunings=detunings, + durations=durations, + **kwargs) def new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( # pylint: disable=invalid-name @@ -502,10 +534,10 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev Returns ------- - qctrlopencontrols.DrivenControls + qctrlopencontrols.DrivenControl The driven control. """ - (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + (maximum_rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( maximum_rabi_rate, rabi_rotation, azimuthal_angle) phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) @@ -517,9 +549,19 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev [2. * np.pi, -phi_p + azimuthal_angle], [2. * np.pi, phi_p + azimuthal_angle]]) - segments = _derive_segments(angles, amplitude=rabi_rate) + rabi_rates = [2 * np.pi + rabi_rotation / 2. - k, 2 * np.pi - 2 * k, + rabi_rotation / 2. - k, 2 * np.pi, 2 * np.pi] + azimuthal_angles = [azimuthal_angle, azimuthal_angle + np.pi, azimuthal_angle, + azimuthal_angle - phi_p, azimuthal_angle + phi_p] + detunings = [0] * 5 + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] - return DrivenControl(segments=segments, **kwargs) + return DrivenControl( + rabi_rates=rabi_rates, + azimuthal_angles=azimuthal_angles, + detunings=detunings, + durations=durations, + **kwargs) def new_corpse_in_scrofulous_control( # pylint: disable=invalid-name @@ -546,7 +588,7 @@ def new_corpse_in_scrofulous_control( # pylint: disable=invalid-name Returns ------- - qctrlopencontrols.DrivenControls + qctrlopencontrols.DrivenControl The driven control. Raises @@ -554,7 +596,7 @@ def new_corpse_in_scrofulous_control( # pylint: disable=invalid-name ArgumentsValueError Raised when an argument is invalid. """ - (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + (maximum_rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( maximum_rabi_rate, rabi_rotation, azimuthal_angle) # Create a lookup table for rabi rotation and phase angles, taken from @@ -597,12 +639,21 @@ def degrees_to_radians(angle_in_degrees): [2. * np.pi - 2. * k, np.pi + phi + azimuthal_angle], [theta / 2. - k, phi + azimuthal_angle]]) total_angles.append(angles) - + total_angles = np.vstack(total_angles) - segments = _derive_segments(total_angles, amplitude=rabi_rate) + rabi_rates = total_angles[:, 0] + azimuthal_angles = total_angles[:, 1] + + detunings = [0] * 9 + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] - return DrivenControl(segments=segments, **kwargs) + return DrivenControl( + rabi_rates=rabi_rates, + azimuthal_angles=azimuthal_angles, + detunings=detunings, + durations=durations, + **kwargs) def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-name @@ -635,7 +686,7 @@ def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-n ArgumentsValueError Raised when an argument is invalid. """ - (rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( + (maximum_rabi_rate, rabi_rotation, azimuthal_angle) = _predefined_common_attributes( maximum_rabi_rate, rabi_rotation, azimuthal_angle) if np.isclose(rabi_rotation, np.pi): @@ -652,7 +703,7 @@ def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-n 'rabi_rotation angle must be either pi, pi/2 or pi/4', {'rabi_rotation': rabi_rotation}) - rabi_rate_plus = rabi_rate + rabi_rate_plus = maximum_rabi_rate time_segment = theta_plus / rabi_rate_plus rabi_rate_minus = theta_minus / time_segment @@ -670,4 +721,14 @@ def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-n rabi_rate_plus * np.sin(azimuthal_angle), 0., time_segment]]) - return DrivenControl(segments=segments, **kwargs) + rabi_rates = [rabi_rate_plus, rabi_rate_minus, rabi_rate_minus, rabi_rate_plus] + azimuthal_angles = [azimuthal_angle] * 4 + detunings = [0] * 4 + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] + + return DrivenControl( + rabi_rates=rabi_rates, + azimuthal_angles=azimuthal_angles, + detunings=detunings, + durations=durations, + **kwargs) diff --git a/tests/test_driven_controls.py b/tests/test_driven_controls.py index bcb38288..40c11609 100644 --- a/tests/test_driven_controls.py +++ b/tests/test_driven_controls.py @@ -25,6 +25,10 @@ from qctrlopencontrols import DrivenControl from qctrlopencontrols.globals import CARTESIAN, CYLINDRICAL +from qctrlopencontrols.driven_controls.constants import ( + UPPER_BOUND_SEGMENTS, UPPER_BOUND_RABI_RATE, UPPER_BOUND_DETUNING_RATE, + UPPER_BOUND_DURATION, LOWER_BOUND_DURATION) + def _remove_file(filename): """Removes the file after test done @@ -41,43 +45,56 @@ def test_driven_controls(): """Tests the construction of driven controls """ - _segments = [[np.pi, 0., 0., 1.], - [np.pi, np.pi/2, 0., 2.], - [0., 0., np.pi, 3.]] + _rabi_rates = [np.pi, np.pi, 0] + _azimuthal_angles = [np.pi/2, 0, -np.pi] + _detunings = [0, 0, 0] + _durations = [1, 2, 3] _name = 'driven_control' driven_control = DrivenControl( - segments=_segments, name=_name) + rabi_rates=_rabi_rates, + azimuthal_angles=_azimuthal_angles, + detunings=_detunings, + durations=_durations, + name=_name) - assert np.allclose(driven_control.segments, _segments) - assert driven_control.number_of_segments == 3 - assert np.allclose(driven_control.segment_durations, np.array( - [1., 2., 3.])) + assert np.allclose(driven_control.rabi_rates, _rabi_rates) + assert np.allclose(driven_control.durations, _durations) + assert np.allclose(driven_control.detunings, _detunings) + assert np.allclose(driven_control.azimuthal_angles, _azimuthal_angles) assert driven_control.name == _name with pytest.raises(ArgumentsValueError): - - _ = DrivenControl(segments=[[1e12, 0., 3, 1.]]) - _ = DrivenControl(segments=[[3., 0., 1e12, 1.]]) - _ = DrivenControl(segments=[[3., 0., 1e12, -1.]]) - _ = DrivenControl(segments=[[0., 0., 0., 0.]]) - + _ = DrivenControl(rabi_rates=[-1]) + with pytest.raises(ArgumentsValueError): + _ = DrivenControl(durations=[0]) + with pytest.raises(ArgumentsValueError): + _ = DrivenControl(rabi_rates=[1.1 * UPPER_BOUND_RABI_RATE]) + with pytest.raises(ArgumentsValueError): + _ = DrivenControl(detunings=[1.1 * UPPER_BOUND_DETUNING_RATE]) + with pytest.raises(ArgumentsValueError): + _ = DrivenControl(rabi_rates=[1] * UPPER_BOUND_SEGMENTS + [1]) def test_control_export(): """Tests exporting the control to a file """ + _rabi_rates = [5 * np.pi, 4 * np.pi, 3 * np.pi] + _azimuthal_angles = [np.pi / 4, np.pi / 3, 0] + _detunings = [0, 0, np.pi] + _durations = [2, 2, 1] - _maximum_rabi_rate = 5*np.pi - _segments = [[_maximum_rabi_rate*np.cos(np.pi/4), _maximum_rabi_rate*np.sin(np.pi/4), 0., 2.], - [_maximum_rabi_rate*np.cos(np.pi/3), _maximum_rabi_rate*np.sin(np.pi/3), 0., 2.], - [0., 0., np.pi, 1.]] - _name = 'driven_controls' + _name = 'driven_control' driven_control = DrivenControl( - segments=_segments, name=_name) + rabi_rates=_rabi_rates, + azimuthal_angles=_azimuthal_angles, + detunings=_detunings, + durations=_durations, + name=_name + ) _filename = 'driven_control_qctrl_cylindrical.csv' driven_control.export_to_file( @@ -116,22 +133,33 @@ def test_plot_data(): """ Test the plot data produced for a driven control. """ + _rabi_rates = [np.pi, 2 * np.pi, np.pi] + _azimuthal_angles = [0, np.pi/2, -np.pi/2] + _detunings = [0, 1, 0] + _durations = [1, 1.25, 1.5] - segments = [[1., 0., 0., 2.], - [0., 1.5, 1.7, 3.], - [1., 0., 2.1, 0.5]] - x_amplitude = [0., 1., 1., 0., 0., 1., 1., 0.] - y_amplitude = [0., 0., 0., 1.5, 1.5, 0., 0., 0.] - z_amplitude = [0., 0., 0., 1.7, 1.7, 2.1, 2.1, 0.] - times = [0., 0., 2., 2., 5., 5., 5.5, 5.5] - driven_control = DrivenControl(segments=segments) - plot_data = driven_control.get_plot_formatted_arrays(dimensionless_rabi_rate=False) + driven_control = DrivenControl( + rabi_rates=_rabi_rates, + azimuthal_angles=_azimuthal_angles, + detunings=_detunings, + durations=_durations + ) + + x_amplitude = [0., 0.5, 0.5, 0., 0., 0., 0., 0.] + y_amplitude = [0., 0., 0., 1., 1., -0.5, -0.5, 0.] + z_amplitude = [0., 0., 0., 1., 1., 0., 0., 0.] + times = [0., 0., 1., 1., 2.25, 2.25, 3.75, 3.75] + + plot_data = driven_control.get_plot_formatted_arrays( + dimensionless_rabi_rate=False, coordinates='cartesian' + ) assert np.allclose(plot_data['times'], times) assert np.allclose(plot_data['amplitude_x'], x_amplitude) assert np.allclose(plot_data['amplitude_y'], y_amplitude) assert np.allclose(plot_data['detuning'], z_amplitude) +@pytest.mark.skip('save for later') def test_dimensionless_segments(): """ Test the dimensionless amplitude and angle segments generated diff --git a/tests/test_predefined_driven_controls.py b/tests/test_predefined_driven_controls.py index d30ce6a8..88f85bb1 100644 --- a/tests/test_predefined_driven_controls.py +++ b/tests/test_predefined_driven_controls.py @@ -63,8 +63,8 @@ def test_primitive_control_segments(): _rabi_rotation = np.pi _azimuthal_angle = np.pi/2 _segments = [[ - np.cos(_azimuthal_angle), - np.sin(_azimuthal_angle), + _rabi_rotation * np.cos(_azimuthal_angle), + _rabi_rotation * np.sin(_azimuthal_angle), 0., _rabi_rotation], ] @@ -82,8 +82,13 @@ def test_primitive_control_segments(): scheme=PRIMITIVE ) - assert np.allclose(_segments, primitive_control_1.segments) - assert np.allclose(_segments, primitive_control_2.segments) + for control in [primitive_control_1, primitive_control_2]: + segments = np.vstack(( + control.amplitude_x, control.amplitude_y, + control.detunings, control.durations + )) + assert np.allclose(_segments, segments) + assert np.allclose(_rabi_rate, control.maximum_rabi_rate) def test_wimperis_1_control(): From dad4973275167d15bc1f118ea6f08548751e9619 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Thu, 9 May 2019 15:40:08 +1000 Subject: [PATCH 41/51] adapting dimensions for segments and fixing tests --- .../driven_controls/driven_control.py | 16 ++++----- tests/test_driven_controls.py | 10 +++--- tests/test_predefined_driven_controls.py | 35 +++++++++++-------- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index 1c7f8877..02f194c1 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -252,7 +252,7 @@ def amplitude_x(self): X-Amplitude of each segment """ - return (self.rabi_rates * np.cos(self.azimuthal_angles))/self.maximum_rabi_rate + return (self.rabi_rates * np.cos(self.azimuthal_angles)) @property def amplitude_y(self): @@ -264,7 +264,7 @@ def amplitude_y(self): Y-Amplitude of each segment """ - return (self.rabi_rates * np.sin(self.azimuthal_angles))/self.maximum_rabi_rate + return (self.rabi_rates * np.sin(self.azimuthal_angles)) @property def angles(self): @@ -546,9 +546,9 @@ def get_plot_formatted_arrays(self, coordinates=CARTESIAN, dimensionless_rabi_ra self.detunings, self.durations)).T ) plot_dictionary = { - 'amplitude_x': x_amplitudes, - 'amplitude_y': y_amplitudes, - 'detuning': detunings, + 'amplitudes_x': x_amplitudes, + 'amplitudes_y': y_amplitudes, + 'detunings': detunings, 'times': times } @@ -562,9 +562,9 @@ def get_plot_formatted_arrays(self, coordinates=CARTESIAN, dimensionless_rabi_ra azimuthal_angles_plot = np.arctan2(y_plot, x_plot) amplitudes_plot = np.sqrt(np.abs(x_plot**2 + y_plot**2)) plot_dictionary = { - 'rabi_rate': amplitudes_plot, - 'azimuthal_angle': azimuthal_angles_plot, - 'detuning': detunings, + 'rabi_rates': amplitudes_plot, + 'azimuthal_angles': azimuthal_angles_plot, + 'detunings': detunings, 'times': times } else: diff --git a/tests/test_driven_controls.py b/tests/test_driven_controls.py index 40c11609..130d2f88 100644 --- a/tests/test_driven_controls.py +++ b/tests/test_driven_controls.py @@ -145,8 +145,8 @@ def test_plot_data(): durations=_durations ) - x_amplitude = [0., 0.5, 0.5, 0., 0., 0., 0., 0.] - y_amplitude = [0., 0., 0., 1., 1., -0.5, -0.5, 0.] + x_amplitude = [0., np.pi, np.pi, 0., 0., 0., 0., 0.] + y_amplitude = [0., 0., 0., 2*np.pi, 2*np.pi, -np.pi, -np.pi, 0.] z_amplitude = [0., 0., 0., 1., 1., 0., 0., 0.] times = [0., 0., 1., 1., 2.25, 2.25, 3.75, 3.75] @@ -155,9 +155,9 @@ def test_plot_data(): ) assert np.allclose(plot_data['times'], times) - assert np.allclose(plot_data['amplitude_x'], x_amplitude) - assert np.allclose(plot_data['amplitude_y'], y_amplitude) - assert np.allclose(plot_data['detuning'], z_amplitude) + assert np.allclose(plot_data['amplitudes_x'], x_amplitude) + assert np.allclose(plot_data['amplitudes_y'], y_amplitude) + assert np.allclose(plot_data['detunings'], z_amplitude) @pytest.mark.skip('save for later') def test_dimensionless_segments(): diff --git a/tests/test_predefined_driven_controls.py b/tests/test_predefined_driven_controls.py index 88f85bb1..bbb39a98 100644 --- a/tests/test_predefined_driven_controls.py +++ b/tests/test_predefined_driven_controls.py @@ -60,13 +60,14 @@ def test_primitive_control_segments(): """Test the segments of the predefined primitive driven control """ _rabi_rate = 1 - _rabi_rotation = np.pi + _rabi_rotation = 1.5 _azimuthal_angle = np.pi/2 - _segments = [[ - _rabi_rotation * np.cos(_azimuthal_angle), - _rabi_rotation * np.sin(_azimuthal_angle), + _segments = [ + np.cos(_azimuthal_angle) * _rabi_rotation, + np.sin(_azimuthal_angle) * _rabi_rotation, 0., - _rabi_rotation], ] + _rabi_rotation + ] primitive_control_1 = new_primitive_control( rabi_rotation=_rabi_rotation, @@ -83,12 +84,14 @@ def test_primitive_control_segments(): ) for control in [primitive_control_1, primitive_control_2]: - segments = np.vstack(( - control.amplitude_x, control.amplitude_y, - control.detunings, control.durations - )) + segments = [ + control.amplitude_x[0], + control.amplitude_y[0], + control.detunings[0], + control.durations[0] + ] assert np.allclose(_segments, segments) - assert np.allclose(_rabi_rate, control.maximum_rabi_rate) + assert np.allclose(_rabi_rotation, control.maximum_rabi_rate) def test_wimperis_1_control(): @@ -99,13 +102,13 @@ def test_wimperis_1_control(): phi_p = np.arccos(-_rabi_rotation / (4 * np.pi)) - _segments = [ + _segments = np.array([ [np.cos(_azimuthal_angle), np.sin(_azimuthal_angle), 0., _rabi_rotation], [np.cos(phi_p + _azimuthal_angle), np.sin(phi_p + _azimuthal_angle), 0., np.pi], [np.cos(3. * phi_p + _azimuthal_angle), np.sin(3. * phi_p + _azimuthal_angle), 0., 2 * np.pi], [np.cos(phi_p + _azimuthal_angle), np.sin(phi_p + _azimuthal_angle), 0., np.pi] - ] + ]) wimperis_control_1 = new_wimperis_1_control( rabi_rotation=_rabi_rotation, @@ -119,8 +122,12 @@ def test_wimperis_1_control(): scheme=BB1 ) - assert np.allclose(wimperis_control_1.segments, _segments) - assert np.allclose(wimperis_control_2.segments, _segments) + for control in [wimperis_control_1, wimperis_control_2]: + segments = np.vstack(( + control.amplitude_x, control.amplitude_y, control.detunings, control.durations + )).T + assert np.allclose(segments, _segments) + assert np.allclose(segments, _segments) def test_solovay_kitaev_1_control(): From 2e09cabfd1748bb216ad1e05737bba452efcd864 Mon Sep 17 00:00:00 2001 From: Rajib Chakravorty Date: Thu, 9 May 2019 15:45:55 +1000 Subject: [PATCH 42/51] dynamical_decoupling_sequence tests fixed --- tests/test_dynamical_decoupling.py | 35 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/tests/test_dynamical_decoupling.py b/tests/test_dynamical_decoupling.py index 9479d20d..da785404 100644 --- a/tests/test_dynamical_decoupling.py +++ b/tests/test_dynamical_decoupling.py @@ -387,24 +387,18 @@ def test_conversion_to_driven_controls(): maximum_rabi_rate=_maximum_rabi_rate, maximum_detuning_rate=_maximum_detuning_rate, name=_name) - assert np.sum(driven_control.segments[:, 3]) == _duration - assert np.allclose(driven_control.segments[:, 0], np.array( - [0., _maximum_rabi_rate*np.cos(_azimuthal_angles[1]), 0., 0., 0., - _maximum_rabi_rate*np.cos(_azimuthal_angles[3]), 0.])) - assert np.allclose(driven_control.segments[:, 1], np.array( - [0., _maximum_rabi_rate*np.sin(_azimuthal_angles[1]), 0., 0., 0., 0., 0])) - assert np.allclose(driven_control.segments[:, 2], np.array( + assert np.sum(driven_control.durations) == _duration + assert np.allclose(driven_control.rabi_rates, np.array( + [0., _maximum_rabi_rate, 0., 0., 0., + _maximum_rabi_rate, 0.])) + assert np.allclose(driven_control.azimuthal_angles, np.array( + [0., _azimuthal_angles[1], 0., 0., 0., + _azimuthal_angles[3], 0.])) + assert np.allclose(driven_control.detunings, np.array( [0., 0., 0., np.pi, 0., 0., 0])) - assert np.allclose(driven_control.segments[:, 3], np.array( + assert np.allclose(driven_control.durations, np.array( [4.75e-1, 5e-2, 4.5e-1, 5e-2, 4.5e-1, 5e-2, 4.75e-1])) - _duration = 2. - _offsets = 2 * np.array([0., 0.25, 0.5, 0.75, 1.]) - _rabi_rotations = np.array([0., np.pi, 0., np.pi, 0.]) - _azimuthal_angles = np.array([0., np.pi / 2, 0., 0., 0.]) - _detuning_rotations = np.array([0., 0., np.pi, 0., 0.]) - _name = 'test_sequence' - def test_free_evolution_conversion(): @@ -433,8 +427,14 @@ def test_free_evolution_conversion(): maximum_detuning_rate=_maximum_detuning_rate, name=_name) - _segments = np.reshape(np.array([0., 0., 0., _duration]), (1, 4)) - assert np.allclose(driven_control.segments, _segments) + _rabi_rates = np.array([0.]) + _azimuthal_angles = np.array([0.]) + _detunings = np.array([0.]) + _durations = np.array([_duration]) + assert np.allclose(driven_control.rabi_rates, _rabi_rates) + assert np.allclose(driven_control.azimuthal_angles, _azimuthal_angles) + assert np.allclose(driven_control.detunings, _detunings) + assert np.allclose(driven_control.durations, _durations) def test_export_to_file(): @@ -501,5 +501,6 @@ def test_export_to_file(): _remove_file('dds_qctrl_cartesian.json') + if __name__ == '__main__': pass From 8b37858c95956ae6bfcf47cc00e6ba9d2ecc61f4 Mon Sep 17 00:00:00 2001 From: michaelhush Date: Thu, 9 May 2019 16:16:54 +1000 Subject: [PATCH 43/51] Updated notebooks --- examples/creating_a_dds.ipynb | 37 +++--- examples/creating_a_driven_control.ipynb | 147 ++++++++++++----------- 2 files changed, 90 insertions(+), 94 deletions(-) diff --git a/examples/creating_a_dds.ipynb b/examples/creating_a_dds.ipynb index e1e738ce..b0fa9228 100644 --- a/examples/creating_a_dds.ipynb +++ b/examples/creating_a_dds.ipynb @@ -39,16 +39,16 @@ "\n", "Q-CTRL Open Controls can create DDSs according to the following protocols:\n", "\n", - "1. Ramsey\n", - "2. Spin Echo (SE)\n", - "3. Carr-Purcell (CP)\n", - "4. Carr-Purcell-Meiboom-Gill (CPMG)\n", - "5. Uhrig\n", - "6. Periodic\n", - "7. Walsh Single-axis\n", - "8. Quadratic\n", - "9. X-concatenated\n", - "10. XY-concatenated\n", + "1. `Ramsey`\n", + "2. `spin echo`\n", + "3. `Carr-Purcell`\n", + "4. `Carr-Purcell-Meiboom-Gill`\n", + "5. `Uhrig`\n", + "6. `periodic`\n", + "7. `Walsh single-axis`\n", + "8. `quadratic`\n", + "9. `X concatenated`\n", + "10. `XY concatenated`\n", "\n", "See the [technical documentation](https://docs.q-ctrl.com/control-formats#dynamical-decoupling-sequences) for details." ] @@ -278,19 +278,19 @@ "rabi_plot_axis.plot(times, rabi_rotations)\n", "rabi_plot_axis.ticklabel_format(style='sci', axis='x', scilimits=(0, 2))\n", "rabi_plot_axis.set_xlim([0, max(times)])\n", - "rabi_plot_axis.set_xlabel('Time (sec)')\n", + "rabi_plot_axis.set_xlabel('Time (s)')\n", "rabi_plot_axis.set_ylabel('Rabi Rotations (rad)')\n", "\n", "azimuth_plot_axis.plot(times, azimuthal_angles)\n", "azimuth_plot_axis.ticklabel_format(style='sci', axis='x', scilimits=(0, 2))\n", "azimuth_plot_axis.set_xlim([0, max(times)])\n", - "azimuth_plot_axis.set_xlabel('Time (sec)')\n", + "azimuth_plot_axis.set_xlabel('Time (s)')\n", "azimuth_plot_axis.set_ylabel('Azimuthal Angle (rad)')\n", "\n", "detuning_plot_axis.plot(times, detuning_rotations)\n", "detuning_plot_axis.ticklabel_format(style='sci', axis='x', scilimits=(0, 2))\n", "detuning_plot_axis.set_xlim([0, max(times)])\n", - "detuning_plot_axis.set_xlabel('Time (sec)')\n", + "detuning_plot_axis.set_xlabel('Time (s)')\n", "detuning_plot_axis.set_ylabel('Detuning Rotation (rad)')\n" ] }, @@ -401,7 +401,7 @@ "source": [ "## Custom Definition of Dynamic Decoupling Sequence\n", "\n", - "`DynamicDecouplingSequence`, defined in Q-CTRL Open Controls, accepts `duration`, `rabi_rotations`, `azimuthal_angles` and `detuning_rotations` as parameters to define any arbitrary DDS. This is the most generalized way to create any custom DDS." + "An arbitrary `DynamicDecouplingSequence` can be created by providing a `duration` along with arrays for the `rabi_rotations`, `azimuthal_angles`, `detuning_rotations` and offsets." ] }, { @@ -433,7 +433,6 @@ } ], "source": [ - "# defining the required parameters\n", "_duration = 1.50\n", "_rabi_rotations = [0., np.pi, np.pi, 0., np.pi, np.pi, 0.]\n", "_azimuthal_angles = [0., np.pi/2, 0., 0., 0., np.pi/2, 0.]\n", @@ -441,24 +440,20 @@ "_offsets = [0., 0.25, 0.50, 0.75, 1.00, 1.25, 1.50]\n", "_name = 'Custom DDS'\n", "\n", - "custom_dds = DynamicDecouplingSequence(duration=_duration,\\\n", - " rabi_rotations=_rabi_rotations,\\\n", + "custom_dds = DynamicDecouplingSequence(duration=_duration,\n", + " rabi_rotations=_rabi_rotations,\n", " azimuthal_angles=_azimuthal_angles,\n", " detuning_rotations=_detuning_rotations,\n", " offsets=_offsets,\n", " name=_name)\n", "\n", - "## this specific pulse, as defined above consists of (Y_pi, X_pi, Z_pi, X_pi, Y_pi) pulses\n", - "## at the offsets (0.25, 0.50, 0.75, 1.00, 1.25)\n", "## let us plot and verify\n", - "\n", "formatted_plot_data = custom_dds.get_plot_formatted_arrays()\n", "rabi_rotations, azimuthal_angles, detuning_rotations, times = (formatted_plot_data['rabi_rotations'],\n", " formatted_plot_data['azimuthal_angles'],\n", " formatted_plot_data['detuning_rotations'],\n", " formatted_plot_data['times'])\n", "\n", - "# prepare the axes\n", "figure, (rabi_plot_axis, azimuth_plot_axis, detuning_plot_axis) = plt.subplots(\n", " 1, 3, figsize=(20,5))\n", "\n", diff --git a/examples/creating_a_driven_control.ipynb b/examples/creating_a_driven_control.ipynb index 29db3d8a..cdd410bc 100644 --- a/examples/creating_a_driven_control.ipynb +++ b/examples/creating_a_driven_control.ipynb @@ -8,7 +8,7 @@ "\n", "This notebook illustrates how to use Q-CTRL Open Controls to create a driven control.\n", "\n", - "A driven control represents the physical implementation of a quantum gate and is specified via one or multiple sets of rotation angles and phases with finite durations. Primitive driven controls only consist of one set and implement the desired gate directly, whereas dynamically corrected gates (DCGs) require driven controls made up of multiple segments. DCGs can be used as drop-in replacements to actively suppress errors in quantum circuits and improve the overall gate fidelity.\n", + "A driven control represents a continuous drive on a the transition of a qubit with a tunable detuning. The Open Controls package allows you to generate driven controls that enact dynamically corrected gates (DCG). These dynamically corrected gates are able to achieve an arbitrary rotation of the bloch sphere (around any point on the equator), in a manner that is robust to dephasing and/or control noise. DCGs can be used as drop-in gate replacements to suppress errors in a quantum computation.\n", "\n", "Q-CTRL Open Controls can be used to create a driven control from a library of well-known control schemes. Once created, it can be printed, plotted, exported in CSV or JSON format for use on a quantum computer or any of [Q-CTRL's products](https://q-ctrl.com/products/)." ] @@ -22,18 +22,13 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", - "from qctrlopencontrols import new_predefined_driven_control, DrivenControl\n", - "\n", - "plt.rcParams['axes.prop_cycle'] = plt.cycler(\n", - " color=['#231e21', '#381e42', '#c80a64', '#680cea', '#5b02c1',\n", - " '#ffffff', '#faf7fc', '#ae9fb4', '#6c5b72', '250d2e']\n", - ")" + "from qctrlopencontrols import new_predefined_driven_control, DrivenControl" ] }, { @@ -42,17 +37,17 @@ "source": [ "## Predefined Driven Control Schemes\n", "\n", - "Q-CTRL Open Controls can create driven controls according to the following protocols:\n", + "Q-CTRL Open Controls can create driven controls according to the following dynamically corrected gate protocols:\n", "\n", - "1. Primitive\n", - "2. First-order Wimperis broadband (BB1)\n", - "3. First-order Solovay-Kitaev (SK1)\n", - "4. Compensating for Off-Resonance with a Pulse Sequence (CORPSE)\n", - "5. First-order Walsh Amplitude-Modulated Filter (WAMF1)\n", - "6. Short Composite Rotation for Undoing Length Over and Under Shoot (SCROFULOUS)\n", - "7. COPRSE in BB1\n", - "8. CORPSE in SK1\n", - "9. CORPSE in SCROFULOUS\n", + "1. `primitive`\n", + "2. `BB1`\n", + "3. `SK1`\n", + "4. `CORPSE`\n", + "5. `WAMF1`\n", + "6. `SCROFULOUS`\n", + "7. `COPRSE in BB1`\n", + "8. `CORPSE in SK1`\n", + "9. `CORPSE in SCROFULOUS`\n", "\n", "See the [technical documentation](https://docs.q-ctrl.com/control-library) for details." ] @@ -63,25 +58,31 @@ "source": [ "## Creating and Printing a Driven Control\n", "\n", - "A driven control is defined as one or multiple sets of unitary operations, each initialized with by a `rabi_rotation`, an `azimuthal_angle` and a `maximum_rabi_rate`. For some driven controls, you can optionally specify the `shape` of the control to create either square- or Gaussian-shaped controls.\n", + "A driven control is made of a continuous drive on the qubits transition with a tunable detuning. The continuous drive is described by a piecewise constant function made of a set of segments. Each drive segment has a `rabi_rate` applied at a `azimuthal_angle` for a `duration`, with a `detuning`. The mathematical definition of a driven control is explained in the [technical documentation](http://docs.q-ctrl.com/control-library#dynamical-decoupling-sequences).\n", + "\n", + "Q-CTRL Open controls can generate a driven control from a library of dynamically corrected gate schemes, mathematically defined in the [technical documentation](https://docs.q-ctrl.com/control-formats#dynamical-decoupling-sequences). All dynamically corrected gates are derived from three quantities: \n", "\n", - "From those inputs, an array of segments is derived where each segment contains the (Cartesian) driving amplitudes in x, y, and z, as well as its duration. The full mathematical description of a driven control is explained in the [technical documentation](https://docs.q-ctrl.com/control-formats#control-coordinates)." + "* `maximum_rabi_rate` the maximum achievable rabi rate.\n", + "* `rabi_rotation` the total rotation of the bloch sphere.\n", + "* `azimuthal_angle` the angle to the center point of the rotation on the equator. " ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 2, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Primitive X-pi:\n", - "X Amplitudes: [+2] x pi\n", - "Y Amplitudes: [+0] x pi\n", - "Z Amplitudes: [+0] x pi\n", - "Durations: [+1] x 0.5s\n" + "ename": "TypeError", + "evalue": "unsupported format string passed to numpy.ndarray.__format__", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'Primitive X-pi'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m )\n\u001b[0;32m----> 9\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mprim\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/Dropbox/Q-Ctrl/Code/OpenControls/python-open-controls/qctrlopencontrols/driven_controls/driven_control.py\u001b[0m in \u001b[0;36m__str__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 590\u001b[0m pretty_amplitudes_str = ', '.join(\n\u001b[1;32m 591\u001b[0m [decimals_format_str.format(amplitude/np.pi).rstrip('0').rstrip('.')\n\u001b[0;32m--> 592\u001b[0;31m for amplitude in [self.amplitude_x, self.amplitude_y, self.detunings]]\n\u001b[0m\u001b[1;32m 593\u001b[0m )\n\u001b[1;32m 594\u001b[0m driven_control_string.append(\n", + "\u001b[0;32m~/Dropbox/Q-Ctrl/Code/OpenControls/python-open-controls/qctrlopencontrols/driven_controls/driven_control.py\u001b[0m in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 590\u001b[0m pretty_amplitudes_str = ', '.join(\n\u001b[1;32m 591\u001b[0m [decimals_format_str.format(amplitude/np.pi).rstrip('0').rstrip('.')\n\u001b[0;32m--> 592\u001b[0;31m for amplitude in [self.amplitude_x, self.amplitude_y, self.detunings]]\n\u001b[0m\u001b[1;32m 593\u001b[0m )\n\u001b[1;32m 594\u001b[0m driven_control_string.append(\n", + "\u001b[0;31mTypeError\u001b[0m: unsupported format string passed to numpy.ndarray.__format__" ] } ], @@ -219,7 +220,7 @@ "source": [ "### Plotting a driven control\n", "\n", - "Once created, Q-CTRL Open Controls provides the method `get_plot_formatted_arrays` to create a set of formatted arrays ready to be immediately plotted with Matplotlib. We use the `wimperis_1` (BB1) as a driven control to generate plots of the `x_amplitudes`, `y_amplitudes` and `z_amplitudes`." + "Once created, Q-CTRL Open Controls provides the method `get_plot_formatted_arrays` to create a set of formatted arrays ready to be immediately plotted with Matplotlib. We use the `BB1` as a driven control to generate plots of the `rabi_rates`, `azimuthal_angles` and `detunings`." ] }, { @@ -241,28 +242,28 @@ } ], "source": [ - "formatted_plot_data = bb1_x.get_plot_formatted_arrays(coordinates='cartesian')\n", - "x_amplitudes, y_amplitudes, z_amplitudes, times = (formatted_plot_data['amplitude_x'],\n", - " formatted_plot_data['amplitude_y'],\n", - " formatted_plot_data['detuning'],\n", + "formatted_plot_data = bb1_x.get_plot_formatted_arrays(coordinates='cylindrical')\n", + "rabi_rates, azimuthal_angles, detunings, times = (formatted_plot_data['rabi_rates'],\n", + " formatted_plot_data['azimuthal_angles'],\n", + " formatted_plot_data['detunings'],\n", " formatted_plot_data['times'])\n", "# prepare the axes\n", "figure, (x_axis, y_axis, z_axis) = plt.subplots(1, 3, figsize=(20,5))\n", "\n", - "x_axis.fill_between(times, x_amplitudes, 0, alpha=0.15, color='C3')\n", - "x_axis.plot(times, x_amplitudes, color='C3')\n", - "x_axis.set_xlabel('Time (sec)')\n", - "x_axis.set_ylabel('Drive amplitude in X (rad)')\n", + "x_axis.fill_between(times, x_amplitudes, 0, alpha=0.15, color='#680cea')\n", + "x_axis.plot(times, rabi_rates, color='#680cea')\n", + "x_axis.set_xlabel('Time (s)')\n", + "x_axis.set_ylabel('Rabi Rate (radHz)')\n", "\n", - "y_axis.fill_between(times, y_amplitudes, 0, alpha=0.15, color='C3')\n", - "y_axis.plot(times, y_amplitudes, color='C3')\n", - "y_axis.set_xlabel('Time (sec)')\n", - "y_axis.set_ylabel('Drive amplitude in Y (rad)')\n", + "y_axis.fill_between(times, azimuthal_angles, 0, alpha=0.15, color='#680cea')\n", + "y_axis.plot(times, y_amplitudes, color='#680cea')\n", + "y_axis.set_xlabel('Time (s)')\n", + "y_axis.set_ylabel('Azimuthal Angle (rad)')\n", "\n", - "z_axis.fill_between(times, z_amplitudes, 0, alpha=0.15, color='C3')\n", - "z_axis.plot(times, z_amplitudes, color='C3')\n", - "z_axis.set_xlabel('Time (sec)')\n", - "z_axis.set_ylabel('Drive amplitude in Z (rad)')\n", + "z_axis.fill_between(times, detunings, 0, alpha=0.15, color='#680cea')\n", + "z_axis.plot(times, z_amplitudes, color='#680cea')\n", + "z_axis.set_xlabel('Time (s)')\n", + "z_axis.set_ylabel('Detuning (radHz)')\n", "\n", "plt.show()" ] @@ -344,7 +345,7 @@ "source": [ "## Custom Definition of a Driven Control\n", "\n", - "`DrivenControls`, defined in Q-CTRL Open Controls, accepts a `segments` array of x, y and z amplitudes and durations to define any arbitrary Driven Control. This is the most generalized way to create any custom Driven Control." + "An arbitrary `DrivenControl` can be defined defined using arrays of `rabi_rotations`, `azimuthal_angles`, `detuning_rotations` and `durations`." ] }, { @@ -366,40 +367,40 @@ } ], "source": [ - "# defining the required parameters\n", - "x_amplitudes = [np.pi/2, 0, -np.pi, 0]\n", - "y_amplitudes = [0, -np.pi/2, 0, np.pi]\n", - "z_amplitudes = [np.pi/2, 0, -np.pi/2, 0]\n", - "durations = [0.5, 1, 2, 0.5]\n", - "\n", - "_segments = np.vstack([x_amplitudes, y_amplitudes, z_amplitudes, durations]).T\n", + "_rabi_rates = [np.pi/2,np.pi/2,np.pi,np.pi]\n", + "_azimuthal_angles = [0., -np.pi/2, 0., np.pi/2]\n", + "_detunings = [np.pi/2, 0, -np.pi/2, 0]\n", + "_durations = [0.5, 1, 2, 0.5]\n", "_name = 'Custon Driven Control'\n", "\n", - "custom_driven_control = DrivenControl(segments=_segments, name=_name)\n", + "custom_driven_control = DrivenControl(rabi_rates=_rabi_rates, \n", + " azimuthal_angles=_azimuthal_angles,\n", + " detunings=_detunings,\n", + " name=_name)\n", "\n", "## let us plot and verify\n", - "formatted_plot_data = custom_driven_control.get_plot_formatted_arrays(coordinates='cartesian')\n", - "x_amplitudes, y_amplitudes, z_amplitudes, times = (formatted_plot_data['amplitude_x'],\n", - " formatted_plot_data['amplitude_y'],\n", - " formatted_plot_data['detuning'],\n", + "formatted_plot_data = bb1_x.get_plot_formatted_arrays(coordinates='cylindrical')\n", + "rabi_rates, azimuthal_angles, detunings, times = (formatted_plot_data['rabi_rates'],\n", + " formatted_plot_data['azimuthal_angles'],\n", + " formatted_plot_data['detunings'],\n", " formatted_plot_data['times'])\n", - "# prepare the axes\n", + "\n", "figure, (x_axis, y_axis, z_axis) = plt.subplots(1, 3, figsize=(20,5))\n", "\n", - "x_axis.fill_between(times, x_amplitudes, 0, alpha=0.15, color='C3')\n", - "x_axis.plot(times, x_amplitudes, color='C3')\n", - "x_axis.set_xlabel('Time (sec)')\n", - "x_axis.set_ylabel('Drive amplitude in X (rad)')\n", + "x_axis.fill_between(times, x_amplitudes, 0, alpha=0.15, color='#680cea')\n", + "x_axis.plot(times, rabi_rates, color='#680cea')\n", + "x_axis.set_xlabel('Time (s)')\n", + "x_axis.set_ylabel('Rabi Rate (radHz)')\n", "\n", - "y_axis.fill_between(times, y_amplitudes, 0, alpha=0.15, color='C3')\n", - "y_axis.plot(times, y_amplitudes, color='C3')\n", - "y_axis.set_xlabel('Time (sec)')\n", - "y_axis.set_ylabel('Drive amplitude in Y (rad)')\n", + "y_axis.fill_between(times, azimuthal_angles, 0, alpha=0.15, color='#680cea')\n", + "y_axis.plot(times, y_amplitudes, color='#680cea')\n", + "y_axis.set_xlabel('Time (s)')\n", + "y_axis.set_ylabel('Azimuthal Angle (rad)')\n", "\n", - "z_axis.fill_between(times, z_amplitudes, 0, alpha=0.15, color='C3')\n", - "z_axis.plot(times, z_amplitudes, color='C3')\n", - "z_axis.set_xlabel('Time (sec)')\n", - "z_axis.set_ylabel('Drive amplitude in Z (rad)')\n", + "z_axis.fill_between(times, detunings, 0, alpha=0.15, color='#680cea')\n", + "z_axis.plot(times, z_amplitudes, color='#680cea')\n", + "z_axis.set_xlabel('Time (s)')\n", + "z_axis.set_ylabel('Detuning (radHz)')\n", "\n", "plt.show()" ] @@ -428,7 +429,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.1" + "version": "3.6.7" } }, "nbformat": 4, From 9ddabd9d3052f662b1043f459ca3db0d84892c6e Mon Sep 17 00:00:00 2001 From: michaelhush Date: Thu, 9 May 2019 16:21:36 +1000 Subject: [PATCH 44/51] Update the example notebooks --- examples/creating_a_dds.ipynb | 10 +--- examples/creating_a_driven_control.ipynb | 70 +++++++++++------------- 2 files changed, 34 insertions(+), 46 deletions(-) diff --git a/examples/creating_a_dds.ipynb b/examples/creating_a_dds.ipynb index b0fa9228..0f2d9b75 100644 --- a/examples/creating_a_dds.ipynb +++ b/examples/creating_a_dds.ipynb @@ -253,14 +253,12 @@ }, { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAABJcAAAFACAYAAAABCp3YAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvOIA7rQAAIABJREFUeJzs3XmYZGV5///3h0UwkUVhVGRxXFCDREFH0K/GuERFv5ExERXiggYlRom4/gSTCKLfn1vUn3HDUfmKJEEMKo4GRVQQNYIOiLJHgtsElBGQRQUZuH9/nNNatD3dRXWdqq6a9+u66uqzPOfU3eeaqbvPXc95nlQVkiRJkiRJ0iA2GXcAkiRJkiRJmlwWlyRJkiRJkjQwi0uSJEmSJEkamMUlSZIkSZIkDczikiRJkiRJkgZmcUmSJEmSJEkDs7gkSZIkSZKkgVlckiRJkiRJ0sAsLkmSJEmSJGlgm407gNtr++23r+XLl487DElacs4+++yfV9WycccxbuYJSZqbeaJhnpCkuS0mT0xccWn58uWsWbNm3GFI0pKT5EfjjmEpME9I0tzMEw3zhCTNbTF5wsfiJEmSJEmSNDCLS5IkSZIkSRqYxSVJkiRJkiQNzOKSJEmSJEmSBmZxSZIkSZIkSQOzuCRJkiRJkqSBWVySJEmSJEnSwCwuSZIkSZIkaWAWlyRJkiRJkjQwi0sboed8+Cye8+Gzxh2GBPjvUZIkTa5J+TtmUuIcpo3xd+7CxnodJ+H3XmoxbjbuADR6X7/05+MOQfot/z1KkqRJNSl/x0xKnMO0Mf7OXdhYr+Mk/N5LLUZ7LkmSJEmSJGlgFpckSZIkSZI0MItLkiRJkiRJGpjFJUmSJEmSJA3M4pIkSZIkSZIGZnFJkiRJkiRJA7O4JEmSJEmSpIF1VlxKsmWSbyX5bpILkrxhjjZbJDkhyaVJzkqyvKt4JEmSJE0O7yckaXJ02XPpJuBxVfVgYA9gnyQPn9XmIOCaqrov8C7grR3GI0mSJGlyeD8hSROis+JSNW5oVzdvXzWr2Urg2Hb5RODxSdJVTJIkSZImg/cTkjQ5Oh1zKcmmSc4FrgROraqzZjXZEfgJQFWtB64FtpvjPAcnWZNkzbp167oMWZIkSdIS4f2EJE2GTotLVXVLVe0B7ATslWT3WU3m+lZh9rcRVNWqqlpRVSuWLVvWRaiSJEmSlhjvJyRpMoxktriq+gVwOrDPrF1rgZ0BkmwGbANcPYqYJEmSJE0G7yckaWnrcra4ZUm2bZfvCPwZcPGsZquBA9vl/YCvVNXvfdMgSZIkaePi/YQkTY7NOjz3DsCxSTalKWJ9oqo+l+QoYE1VrQY+AhyX5FKabxj27zAeSZIkSZPD+wlJmhCdFZeq6nvAnnNsf33P8o3AM7qKQZIkSdJk8n5CkibHSMZckiRJkiRJ0nSyuCRJkiRJkqSBWVySJEmSJEnSwCwuSZIkSZIkaWAWlyRJkiRJkjQwi0uSpM4l2SfJJUkuTXLYHPu3SHJCu/+sJMtn7d8lyQ1JXj2qmCVJkiT1x+KSJKlTSTYF3gc8GdgNOCDJbrOaHQRcU1X3Bd4FvHXW/ncBn+86VkmSJEm3n8UlSVLX9gIurarLquo3wMeBlbParASObZdPBB6fJABJngZcBlwwonglSZIk3Q4WlyRJXdsR+EnP+tp225xtqmo9cC2wXZI/BF4LvGG+N0hycJI1SdasW7duaIFLkiRJWpjFJUlS1zLHtuqzzRuAd1XVDfO9QVWtqqoVVbVi2bJlA4YpSZIkaRCbjTsASdLUWwvs3LO+E3D5BtqsTbIZsA1wNbA3sF+StwHbArcmubGq3tt92JIkSZL6YXFJktS1bwO7JrkX8D/A/sBfzWqzGjgQ+CawH/CVqirgT2YaJDkSuMHCkiRJkrS0WFySJHWqqtYnOQQ4BdgUOKaqLkhyFLCmqlYDHwGOS3IpTY+l/ccXsSRJkqTbw+KSJKlzVXUycPKsba/vWb4ReMYC5ziyk+AkSZIkLYoDekuSJEmSJGlgFpckSZIkSZI0MItLkiRJkiRJGpjFJUmSJEmSJA3M4pIkSZIkSZIGZnFJkiRJkiRJA7O4JEmSJEmSpIFZXJIkSZIkSdLALC5JkiRJkiRpYBaXJEmSJEmSNDCLS5IkSZIkSRqYxSVJkiRJkiQNzOKSJEmSJEmSBmZxSZIkSZIkSQOzuCRJkiRJkqSBWVySJEmSJEnSwCwuSZIkSZIkaWCdFZeS7JzktCQXJbkgyaFztHlMkmuTnNu+Xt9VPJIkSZImh/cTkjQ5Nuvw3OuBV1XVOUm2As5OcmpVXTir3deq6s87jEOSJEnS5PF+QpImRGc9l6rqiqo6p12+HrgI2LGr95MkSZI0PbyfkKTJMZIxl5IsB/YEzppj9yOSfDfJ55M8cAPHH5xkTZI169at6zBSSZIkSUuN9xOStLR1XlxKcifgk8DLq+q6WbvPAe5ZVQ8G3gOcNNc5qmpVVa2oqhXLli3rNmBJkiRJS4b3E5K09HVaXEqyOU0i+Neq+tTs/VV1XVXd0C6fDGyeZPsuY5IkSZI0GbyfkKTJ0OVscQE+AlxUVe/cQJu7t+1Islcbz1VdxSRJkiRpMng/IUmTo8vZ4h4JPBc4L8m57bbXAbsAVNXRwH7A3yZZD/wa2L+qqsOYJEmSJE0G7yckaUJ0Vlyqqq8DWaDNe4H3dhWDJEmSpMnk/YQkTY6RzBYnSZIkSZKk6WRxSZIkSZIkSQOzuCRJkiRJkqSBWVySJEmSJEnSwCwuSZIkSZIkaWAWlyRJkiRJkjQwi0uSJEmSJEkamMUlSZIkSZIkDczikiRJkiRJkgZmcUmSJEmSJEkD22zcAUiSJEmaTkkeATwH+BNgB+DXwPnAfwD/UlXXjjE8SdKQ2HNJkiRJ0tAl+TzwQuAUYB+a4tJuwD8AWwKfSbLv+CKUJA2LPZckSZIkdeG5VfXzWdtuAM5pX+9Isv3ow5IkDZs9lyRJkiQN3RyFpYHaSJKWPnsuSZIASLKCZkyMe/C7MTG+VFVXD+Hc+wDvBjYFPlxVb5m1fwvgY8BDgauAZ1XVD5M8AXgLcAfgN8Brquori41HktS9JNcDtaH9VbX1CMORJHXI4pIkbeSSPB94GfAD4GzgEpqxMB4FvDbJ+cA/VtWPBzz/psD7gCcAa4FvJ1ldVRf2NDsIuKaq7ptkf+CtwLOAnwNPrarLk+xOM27HjoPEIUkararaCiDJUcBPgeOAAM8GthpjaJKkIbO4JEn6Q+CRVfXruXYm2QPYFRiouATsBVxaVZe15/s4sBLoLS6tBI5sl08E3pskVfWdnjYXAFsm2aKqbhowFknS6D2pqvbuWf9AkrOAt40rIEnScDnmkiRt5KrqfRsqLLX7z62qLy/iLXYEftKzvpbf73302zZVtR64FthuVpunA9+Zq7CU5OAka5KsWbdu3SJClSR14JYkz06yaZJNkjwbuGXcQUmShseeS5K0kUvyz/Ptr6qXLfYt5jrt7WmT5IE0j8o9ca43qKpVwCqAFStWbHB8D0nSWPwVzbh776b5bP9Gu02SNCX6Li4luTO/G+T1h1V1a2dRSZJG6ez25yOB3YAT2vVn9OxbjLXAzj3rOwGXb6DN2iSbAdsAVwMk2Qn4NPC8qvrvIcQjSRqhqvohzePPkqQpNW9xKck2wEuBA2hm6llHM8jr3ZKcCby/qk7rPEpJUmeq6lj47cDej62qm9v1o4EvDuEtvg3smuRewP8A+/P731ivBg4EvgnsB3ylqirJtsB/AIdX1TeGEIskacSSbEkzccMDae4lAKiqvx5bUJKkoVpozKUTacbA+JOqun9VPaqqVlTVzjRTQ69MclDnUUqSRuEe3Hb2nju12xalHUPpEJqZ3i4CPlFVFyQ5Ksm+bbOPANsluRR4JXBYu/0Q4L7APyY5t33ddbExSZJG6jjg7sCTgK/S9GC9fqwRSZKGat6eS1X1hHn2nc1wHpeQJC0NbwG+k2SmR+qf8rsZ3Balqk4GTp617fU9yzfSPIY3+7g3AW8aRgySpLG5b1U9I8nKqjo2yb/RfOEgSZoSCz0W95D59lfVOcMNR5I0LlX1f5N8HpiZLvqwqvrpOGOSJE2Fm9ufv0iyO/BTYPn4wpEkDdtCA3q/o/25JbAC+C7NjD4PAs4CHtVdaJKkMbgJuILmc/9+Se5XVWeMOSZJ0mRb1U4O9A80Y+zdCfjH8YYkSRqmhR6LeyxAko8DB1fVee367sCruw9PkjQqSV4IHEozFsa5wMNpBth+3DjjkiRNriSbANdV1TXAGcC9xxySJKkDCw3oPeMBM4UlgKo6H9ijm5AkSWNyKPAw4Eftlwt70swSKknSQKrqVprJGSRJU2yhx+JmXJTkw8C/AAU8h2bGH0nS9Lixqm5MQpItquriJPcfd1CSpIl3apJXAycAv5zZWFVXjy8kSdIw9VtcegHwtzTfakPTpfUDnUQkSRqXtUm2BU6iuRG4Brh8zDFJkibfX7c/X9qzrfAROUmaGn0Vl9opot/VviRJU6iq/qJdPDLJacA2wBfGGJIkaQpU1b3GHYMkqVt9jbmUZNckJya5MMllM68Fjtk5yWlJLkpyQZJD52iTJP+c5NIk30vykEF/EUnS4JJskuT8mfWq+mpVra6q34wzLknS5Eoy78zSSbZuJwra0H7vJyRpQvT7WNz/BY6g6bn0WJrH5LLAMeuBV1XVOUm2As5OcmpVXdjT5snAru1rb5pH7fa+HfFLkoagqm5N8t0ku1TVj8cdjyRpKjw9ydtoesGeTTNJxJbAfWnuKe4JvGqe472fkKQJ0W9x6Y5V9eUkqaof0Twy8TWagtOcquoK4Ip2+fokFwE7Ar3JYCXwsaoq4Mwk2ybZoT1WkjRaOwAXJPkWtx1wdd/xhSRJmlRV9Yokdwb2A55Bk2d+TTMx0Aer6usLHO/9hCRNiH6LSzcm2QT4fpJDgP8B7trvmyRZTjOl9Vmzdu0I/KRnfW277TbJIMnBwMEAu+yyS79vK0m6fd4w7gAkSdOlqq4BPtS+Bub9hCQtbf0Wl14O/AHwMuCNNN1YD+znwCR3Aj4JvLyqrpu9e45D6vc2VK0CVgGsWLHi9/ZLkgbX9kqtqvrqQm1GGZckSeD9hCRNggUH9E6yKfDMqrqhqtZW1Quq6ulVdWYfx25Okwj+tao+NUeTtcDOPes74bTXkjRqpyX5uyS3+So3yR2SPC7JsfT5hYIkScPk/YQkTYYFi0tVdQvw0CQLDeB9G237jwAXVdU7N9BsNfC8dpaHhwPX+ny0JI3cPsAtwPFJLp+ZGRT4PnAA8K6q+ug4A5QkbXy8n5CkydHvY3HfAT6T5N+57SCvc317MOORwHOB85Kc2257HbBLe+zRwMnAU4BLgV/RzEInSRqhqroReD/w/vYb4u2BX1fVL8YbmSRpWiT5X8Byeu4/qupjCxzm/YQkTYh+i0t3Aa4CHtezrYANFpfa2R/m7e3Ujt/x0j5jkCR1rKpuZtYgqJIkLUaS44D7AOfS9JSF5l5i3uKS9xOSNDn6Ki5Vld8ASJIkSRrECmA3J4aQpOk175hLSf4hyV3m2f+4JH8+/LAkSZIkTYnzgbuPOwhJUncW6rl0HvDZJDcC5wDrgC2BXYE9gC8B/2+nEUqSRibJPYFdq+pLSe4IbFZV1487LknSRNseuDDJt4CbZjZW1b7jC0mSNEzzFpeq6jM0A3nvSjOg3g7AdcC/AAdX1a+7D1GSNApJXgQcTDPO3n1opnM+Gnj8OOOSJE28I8cdgCSpW/2OufR9mimpJUnT66XAXsBZ0Hz2J7nreEOSJE26qvpqkrsBD2s3fauqrhxnTJKk4Zp3zCVJ0kblpqr6zcxKks1oZvORJGlgSZ4JfAt4BvBM4Kwk+403KknSMPXVc0mStFH4apLXAXdM8gTgJcBnxxyTJGny/T3wsJneSkmW0YzdeuJYo5IkDY09lyRJMw6jmbjhPOBvgJOBfxhrRJKkabDJrMfgrsL7EEmaKn31XEryNuBNwK+BLwAPBl5eVf/SYWySpBGqqluBD7UvSZKG5QtJTgGOb9efRfMFhiRpSvT7WNwTq+r/SfIXwFqa56VPo5k1TpI0wZKcxzxjK1XVg0YYjiRpylTVa5I8nWb26QCrqurTYw5LkjRE/RaXNm9/PgU4vqquTtJRSJKkEfvzcQcgSZpuVfVJ4JPjjkOS1I1+i0ufTXIxzWNxL2kH4buxu7AkSaNSVT8adwySpOmT5OtV9agk13PbHrIBqqq2HlNokqQh66u4VFWHJXkrcF1V3ZLkl8DKbkOTJI3SHH/8A1wLrAFeVVWXjT4qSdKkqqpHtT+3GncskqRu9dtzCeCPgOVJeo/52JDjkSSNzzuBy4F/o/lWeX/g7sAlwDHAY8YWmSRpYiU5rqqeu9A2SdLk6ne2uOOA+wDnAre0mwuLS5I0Tfapqr171lclObOqjkryurFFJUmadA/sXWm/rH7omGKRJHWg355LK4DdqmqDswlJkiberUmeCZzYru/Xs8/Pf0nS7ZLkcOB1wB2TXDezGfgNsGpsgUmShm6TPtudT/NohCRpej0beC5wJfCzdvk5Se4IHDLOwCRJk6eq3tyOt/T2qtq6fW1VVdtV1eHjjk+SNDz99lzaHrgwybeAm2Y2VtW+nUQlSRq5dsDup25g99dHGYskaXpU1eFJ7gzsCmzZs/2M8UUlSRqmfotLR3YZhCRp/JIsA14ELKcnP1TVX48rJknS5EvyQuBQYCeaMVwfDnwTeNw445IkDU9fxaWq+mqSuwEPazd9q6qu7C4sSdIYfAb4GvAlfjd5gyRJi3UozX3EmVX12CQPAN4w5pgkSUPU72xxzwTeDpxOMwjfe5K8pqpOnPdASdIk+YOqeu24g5AkTZ0bq+rGJCTZoqouTnL/cQclSRqefh+L+3vgYTO9ldpHJ77E72YUkiRNvs8leUpVnTzuQCRJU2Vtkm2Bk4BTk1wDXD7mmCRJQ9RvcWmTWY/BXUX/M81JkibDocDrktwE3EzTU7WqauvxhiVJmmRV9Rft4pFJTgO2AT4/xpAkSUPWb3HpC0lOAY5v158F+M22JE2RdrpoSZKGKslxVfVcaMZyndkGPHesgUmShqbfAb1fk+TpwCNpvsleVVWf7jQySdLYJLkPsD9wQFXtPu54JEkT7YG9K0k2BR46plgkSR3o+9G2qvpkVb2yql5hYUmSpk+SHZK8Ism3gAtovoA4YEjn3ifJJUkuTXLYHPu3SHJCu/+sJMt79h3ebr8kyZOGEY8kqXvt5/f1wIOSXJfk+nb9SpoZSiVJU2Le4lKSr7c/r28TwnU9ieG60YQoSepSkhcl+QrwVWA74IXAFVX1hqo6bwjn3xR4H/BkYDfggCS7zWp2EHBNVd0XeBfw1vbY3Wh6UD0Q2Ad4f3s+SdISV1Vvbh+5fntVbV1VW7Wv7arq8HHHJ0kannkfi6uqR7U/HYdDkqbX+4BvAn9VVWsAktQQz78XcGlVXdae++PASuDCnjYrgSPb5ROB9yZJu/3jVXUT8IMkl7bn++aG3uyydb/kWR/c4G5J0ohV1eFJ9gUe3W46vao+N86YJEnD1ddjce2AewtukyRNpHsAHwfe2T569kZg8yGef0fgJz3ra9ttc7apqvXAtTS9qPo5liQHJ1mTZM3NN988xNAlSYuV5M00M5Je2L4ObbdJkqZEv7PFzR6EbzMchE+SpkJV/Rz4APCBJDvRPIZ2ZZKLgE9X1esW+RaZ6237bNPPsVTVKmAVwIoVK+qEv3nE7Y1RkqbeJ148trf+38AeVXUrQJJjge8APhonSVNioTGXZg/Cd127/jMWGIQvyTFJrkxy/gb2PybJtUnObV+vH/i3kCQNRVWtrap/qqqHAk8DbhrCadcCO/es7wRcvqE27RcY2wBX93msJGnp27ZneZt+DvB+QpImx7zFpTkG4dv6dgzC91GawVfn87Wq2qN9HXU74pYkdayqLqmqNwzhVN8Gdk1yryR3oOkZtXpWm9XAge3yfsBXqqra7fu3s8ndC9gV+NYQYpIkjc6bge8k+Wjba+nsdttCPor3E5I0Efp6LK4dhO/ONH/Ub9mz/Yx5jjmjdyppSdLGqarWJzkEOAXYFDimqi5IchSwpqpWAx8BjmsH7L6apgBF2+4TNGN0rAdeWlW3jOUXkSQNpKqOT3I68DCax51fW1U/7eM47yckaUL0VVxK8kKaQfh2As4FHk4zU8/jFvn+j0jyXZpHHF5dVRds4P0PBg4G2GWXXRb5lpKkUauqk4GTZ217fc/yjcAzNnDs/wH+T6cBSpI6VVVX0PZaTXL/JG+sqhcN4dTeT0jSEtDXbHE0haWHAT+qqscCewLrFvne5wD3rKoHA+8BTtpQw6paVVUrqmrFsmXLFvm2kqReSR4y32vc8UmSJlOSByX5YpLzk7wpyd2SfBL4Mk2P1MXyfkKSloh+Z4u7sapuTEKSLarq4iT3X8wbV9V1PcsnJ3l/ku3bWYskSaPzjnn2FYvvpSpJ2jh9iGY20m/SjJ10DvBvwLPbHquL4v2EJC0d/RaX1ibZlubbgFOTXMMiZ+tJcnfgZ1VVSfai6UV11WLOKUm6/doeqZIkDdsWVfXRdvmSJK8GDhvW2HneT0jS0tHvgN5/0S4emeQ0mulDPz/fMUmOBx4DbJ9kLXAEsHl7vqNpZgP62yTrgV8D+7czA0mSxiTJ7sBu3Hbyho+NLyJJ0gTbMsmeNIN4A9wAPChJAKrqnPkO9n5CkiZHvwN6H1dVzwWoqq/ObAOeu6FjquqA+c5ZVe8F3tt/qJKkLiU5guaP+N1oBt9+MvB1wOKSJGkQVwDv7Fn/ac/6go9dez8hSZOj38fiHti7kmRT4KHDD0eSNEb7AQ8GvlNVL0hyN+DDY45JkjShfOxakjYe884Wl+TwJNfTdF+9Lsn17fqVwGdGEqEkaVR+XVW3AuuTbE3zWX/vMcckSZIkaYmbt7hUVW+uqq2At1fV1lW1VfvarqoOH1GMkqTRWNNO3vAh4GyaWX2+Nd6QJEmSJC11/Q7ofXiSfYFHt5tOr6rPdReWJGnUquol7eLRSb4AbF1V3xtnTJIkSZKWvn4H9H4zsBfwr+2mQ5M80t5LkjRdkuwI3JM2PyR5dFWdMd6oJEmTLMlD5th8LfCjqlo/6ngkScPX74De/xvYox2LgyTHAt8BLC5J0pRI8lbgWcCFwC3t5gIsLkmSFuP9wEOA7wEBdm+Xt0vy4qr64jiDkyQtXr/FJYBtgavb5W06iEWSNF5PA+5fVTeNOxBJ0lT5IXBQVV0AkGQ34DXAG4FPARaXJGnC9VtcejPwnSSn0Xzb8GjgdZ1FJUkah8uAzQGLS5KkYXrATGEJoKouTLJnVV2WZJxxSZKGpN8BvY9PcjrwMJri0mur6qddBiZJGo0k76F5/O1XwLlJvkxPgamqXjau2CRJU+GSJB8APt6uPwv4ryRbADePLyxJ0rD0/VhcVV0BrAZIcv8kb6yqF3UWmSRpVNa0P8+m/ZzvUSOORZI0fZ4PvAR4Oc0X1V8HXk1TWHrs+MKSJA3LvMWlJA8C/gm4B3AS8B6aAfn2Bt7ReXSSpM5V1bEASQ6tqnf37kty6HiikiRNi6r6Nc29w1z3DzeMOBxJUgc2WWD/h4B/A54OrAPOoRmT475V9a6OY5MkjdaBc2x7/qiDkCRNlySPTHJqkv9KctnMa9xxSZKGZ6HH4raoqo+2y5ckeTVwWFXdMs8xkqQJkuQA4K+AeyXpfSxuK+Cq8UQlSZoiHwFeQfP4tfcRkjSFFioubZlkT5pno6HptvqgtNM6VNU5XQYnSRqJ/wSuALbnto8sXA98bywRSZKmybVV9flxByFJ6s5CxaUrgHf2rP+0Z72Ax3URlCRpdKrqR8CPgEeMOxZJ0lQ6LcnbgU9x29lI/aJakqbEvMWlqnL2BknaSCS5nt/NDncHYHPgl1W19fiikiRNgb3bnyt6tvlFtSRNkYV6LkmSNhJVtVXvepKnAXuNKRxJ0pTwC2tJmn4WlyRJc6qqk5IcNu44JEmTKclzqupfkrxyrv1V9c65tkuSJo/FJUkSAEn+smd1E5rHF2oDzSVJWsgftj+3mreVJGnizVtcSvKAqro4yUPm2u8gfJI0VZ7as7we+CGwcjyhSJImXVV9sP35hnHHIknq1kI9l14JHMxtp6ae4SB8kjRFquoF445BkjR9kiwDXgQsp+f+o6r+elwxSZKGa6HZ4g5ufzoInyRNuST3Av6O3//jf99xxSRJmgqfAb4GfAm4ZcyxSJI60NeYS0m2BF4CPIqmx9LXgKOr6sYOY5MkjdZJwEeAzwK3jjkWSdL0+IOqeu24g5AkdaffAb0/BlwPvKddPwA4DnhGF0FJksbixqr653EHIUmaOp9L8pSqOnncgUiSutFvcen+VfXgnvXTkny3i4AkSWPz7iRHAF8EbprZ6OQNkqRFOhR4XZKbgJuBAFVVW483LEnSsPRbXPpOkodX1ZkASfYGvtFdWJKkMfhj4Lk0kzXMPBbn5A2SpEWpqq3GHYMkqVvzFpeSnEdzY7E58LwkP27X7wlc2H14kqQR+gvg3lX1m3EHIkmaHkkePdf2qjpj1LFIkrqxUM+lPx9JFJKkpeC7wLbAleMORJI0VV7Ts7wlsBdwNvaMlaSpMW9xqap+1Lue5K40CUGSNH3uBlyc5NvcdsylfccXkiRp0lXVU3vXk+wMvG1M4UiSOtDXmEtJ9gXeAdyD5hvtewIXAQ/sLjRJ0ogdMe4AJEkbhbXA7uMOQpI0PP0O6P1G4OHAl6pqzySPBQ6Y74Akx9A8VndlVf1e8kgS4N3AU4BfAc93RiJJGp+q+uq4Y5AkTZ8k76EZtxVgE2APmkexFzrO+wlJmhCb9Nnu5qq6CtgkySZVdRpNUpjPR4F95tn/ZGDX9nUw8IE+Y5EkDVGSr7c/r09yXc/r+iTXjTs+SdLEW0MzxtLZwDeB11bVc/o47qN4PyFJE6Hfnku/SHIn4AzgX5NcCayf74CqOiPJ8nmarAQ+VlUFnJlk2yQ7VNUVfcYkSRqCqnpU+9OpoiVJXdi2qt5Qcr5pAAAUp0lEQVTduyHJobO3zeb9hCRNjn57Lq2k6Wr6CuALwH8DT533iIXtCPykZ31tu02SNAZJPpJkj1nbjhxTOJKk6XHgHNueP4Tzej8hSUtEX8WlqvplVd1aVeur6ljgfczfRbUfmeut5myYHJxkTZI169atW+TbSpI24EnAR5P03gQ4U5wkaSBJDkjyWeBeSVb3vE4DrhrGW8yxzfsJSRqDeYtLSbZOcniS9yZ5YhqHAJcBz1zke68Fdu5Z3wm4fK6GVbWqqlZU1Yply5Yt8m0lSRtwJfBoYL8k70uyGXP/4d63JHdJcmqS77c/77yBdge2bb4/U9xK8gdJ/iPJxUkuSPKWxcQiSRq5/6SZcfri9ufM61Us/otq8H5CkpaMhXouHQfcHzgPeCHwReAZwMqqWrnI914NPK8tWD0cuNbnoyVprFJV11XVU4F1wFeBbRZ5zsOAL1fVrsCX2/XbvmlyF+AIYG9gL+CIniLUP1XVA4A9gUcmefIi45EkjUhV/aiqTq+qRwA/BDZvZya9CLjjEN7C+wlJWiIWGtD73lX1xwBJPgz8HNilqq5f6MRJjgceA2yfZC3NjcPmAFV1NHAyzbShl9KM5/SCAX8HSdJwrJ5ZqKojk6yhGWtvMVbS5AKAY4HTgdfOavMk4NSquhogyanAPlV1PHBaG89vkpxD8620JGmCJHkRzWxudwHuQ/NZfjTw+AWO835CkibEQsWlm2cWquqWJD/op7DUtj9ggf0FvLSfc0mSuldVR8zadA3NowyLcbeZb5Gr6ookd52jzYIDsibZlmYiiTlnFkpyMM2NC7vssssiQ5YkDdlLaXqmngVQVd/fQD64De8nJGlyLFRcenCS69rlAHds10Pzeb51p9FJkkaqnS3ur2jG1fsB8Mk+jvkScPc5dv19v287x7bfDsjajv10PPDPVXXZXCeoqlXAKoAVK1bMOZirJGlsbmp7oAK//Vz3s1qSpsi8xaWq2nRUgUiSxiPJ/YD9gQNoZu85gWb8pcf2c3xV/dk85/5Zkh3aXks70AwaPttafvfoHDSPS5zes74K+H5V/X/9xCNJWnK+muR1NF9UPwF4CfDZMcckSRqihQb0liRNv4tpxr14alU9qqreA9wypHOvBg5slw8EPjNHm1OAJya5czuQ9xPbbSR5E82g4i8fUjySpNE7jGaiiPOAv6EZK+kfxhqRJGmoFnosTpI0/Z5O03PptCRfAD7O3I+qDeItwCeSHAT8mGbGUZKsAF5cVS+sqquTvBH4dnvMUe22nWgerbsYOKd9nOK9VfXhIcUmSRqBqro1yUnASVW1btzxSJKGz+KSJG3kqurTwKeT/CHwNJoZ4u6W5APAp6vqi4s491XMMRtQVa0BXtizfgxwzKw2axlekUuSNGJpvhU4AjiE5vM8SW4B3lNVR401OEnSUPlYnCQJgKr6ZVX9a1X9Oc24R+fSPMogSdIgXg48EnhYVW1XVXcB9gYemeQV4w1NkjRMFpckSb+nqq6uqg9W1ePGHYskaWI9Dzigqn4ws6Gd9fM57T5J0pSwuCRJkiSpC5tX1c9nb2zHXdp8DPFIkjpicUmSJElSF34z4D5J0oRxQG9JkiRJXXhwkuvm2B5gy1EHI0nqjsUlSZIkSUNXVZuOOwZJ0mj4WJwkSZIkSZIGZnFJkiRJkiRJA7O4JEmSJEmSpIFZXJIkSZIkSdLALC5JkiRJkiRpYBaXJEmSJEmSNDCLS5IkSZIkSRqYxSVJkiRJkiQNzOKSJEmSJEmSBmZxSZIkSZIkSQOzuCRJkiRJkqSBWVySJEmSJEnSwCwuSZIkSZIkaWAWlyRJkiRJkjQwi0uSJEmSJEkamMUlSZIkSZIkDczikiRJkiRJkgZmcUmSJEmSJEkDs7gkSZIkSZKkgVlckiRJkiRJ0sA6LS4l2SfJJUkuTXLYHPufn2RdknPb1wu7jEeSJEnS5PB+QpImw2ZdnTjJpsD7gCcAa4FvJ1ldVRfOanpCVR3SVRySJEmSJo/3E5I0ObrsubQXcGlVXVZVvwE+Dqzs8P0kSZIkTQ/vJyRpQnRZXNoR+EnP+tp222xPT/K9JCcm2XmuEyU5OMmaJGvWrVvXRaySJEmSlhbvJyRpQnRZXMoc22rW+meB5VX1IOBLwLFznaiqVlXViqpasWzZsiGHKUmSJGkJ8n5CkiZEl8WltUDvNwc7AZf3Nqiqq6rqpnb1Q8BDO4xHkiRJ0uTwfkKSJkSXxaVvA7smuVeSOwD7A6t7GyTZoWd1X+CiDuORJEmSNDm8n5CkCdHZbHFVtT7JIcApwKbAMVV1QZKjgDVVtRp4WZJ9gfXA1cDzu4pHkiRJ0uTwfkKSJkdnxSWAqjoZOHnWttf3LB8OHN5lDJIkSZImk/cTkjQZunwsTpIkSZIkSVPO4pIkSZIkSZIGZnFJkiRJkiRJA7O4JEmSJEmSpIFZXJIkSZIkSdLALC5JkjqT5C5JTk3y/fbnnTfQ7sC2zfeTHDjH/tVJzu8+YkmSJEm3l8UlSVKXDgO+XFW7Al9u128jyV2AI4C9gb2AI3qLUEn+ErhhNOFKkiRJur0sLkmSurQSOLZdPhZ42hxtngScWlVXV9U1wKnAPgBJ7gS8EnjTCGKVJEmSNACLS5KkLt2tqq4AaH/edY42OwI/6Vlf224DeCPwDuBXXQYpSZIkaXCbjTsASdJkS/Il4O5z7Pr7fk8xx7ZKsgdw36p6RZLlC8RwMHAwwC677NLn20qSJEkaBotLkqRFqao/29C+JD9LskNVXZFkB+DKOZqtBR7Ts74TcDrwCOChSX5Ik6/umuT0qnrMrOOpqlXAKoAVK1bUYL+JJEmSpEH4WJwkqUurgZnZ3w4EPjNHm1OAJya5czuQ9xOBU6rqA1V1j6paDjwK+K+5CkuSJEmSxsvikiSpS28BnpDk+8AT2nWSrEjyYYCquppmbKVvt6+j2m2SJEmSJoCPxUmSOlNVVwGPn2P7GuCFPevHAMfMc54fArt3EKIkSZKkRbLnkiRJkiRJkgZmcUmSJEmSJEkDs7gkSZIkSZKkgVlckiRJkiRJ0sAsLkmSJEmSJGlgFpckSZIkSZI0MItLkiRJkiRJGpjFJUmSJEmSJA3M4pIkSZIkSZIGZnFJkiRJkiRJA7O4JEmSJEmSpIFZXJIkSZIkSdLALC5JkiRJkiRpYBaXJEmSJEmSNDCLS5IkSZIkSRqYxSVJkiRJkiQNrNPiUpJ9klyS5NIkh82xf4skJ7T7z0qyvMt4JEmSJE0O7yckaTJ0VlxKsinwPuDJwG7AAUl2m9XsIOCaqrov8C7grV3FI0mSJGlyeD8hSZNjsw7PvRdwaVVdBpDk48BK4MKeNiuBI9vlE4H3JklV1YZOetm6X/KsD36zm4g3Ml5HLSX+e5QkSbNMzP3EpPwdMylxDtPG+Dt3YWO9jpPwey+VGLt8LG5H4Cc962vbbXO2qar1wLXAdrNPlOTgJGuSrLn55ps7CleSJEnSEuL9hCRNiC57LmWObbO/QeinDVW1ClgFsGLFijrhbx6x+Ogkacp84sXjjkCSpKHyfkKSRmgx9xNd9lxaC+zcs74TcPmG2iTZDNgGuLrDmCRJkiRNBu8nJGlCdFlc+jawa5J7JbkDsD+welab1cCB7fJ+wFfmez5akiRJ0kbD+wlJmhCdPRZXVeuTHAKcAmwKHFNVFyQ5ClhTVauBjwDHJbmU5huG/buKR5IkSdLk8H5CkiZHl2MuUVUnAyfP2vb6nuUbgWd0GYMkSZKkyeT9hCRNhi4fi5MkSZIkSdKUs7gkSZIkSZKkgVlckiRJkiRJ0sAsLkmSJEmSJGlgFpckSZIkSZI0MItLkiRJkiRJGpjFJUmSJEmSJA0sVTXuGG6XJNcDl4w7jiVge+Dn4w5izLwGDa9Dw+sA96+qrcYdxLiZJ37L/xNegxleh4bXwTwBmCd6+H/CazDD69DwOiwiT2w27EhG4JKqWjHuIMYtyZqN/Tp4DRpeh4bXobkG445hiTBP4P8J8BrM8Do0vA7miR7mCfw/AV6DGV6HhtdhcXnCx+IkSZIkSZI0MItLkiRJkiRJGtgkFpdWjTuAJcLr4DWY4XVoeB28BjO8Dg2vg9dghteh4XXwGszwOjS8Dl6DGV6HhtdhEddg4gb0liRJkiRJ0tIxiT2XJEmSJEmStERYXJIkSZIkSdLAlmxxKck+SS5JcmmSw+bYv0WSE9r9ZyVZPvoou9fHdXhlkguTfC/Jl5Pccxxxdmmha9DTbr8klWQqp4/s5zokeWb77+GCJP826hhHoY//E7skOS3Jd9r/F08ZR5xdSnJMkiuTnL+B/Unyz+01+l6Sh4w6xlEwT5gjZpgnGuYJc8QM80TDPGGemGGeaJgnzBPQYY6oqiX3AjYF/hu4N3AH4LvAbrPavAQ4ul3eHzhh3HGP6To8FviDdvlvp+069HMN2nZbAWcAZwIrxh33mP4t7Ap8B7hzu37Xccc9puuwCvjbdnk34IfjjruD6/Bo4CHA+RvY/xTg80CAhwNnjTvmMf1bmOo8YY7o/zq07cwTU54nzBG3+T3NE+YJ88TtuA5tO/OEeWKjyBNd5Yil2nNpL+DSqrqsqn4DfBxYOavNSuDYdvlE4PFJMsIYR2HB61BVp1XVr9rVM4GdRhxj1/r5twDwRuBtwI2jDG6E+rkOLwLeV1XXAFTVlSOOcRT6uQ4FbN0ubwNcPsL4RqKqzgCunqfJSuBj1TgT2DbJDqOJbmTME+aIGeaJhnnCHPFb5gnAPAHmiRnmiYZ5wjwBdJcjlmpxaUfgJz3ra9ttc7apqvXAtcB2I4ludPq5Dr0OoqkwTpMFr0GSPYGdq+pzowxsxPr5t3A/4H5JvpHkzCT7jCy60ennOhwJPCfJWuBk4O9GE9qScns/OyaRecIcMcM80TBPmCNuD/PErDbmCcA8YZ4wT4B5AgbMEZt1Fs7izPWNQQ3QZtL1/TsmeQ6wAvjTTiMavXmvQZJNgHcBzx9VQGPSz7+FzWi6sj6G5lunryXZvap+0XFso9TPdTgA+GhVvSPJI4Dj2utwa/fhLRl+PvbfZpKZIxrmiYZ5whxxe0z75yOYJ8A8McM80TBPmCf6NdBn41LtubQW2LlnfSd+vzvab9sk2Yymy9p8XbsmUT/XgSR/Bvw9sG9V3TSi2EZloWuwFbA7cHqSH9I8E7p6Cgfh6/f/xGeq6uaq+gFwCU1ymCb9XIeDgE8AVNU3gS2B7UcS3dLR12fHhDNPmCNmmCca5glzxO1hnpjVxjxhnsA8MdPGPGGeGChHLNXi0reBXZPcK8kdaAbYWz2rzWrgwHZ5P+Ar1Y4+NUUWvA5tF84P0iSDaXsmFha4BlV1bVVtX1XLq2o5zbPi+1bVmvGE25l+/k+cRDMoI0m2p+nWetlIo+xeP9fhx8DjAZL8EU1CWDfSKMdvNfC8dqaHhwPXVtUV4w5qyMwT5ogZ5omGecIccXuYJxrmCfOEeeK2zBPmCRgwRyzJx+Kqan2SQ4BTaEZ0P6aqLkhyFLCmqlYDH6HponYpzTcM+48v4m70eR3eDtwJ+Pd2/MEfV9W+Ywt6yPq8BlOvz+twCvDEJBcCtwCvqaqrxhf18PV5HV4FfCjJK2i6bz5/yv5QJMnxNN2Vt2+fBz8C2Bygqo6meT78KcClwK+AF4wn0u6YJ8wRM8wTDfOEOaKXecI8AeaJGeaJhnnCPDGjqxyRKbtOkiRJkiRJGqGl+licJEmSJEmSJoDFJUmSJEmSJA3M4pIkSZIkSZIGZnFJkjqW5JgkVyY5f0jnuyXJue1roxiEUpKmmXlCkjSfScgTDugtSR1L8mjgBuBjVbX7EM53Q1XdafGRSZKWAvOEJGk+k5An7LmkjUqS7XoqtD9N8j896//Z0XvumeTD8+xfluQLXby3loaqOoNmiuPfSnKfJF9IcnaSryV5wJjCk9TDPKFxME9Ik8M8oXGYhDyx2TjfXBq1qroK2AMgyZHADVX1Tx2/7euAN80T07okVyR5ZFV9o+NYtHSsAl5cVd9PsjfwfuBxfR67ZZI1wHrgLVV1UldBShsb84SWEPOEtASZJ7SELKk8YXFJas10DUzyGOANwM9oEsengPOAQ4E7Ak+rqv9Osgw4GtilPcXLZ3+YJ9kKeFBVfbdd/1Pg3e3uAh5dVdcDJwHPBkwGG4EkdwL+F/DvSWY2b9Hu+0vgqDkO+5+qelK7vEtVXZ7k3sBXkpxXVf/dddzSxs48oVExT0iTyTyhUVmKecLikjS3BwN/RNP18DLgw1W1V5JDgb8DXk7zof6uqvp6kl2AU9pjeq0AegddezXw0qr6RvuBcGO7fQ3zfBuhqbMJ8Iuq2mP2jqr6FM0fIBtUVZe3Py9LcjqwJ+BNgzRa5gl1yTwhTT7zhLq05PKEYy5Jc/t2VV1RVTfR/Cf7Yrv9PGB5u/xnwHuTnAusBrZuv1notQOwrmf9G8A7k7wM2Laq1rfbrwTuMfxfQ0tRVV0H/CDJMwDSeHA/xya5c5KZbyW2Bx4JXNhZsJI2xDyhzpgnpKlgnlBnlmKesLgkze2mnuVbe9Zv5Xc9/jYBHlFVe7SvHdsuqb1+DWw5s1JVbwFeSNMd9syeQde2bNtqCiU5HvgmcP8ka5McRNNt+aAk3wUuAFb2ebo/Ata0x51G84y0Nw3S6JknNDTmCWkqmSc0NJOQJ3wsThrcF4FDgLcDJNmjqs6d1eYi4FUzK0nuU1XnAecleQTwAOBi4H7ctrurpkhVHbCBXfsMcK7/BP54cRFJGhHzhPpinpA2WuYJ9WUS8oQ9l6TBvQxYkeR7SS4EXjy7QVVdDGzT07315UnOb6vEvwY+325/LPAfowha/397d2yDMBAEAfC+QmqhEVISqqEhAhewJCROPjgJY+yZ+IPLVlrd6wA2IycAmJETHMZI8usZ4NDGGNeqWpI8Jm+eVXVJ8tpuMgD2QE4AMCMn+Ac2l+D77rX+c73yOUF6EwQApyUnAJiRE+yezSUAAAAA2mwuAQAAANCmXAIAAACgTbkEAAAAQJtyCQAAAIA25RIAAAAAbW/4i0/gK9E42QAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] }, - "metadata": { - "needs_background": "light" - }, + "metadata": {}, "output_type": "display_data" } ], @@ -426,9 +424,7 @@ "
" ] }, - "metadata": { - "needs_background": "light" - }, + "metadata": {}, "output_type": "display_data" } ], diff --git a/examples/creating_a_driven_control.ipynb b/examples/creating_a_driven_control.ipynb index cdd410bc..ba0b468b 100644 --- a/examples/creating_a_driven_control.ipynb +++ b/examples/creating_a_driven_control.ipynb @@ -100,18 +100,20 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 4, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "BB1 X-pi:\n", - "X Amplitudes: [+2, -0.5, +1.375, -0.5] x pi\n", - "Y Amplitudes: [+0, +1.936, -1.452, +1.936] x pi\n", - "Z Amplitudes: [+0, +0, +0, +0] x pi\n", - "Durations: [+0.2,+0.2,+0.4,+0.2] x 2.5s\n" + "ename": "TypeError", + "evalue": "unsupported format string passed to numpy.ndarray.__format__", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'BB1 X-pi'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m )\n\u001b[0;32m----> 9\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbb1_x\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/Dropbox/Q-Ctrl/Code/OpenControls/python-open-controls/qctrlopencontrols/driven_controls/driven_control.py\u001b[0m in \u001b[0;36m__str__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 590\u001b[0m pretty_amplitudes_str = ', '.join(\n\u001b[1;32m 591\u001b[0m [decimals_format_str.format(amplitude/np.pi).rstrip('0').rstrip('.')\n\u001b[0;32m--> 592\u001b[0;31m for amplitude in [self.amplitude_x, self.amplitude_y, self.detunings]]\n\u001b[0m\u001b[1;32m 593\u001b[0m )\n\u001b[1;32m 594\u001b[0m driven_control_string.append(\n", + "\u001b[0;32m~/Dropbox/Q-Ctrl/Code/OpenControls/python-open-controls/qctrlopencontrols/driven_controls/driven_control.py\u001b[0m in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 590\u001b[0m pretty_amplitudes_str = ', '.join(\n\u001b[1;32m 591\u001b[0m [decimals_format_str.format(amplitude/np.pi).rstrip('0').rstrip('.')\n\u001b[0;32m--> 592\u001b[0;31m for amplitude in [self.amplitude_x, self.amplitude_y, self.detunings]]\n\u001b[0m\u001b[1;32m 593\u001b[0m )\n\u001b[1;32m 594\u001b[0m driven_control_string.append(\n", + "\u001b[0;31mTypeError\u001b[0m: unsupported format string passed to numpy.ndarray.__format__" ] } ], @@ -225,19 +227,17 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 7, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] }, - "metadata": { - "needs_background": "light" - }, + "metadata": {}, "output_type": "display_data" } ], @@ -250,18 +250,18 @@ "# prepare the axes\n", "figure, (x_axis, y_axis, z_axis) = plt.subplots(1, 3, figsize=(20,5))\n", "\n", - "x_axis.fill_between(times, x_amplitudes, 0, alpha=0.15, color='#680cea')\n", + "x_axis.fill_between(times, rabi_rates, 0, alpha=0.15, color='#680cea')\n", "x_axis.plot(times, rabi_rates, color='#680cea')\n", "x_axis.set_xlabel('Time (s)')\n", "x_axis.set_ylabel('Rabi Rate (radHz)')\n", "\n", "y_axis.fill_between(times, azimuthal_angles, 0, alpha=0.15, color='#680cea')\n", - "y_axis.plot(times, y_amplitudes, color='#680cea')\n", + "y_axis.plot(times, azimuthal_angles, color='#680cea')\n", "y_axis.set_xlabel('Time (s)')\n", "y_axis.set_ylabel('Azimuthal Angle (rad)')\n", "\n", "z_axis.fill_between(times, detunings, 0, alpha=0.15, color='#680cea')\n", - "z_axis.plot(times, z_amplitudes, color='#680cea')\n", + "z_axis.plot(times, detunings, color='#680cea')\n", "z_axis.set_xlabel('Time (s)')\n", "z_axis.set_ylabel('Detuning (radHz)')\n", "\n", @@ -283,7 +283,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -298,7 +298,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -307,13 +307,13 @@ "text": [ "amplitude_x,amplitude_y,detuning,duration,maximum_rabi_rate\n", "\n", - "1.0,0.0,0.0,0.5,6.283185307179586\n", + "3.141592653589793,0.0,0.0,0.5,6.283185307179586\n", "\n", - "-0.2500000000000001,0.9682458365518541,0.0,0.5,6.283185307179586\n", + "-0.785398163397448,3.041834006980209,0.0,0.5,6.283185307179586\n", "\n", - "0.6875000000000002,-0.7261843774138904,0.0,1.0,6.283185307179586\n", + "4.319689898685962,-4.562751010470316,0.0,1.0,6.283185307179586\n", "\n", - "-0.2500000000000001,0.9682458365518541,0.0,0.5,6.283185307179586\n" + "-0.785398163397448,3.041834006980209,0.0,0.5,6.283185307179586\n" ] } ], @@ -350,19 +350,17 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 11, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] }, - "metadata": { - "needs_background": "light" - }, + "metadata": {}, "output_type": "display_data" } ], @@ -376,10 +374,11 @@ "custom_driven_control = DrivenControl(rabi_rates=_rabi_rates, \n", " azimuthal_angles=_azimuthal_angles,\n", " detunings=_detunings,\n", + " durations=_durations,\n", " name=_name)\n", "\n", "## let us plot and verify\n", - "formatted_plot_data = bb1_x.get_plot_formatted_arrays(coordinates='cylindrical')\n", + "formatted_plot_data = custom_driven_control.get_plot_formatted_arrays(coordinates='cylindrical')\n", "rabi_rates, azimuthal_angles, detunings, times = (formatted_plot_data['rabi_rates'],\n", " formatted_plot_data['azimuthal_angles'],\n", " formatted_plot_data['detunings'],\n", @@ -387,30 +386,23 @@ "\n", "figure, (x_axis, y_axis, z_axis) = plt.subplots(1, 3, figsize=(20,5))\n", "\n", - "x_axis.fill_between(times, x_amplitudes, 0, alpha=0.15, color='#680cea')\n", + "x_axis.fill_between(times, rabi_rates, 0, alpha=0.15, color='#680cea')\n", "x_axis.plot(times, rabi_rates, color='#680cea')\n", "x_axis.set_xlabel('Time (s)')\n", "x_axis.set_ylabel('Rabi Rate (radHz)')\n", "\n", "y_axis.fill_between(times, azimuthal_angles, 0, alpha=0.15, color='#680cea')\n", - "y_axis.plot(times, y_amplitudes, color='#680cea')\n", + "y_axis.plot(times, azimuthal_angles, color='#680cea')\n", "y_axis.set_xlabel('Time (s)')\n", "y_axis.set_ylabel('Azimuthal Angle (rad)')\n", "\n", "z_axis.fill_between(times, detunings, 0, alpha=0.15, color='#680cea')\n", - "z_axis.plot(times, z_amplitudes, color='#680cea')\n", + "z_axis.plot(times, detunings, color='#680cea')\n", "z_axis.set_xlabel('Time (s)')\n", "z_axis.set_ylabel('Detuning (radHz)')\n", "\n", "plt.show()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From dbba333ca6bd0efbdb073b9ccf93b0b3233f9a75 Mon Sep 17 00:00:00 2001 From: michaelhush Date: Thu, 9 May 2019 16:53:53 +1000 Subject: [PATCH 45/51] Removed unneccesary standard segment convertion. --- .../driven_controls/conversion.py | 99 ------------------- 1 file changed, 99 deletions(-) delete mode 100644 qctrlopencontrols/driven_controls/conversion.py diff --git a/qctrlopencontrols/driven_controls/conversion.py b/qctrlopencontrols/driven_controls/conversion.py deleted file mode 100644 index 1be90689..00000000 --- a/qctrlopencontrols/driven_controls/conversion.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright 2019 Q-CTRL Pty Ltd & Q-CTRL Inc -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -================ -driven_controls.conversion -================ -""" - -import numpy as np - -from qctrlopencontrols.globals import CARTESIAN, CYLINDRICAL -from qctrlopencontrols.exceptions import ArgumentsValueError - -def convert_to_standard_segments(transformed_segments, maximum_rabi_rate=None, - coordinates=CARTESIAN, dimensionless_rabi_rate=True): - """Converts the dimensionless segments of any type into dimension-full Cartesian segments - - Parameters - ---------- - transformed_segments : list - segments of pulse in [number_of_segments, 4] shape - maximum_rabi_rate : float - maximum rabi rate - coordinates : string - 'cartesian' or 'cylindrical' or 'polar' - defines the type of the transformed_segments supplied. - if 'cartesian' - the segments should be in - [amplitude_x, amplitude_y, amplitude_z,segment_duration] format - if 'cylindrical' - the segments should be in - [on_resonance_amplitude, azimuthal_angle, detuning, segment_duration] format - dimensionless_rabi_rate : boolean - if True, identifies if the transformed_segments are dimensionless - - Returns - ------ - numpy.ndarray - Same size as the input segment [number_of_segments, 4] - - The returned array will be equal to the dimension-full cartesian segments of the - transformed segments - - Raises - ------ - ArgumentsValueError - Raised when an argument is invalid. - """ - - # making a copy of the segments otherwise during the call from - # get_segments(.) the internal segments will be modified - transformed_segments = np.array(transformed_segments, dtype=np.float) - segments_copy = np.array(transformed_segments, dtype=np.float) - number_of_segments = len(segments_copy) - dimensionless_rabi_rate = bool(dimensionless_rabi_rate) - - # Raise error if dimensionless is True and maximum_rabi_rate is not a float - if dimensionless_rabi_rate: - if maximum_rabi_rate is None: - raise ArgumentsValueError('Maximum rate rate needs to be a valid float', - {'maximum_rabi_rate': maximum_rabi_rate, - 'dimensionless_rabi_rate': dimensionless_rabi_rate}, - extras={'segments': transformed_segments, - 'number_of_segments': number_of_segments, - 'coordinates': coordinates}) - maximum_rabi_rate = float(maximum_rabi_rate) - - # Raise error if segments are not in [number_of_segments, 4] format - segments_copy = np.array(segments_copy, dtype=np.float) - if segments_copy.shape != (number_of_segments, 4): - raise ArgumentsValueError('Segments must be of shape (number_of_segments,4).', - {'segments': transformed_segments}, - extras={'number_of_segments': number_of_segments}) - - if coordinates == CYLINDRICAL: - - # convert to cartesian - cos_theta = np.cos(transformed_segments[:, 1]) - sin_theta = np.sin(transformed_segments[:, 1]) - radius = transformed_segments[:, 0] - - segments_copy[:, 0] = radius * cos_theta - segments_copy[:, 1] = radius * sin_theta - - # if dimensionless, make the segments dimension-full - if dimensionless_rabi_rate: - segments_copy[:, 0:2] = segments_copy[:, 0:2] * maximum_rabi_rate - - return segments_copy From 148201663eca16572bef0bcdfa7a1805e6e889db Mon Sep 17 00:00:00 2001 From: Rajib Chakravorty Date: Thu, 9 May 2019 17:06:28 +1000 Subject: [PATCH 46/51] driven control pretty string fixed --- .../driven_controls/driven_control.py | 58 ++++--- tests/test_driven_controls.py | 155 +++++++++++++----- 2 files changed, 151 insertions(+), 62 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index 02f194c1..44d0a28f 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -346,6 +346,18 @@ def minimum_duration(self): return np.amin(self.durations) + @property + def duration(self): + """Returns the total duration of the control + + Returns + ------- + float + Total duration of the control + """ + + return np.sum(self.durations) + def _qctrl_expanded_export_content(self, file_type, coordinates): """Private method to prepare the content to be saved in Q-CTRL expanded format @@ -579,36 +591,38 @@ def __str__(self): """ driven_control_string = list() - # Specify the number of decimals for pretty string representation - decimals_format_str = '{:+.3f}' - if self.name is not None: driven_control_string.append('{}:'.format(self.name)) - # Format amplitudes - for axis in 'XYZ': - pretty_amplitudes_str = ', '.join( - [decimals_format_str.format(amplitude/np.pi).rstrip('0').rstrip('.') - for amplitude in [self.amplitude_x, self.amplitude_y, self.detunings]] - ) - driven_control_string.append( - '{} Amplitudes = [{}] x pi'.format(axis, pretty_amplitudes_str) - ) - # Format durations - total_duration = np.sum(self.durations) - pretty_durations_str = ','.join( - [decimals_format_str.format(duration/total_duration).rstrip('0').rstrip('.') - for duration in self.durations] - ) - driven_control_string.append( - 'Durations = [{}] x {}s'.format(pretty_durations_str, str(total_duration)) - ) + pretty_rabi_rates = [str(rabi_rate/self.maximum_rabi_rate) + if self.maximum_rabi_rate != 0 else '0' + for rabi_rate in list(self.rabi_rates)] + pretty_rabi_rates = ','.join(pretty_rabi_rates) + pretty_azimuthal_angles = [str(azimuthal_angle/np.pi) + for azimuthal_angle in self.azimuthal_angles] + pretty_azimuthal_angles = ','.join(pretty_azimuthal_angles) + pretty_detuning = [str(detuning/self.maximum_detuning) + if self.maximum_detuning != 0 else '0' + for detuning in list(self.detunings)] + pretty_detuning = ','.join(pretty_detuning) + + pretty_durations = [str(duration/self.duration) for duration in self.durations] + pretty_durations = ','.join(pretty_durations) + driven_control_string.append( + 'Rabi Rates = [{}] x {}'.format(pretty_rabi_rates, + self.maximum_rabi_rate)) + driven_control_string.append( + 'Azimuthal Angles = [{}] x pi'.format(pretty_azimuthal_angles)) + driven_control_string.append( + 'Detunings = [{}] x {}'.format(pretty_detuning, + self.maximum_detuning)) + driven_control_string.append('Durations = [{}] x {}'.format(pretty_durations, + self.duration)) driven_control_string = '\n'.join(driven_control_string) return driven_control_string - if __name__ == '__main__': pass diff --git a/tests/test_driven_controls.py b/tests/test_driven_controls.py index 130d2f88..8ca0b14e 100644 --- a/tests/test_driven_controls.py +++ b/tests/test_driven_controls.py @@ -23,7 +23,7 @@ from qctrlopencontrols.exceptions import ArgumentsValueError from qctrlopencontrols import DrivenControl -from qctrlopencontrols.globals import CARTESIAN, CYLINDRICAL + from qctrlopencontrols.driven_controls.constants import ( UPPER_BOUND_SEGMENTS, UPPER_BOUND_RABI_RATE, UPPER_BOUND_DETUNING_RATE, @@ -159,50 +159,125 @@ def test_plot_data(): assert np.allclose(plot_data['amplitudes_y'], y_amplitude) assert np.allclose(plot_data['detunings'], z_amplitude) -@pytest.mark.skip('save for later') -def test_dimensionless_segments(): - """ - Test the dimensionless amplitude and angle segments generated - """ - segments = [[1., 0., 0., np.pi / 2], - [0., 1., 0., np.pi / 2], - [1. / np.sqrt(2.), 0., 1. / np.sqrt(2.), np.pi / 2]] - - _on_resonance_amplitudes = np.array([1., 1., 1. / np.sqrt(2.)]) - _azimuthal_angles = np.array([0., np.pi / 2, 0.]) - _detunings = np.array([0, 0, 1. / np.sqrt(2.)]) - _durations = np.pi / 2. * np.array([1., 1., 1.]) - - amplitude_angle_segments = np.stack((_on_resonance_amplitudes, _azimuthal_angles, - _detunings, _durations), axis=1) - - driven_control = DrivenControl(segments=segments) - _max_rabi = driven_control.maximum_rabi_rate - - dimensionless_euclid = segments.copy() - dimensionless_euclid = np.array(dimensionless_euclid) - dimensionless_euclid[:, 0:2] = dimensionless_euclid[:, 0:2] / _max_rabi - - dimensionless_cylinder = amplitude_angle_segments.copy() - dimensionless_cylinder = np.array(dimensionless_cylinder) - dimensionless_cylinder[:, 0] = dimensionless_cylinder[:, 0] / _max_rabi +def test_pretty_print(): - transformed_euclidean = driven_control.get_transformed_segments(coordinates=CARTESIAN, - dimensionless_rabi_rate=False) + """Tests pretty output of driven control + """ - assert np.allclose(segments, transformed_euclidean) + _maximum_rabi_rate = 2*np.pi + _maximum_detuning = 1.0 + _rabi_rates = [np.pi, 2 * np.pi, np.pi] + _azimuthal_angles = [0, np.pi / 2, -np.pi / 2] + _detunings = [0, 1, 0] + _durations = [1., 1., 1.] - transformed_euclidean = driven_control.get_transformed_segments(coordinates=CARTESIAN, - dimensionless_rabi_rate=True) + driven_control = DrivenControl( + rabi_rates=_rabi_rates, + azimuthal_angles=_azimuthal_angles, + detunings=_detunings, + durations=_durations + ) - assert np.allclose(dimensionless_euclid, transformed_euclidean) + _pretty_rabi_rates = [str(_rabi_rate/_maximum_rabi_rate) + for _rabi_rate in _rabi_rates] + _pretty_azimuthal_angles = [str(azimuthal_angle/np.pi) + for azimuthal_angle in _azimuthal_angles] + _pretty_detunings = [str(detuning/_maximum_detuning) + for detuning in _detunings] + _pretty_durations = [str(duration/3.) for duration in _durations] + _pretty_rabi_rates = ','.join(_pretty_rabi_rates) + _pretty_azimuthal_angles = ','.join(_pretty_azimuthal_angles) + _pretty_detunings = ','.join(_pretty_detunings) + _pretty_durations = ','.join(_pretty_durations) + + _pretty_string = [] + _pretty_string.append('Rabi Rates = [{}] x {}'.format( + _pretty_rabi_rates, _maximum_rabi_rate)) + _pretty_string.append('Azimuthal Angles = [{}] x pi'.format( + _pretty_azimuthal_angles)) + _pretty_string.append('Detunings = [{}] x {}'.format( + _pretty_detunings, _maximum_detuning)) + _pretty_string.append('Durations = [{}] x 3.0'.format( + _pretty_durations)) + + _pretty_string = '\n'.join(_pretty_string) + + assert str(driven_control) == _pretty_string + + _maximum_rabi_rate = 0. + _maximum_detuning = 1.0 + _rabi_rates = [0., 0., 0.] + _azimuthal_angles = [0, np.pi / 2, -np.pi / 2] + _detunings = [0, 1, 0] + _durations = [1., 1., 1.] - transformed_cylindrical = driven_control.get_transformed_segments(coordinates=CYLINDRICAL, - dimensionless_rabi_rate=False) + driven_control = DrivenControl( + rabi_rates=_rabi_rates, + azimuthal_angles=_azimuthal_angles, + detunings=_detunings, + durations=_durations + ) - assert np.allclose(amplitude_angle_segments, transformed_cylindrical) + _pretty_rabi_rates = ['0', '0', '0'] + _pretty_azimuthal_angles = [str(azimuthal_angle / np.pi) + for azimuthal_angle in _azimuthal_angles] + _pretty_detunings = [str(detuning / _maximum_detuning) + for detuning in _detunings] + _pretty_durations = [str(duration / 3.) for duration in _durations] + _pretty_rabi_rates = ','.join(_pretty_rabi_rates) + _pretty_azimuthal_angles = ','.join(_pretty_azimuthal_angles) + _pretty_detunings = ','.join(_pretty_detunings) + _pretty_durations = ','.join(_pretty_durations) + + _pretty_string = [] + _pretty_string.append('Rabi Rates = [{}] x {}'.format( + _pretty_rabi_rates, _maximum_rabi_rate)) + _pretty_string.append('Azimuthal Angles = [{}] x pi'.format( + _pretty_azimuthal_angles)) + _pretty_string.append('Detunings = [{}] x {}'.format( + _pretty_detunings, _maximum_detuning)) + _pretty_string.append('Durations = [{}] x 3.0'.format( + _pretty_durations)) + + _pretty_string = '\n'.join(_pretty_string) + + assert str(driven_control) == _pretty_string + + _maximum_rabi_rate = 2 * np.pi + _maximum_detuning = 0. + _rabi_rates = [np.pi, 2 * np.pi, np.pi] + _azimuthal_angles = [0, np.pi / 2, -np.pi / 2] + _detunings = [0, 0., 0] + _durations = [1., 1., 1.] - transformed_cylindrical = driven_control.get_transformed_segments(coordinates=CYLINDRICAL, - dimensionless_rabi_rate=True) + driven_control = DrivenControl( + rabi_rates=_rabi_rates, + azimuthal_angles=_azimuthal_angles, + detunings=_detunings, + durations=_durations + ) - assert np.allclose(amplitude_angle_segments, transformed_cylindrical) + _pretty_rabi_rates = [str(_rabi_rate / _maximum_rabi_rate) + for _rabi_rate in _rabi_rates] + _pretty_azimuthal_angles = [str(azimuthal_angle / np.pi) + for azimuthal_angle in _azimuthal_angles] + _pretty_detunings = ['0', '0', '0'] + _pretty_durations = [str(duration / 3.) for duration in _durations] + _pretty_rabi_rates = ','.join(_pretty_rabi_rates) + _pretty_azimuthal_angles = ','.join(_pretty_azimuthal_angles) + _pretty_detunings = ','.join(_pretty_detunings) + _pretty_durations = ','.join(_pretty_durations) + + _pretty_string = [] + _pretty_string.append('Rabi Rates = [{}] x {}'.format( + _pretty_rabi_rates, _maximum_rabi_rate)) + _pretty_string.append('Azimuthal Angles = [{}] x pi'.format( + _pretty_azimuthal_angles)) + _pretty_string.append('Detunings = [{}] x {}'.format( + _pretty_detunings, _maximum_detuning)) + _pretty_string.append('Durations = [{}] x 3.0'.format( + _pretty_durations)) + + _pretty_string = '\n'.join(_pretty_string) + + assert str(driven_control) == _pretty_string \ No newline at end of file From 596d2bc29b785678cc3bfc1e32080afad12c4685 Mon Sep 17 00:00:00 2001 From: Rajib Chakravorty Date: Thu, 9 May 2019 17:25:36 +1000 Subject: [PATCH 47/51] added more validation tests for driven control inputs --- .../driven_controls/driven_control.py | 4 +- tests/test_driven_controls.py | 62 +++++++++++++++++-- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index 44d0a28f..75b1a4a4 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -120,9 +120,7 @@ def __init__(self, check_none_values = [(rabi_rates is None), (azimuthal_angles is None), (detunings is None), (durations is None)] - - all_are_none = all(value is None for value in check_none_values) - + 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.]) diff --git a/tests/test_driven_controls.py b/tests/test_driven_controls.py index 8ca0b14e..7e3b3621 100644 --- a/tests/test_driven_controls.py +++ b/tests/test_driven_controls.py @@ -66,16 +66,70 @@ def test_driven_controls(): assert driven_control.name == _name + driven_control = DrivenControl( + rabi_rates=None, + azimuthal_angles=_azimuthal_angles, + detunings=_detunings, + durations=_durations, + name=_name) + + assert np.allclose(driven_control.rabi_rates, np.array([0., 0., 0.])) + assert np.allclose(driven_control.durations, _durations) + assert np.allclose(driven_control.detunings, _detunings) + assert np.allclose(driven_control.azimuthal_angles, _azimuthal_angles) + + driven_control = DrivenControl( + rabi_rates=_rabi_rates, + azimuthal_angles=None, + detunings=_detunings, + durations=_durations, + name=_name) + + assert np.allclose(driven_control.rabi_rates, _rabi_rates) + assert np.allclose(driven_control.durations, _durations) + assert np.allclose(driven_control.detunings, _detunings) + assert np.allclose(driven_control.azimuthal_angles, np.array([0., 0., 0.])) + + driven_control = DrivenControl( + rabi_rates=_rabi_rates, + azimuthal_angles=_azimuthal_angles, + detunings=None, + durations=_durations, + name=_name) + + assert np.allclose(driven_control.rabi_rates, _rabi_rates) + assert np.allclose(driven_control.durations, _durations) + assert np.allclose(driven_control.detunings, np.array([0., 0., 0.])) + assert np.allclose(driven_control.azimuthal_angles, _azimuthal_angles) + + driven_control = DrivenControl( + rabi_rates=_rabi_rates, + azimuthal_angles=_azimuthal_angles, + detunings=_detunings, + durations=None, + name=_name) + + assert np.allclose(driven_control.rabi_rates, _rabi_rates) + assert np.allclose(driven_control.durations, np.array([1., 1., 1.])) + assert np.allclose(driven_control.detunings, _detunings) + assert np.allclose(driven_control.azimuthal_angles, _azimuthal_angles) + + driven_control = DrivenControl() + assert np.allclose(driven_control.rabi_rates, np.array([np.pi])) + assert np.allclose(driven_control.durations, np.array([1.])) + assert np.allclose(driven_control.detunings, np.array([0.])) + assert np.allclose(driven_control.azimuthal_angles, np.array([0.])) + with pytest.raises(ArgumentsValueError): _ = DrivenControl(rabi_rates=[-1]) - with pytest.raises(ArgumentsValueError): + _ = DrivenControl(detunings=[-1]) _ = DrivenControl(durations=[0]) - with pytest.raises(ArgumentsValueError): _ = DrivenControl(rabi_rates=[1.1 * UPPER_BOUND_RABI_RATE]) - with pytest.raises(ArgumentsValueError): _ = DrivenControl(detunings=[1.1 * UPPER_BOUND_DETUNING_RATE]) - with pytest.raises(ArgumentsValueError): _ = DrivenControl(rabi_rates=[1] * UPPER_BOUND_SEGMENTS + [1]) + _ = DrivenControl() + _ = DrivenControl(rabi_rates=[1, 2], azimuthal_angles=[1, 2, 3], + detunings=None, durations=None) def test_control_export(): From 748b1886ff87c96c95dc75e6eb117a4bc0d7db5c Mon Sep 17 00:00:00 2001 From: virginia-m Date: Thu, 9 May 2019 17:29:05 +1000 Subject: [PATCH 48/51] consolidating rabi rate definitions, fixing tests --- .../driven_controls/driven_control.py | 4 +- .../driven_controls/predefined.py | 103 ++++------ tests/test_driven_controls.py | 52 +---- tests/test_predefined_driven_controls.py | 177 +++++++++++++----- 4 files changed, 168 insertions(+), 168 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index 02f194c1..edab3da3 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -252,7 +252,7 @@ def amplitude_x(self): X-Amplitude of each segment """ - return (self.rabi_rates * np.cos(self.azimuthal_angles)) + return self.rabi_rates * np.cos(self.azimuthal_angles) @property def amplitude_y(self): @@ -264,7 +264,7 @@ def amplitude_y(self): Y-Amplitude of each segment """ - return (self.rabi_rates * np.sin(self.azimuthal_angles)) + return self.rabi_rates * np.sin(self.azimuthal_angles) @property def angles(self): diff --git a/qctrlopencontrols/driven_controls/predefined.py b/qctrlopencontrols/driven_controls/predefined.py index fd4b7fd6..c47d9b6e 100644 --- a/qctrlopencontrols/driven_controls/predefined.py +++ b/qctrlopencontrols/driven_controls/predefined.py @@ -233,7 +233,7 @@ def new_primitive_control( maximum_rabi_rate, rabi_rotation, azimuthal_angle) return DrivenControl( - rabi_rates=[rabi_rotation], + rabi_rates=[maximum_rabi_rate], azimuthal_angles=[azimuthal_angle], detunings=[0], durations=[rabi_rotation/maximum_rabi_rate], @@ -270,11 +270,13 @@ def new_wimperis_1_control( phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) - rabi_rates = [rabi_rotation, np.pi, 2 * np.pi, np.pi] + rabi_rotations = [rabi_rotation, np.pi, 2 * np.pi, np.pi] + + rabi_rates = [maximum_rabi_rate] * 4 azimuthal_angles = [azimuthal_angle, azimuthal_angle + phi_p, azimuthal_angle + 3 * phi_p, azimuthal_angle + phi_p] detunings = [0] * 4 - durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rotations] return DrivenControl( rabi_rates=rabi_rates, @@ -313,10 +315,12 @@ def new_solovay_kitaev_1_control( phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) - rabi_rates = [rabi_rotation, 2 * np.pi, 2 * np.pi] + rabi_rotations = [rabi_rotation, 2 * np.pi, 2 * np.pi] + + rabi_rates = [maximum_rabi_rate] * 3 azimuthal_angles = [azimuthal_angle, azimuthal_angle - phi_p, azimuthal_angle + phi_p] detunings = [0] * 3 - durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rotations] return DrivenControl( rabi_rates=rabi_rates, @@ -391,15 +395,12 @@ def degrees_to_radians(angle_in_degrees): phi_3 = phi_1 theta_2 = np.pi - angles = np.array([ - [theta_1, phi_1 + azimuthal_angle], - [theta_2, phi_2 + azimuthal_angle], - [theta_3, phi_3 + azimuthal_angle]]) + rabi_rotations = [theta_1, theta_2, theta_3] - rabi_rates = [theta_1, theta_2, theta_3] - azimuthal_angles = [azimuthal_angle + phi_1, azimuthal_angle + phi_2, azimuthal_angle + phi_1] + rabi_rates = [maximum_rabi_rate] * 3 + azimuthal_angles = [azimuthal_angle + phi_1, azimuthal_angle + phi_2, azimuthal_angle + phi_3] detunings = [0] * 3 - durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rotations] return DrivenControl( rabi_rates=rabi_rates, @@ -438,15 +439,13 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_control( # pylint: maximum_rabi_rate, rabi_rotation, azimuthal_angle) k = np.arcsin(np.sin(rabi_rotation / 2.) / 2.) - angles = np.array([ - [2. * np.pi + rabi_rotation / 2. - k, azimuthal_angle], - [2. * np.pi - 2. * k, np.pi + azimuthal_angle], - [rabi_rotation / 2. - k, azimuthal_angle]]) - rabi_rates = [rabi_rotation / 2. + 2 * np.pi - k, 2 * np.pi - 2 * k, rabi_rotation / 2. - k] + rabi_rotations = [rabi_rotation / 2. + 2 * np.pi - k, 2 * np.pi - 2 * k, rabi_rotation / 2. - k] + + rabi_rates = [maximum_rabi_rate] * 3 azimuthal_angles = [azimuthal_angle, azimuthal_angle + np.pi, azimuthal_angle] detunings = [0] * 3 - durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rotations] return DrivenControl( rabi_rates=rabi_rates, @@ -487,21 +486,16 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_contr phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) k = np.arcsin(np.sin(rabi_rotation / 2.) / 2.) - angles = np.array([ - [2. * np.pi + rabi_rotation / 2. - k, azimuthal_angle], - [2. * np.pi - 2. * k, np.pi + azimuthal_angle], - [rabi_rotation / 2. - k, azimuthal_angle], - [np.pi, phi_p + azimuthal_angle], - [2. * np.pi, 3 * phi_p + azimuthal_angle], - [np.pi, phi_p + azimuthal_angle]]) - - rabi_rates = [2 * np.pi + rabi_rotation / 2. - k, 2 * np.pi - 2 * k, - rabi_rotation / 2. - k, np.pi, 2 * np.pi, np.pi] + + rabi_rotations = [2 * np.pi + rabi_rotation / 2. - k, 2 * np.pi - 2 * k, + rabi_rotation / 2. - k, np.pi, 2 * np.pi, np.pi] + + rabi_rates = [maximum_rabi_rate] * 6 azimuthal_angles = [azimuthal_angle, azimuthal_angle + np.pi, azimuthal_angle, azimuthal_angle + phi_p, azimuthal_angle + 3 * phi_p, azimuthal_angle + phi_p] detunings = [0] * 6 - durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rotations] return DrivenControl( rabi_rates=rabi_rates, @@ -542,19 +536,15 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev phi_p = _get_transformed_rabi_rotation_wimperis(rabi_rotation) k = np.arcsin(np.sin(rabi_rotation / 2.) / 2.) - angles = np.array([ - [2. * np.pi + rabi_rotation / 2. - k, azimuthal_angle], - [2. * np.pi - 2. * k, np.pi + azimuthal_angle], - [rabi_rotation / 2. - k, azimuthal_angle], - [2. * np.pi, -phi_p + azimuthal_angle], - [2. * np.pi, phi_p + azimuthal_angle]]) - - rabi_rates = [2 * np.pi + rabi_rotation / 2. - k, 2 * np.pi - 2 * k, - rabi_rotation / 2. - k, 2 * np.pi, 2 * np.pi] + + rabi_rotations = [2 * np.pi + rabi_rotation / 2. - k, 2 * np.pi - 2 * k, + rabi_rotation / 2. - k, 2 * np.pi, 2 * np.pi] + + rabi_rates = [maximum_rabi_rate] * 5 azimuthal_angles = [azimuthal_angle, azimuthal_angle + np.pi, azimuthal_angle, azimuthal_angle - phi_p, azimuthal_angle + phi_p] detunings = [0] * 5 - durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rotations] return DrivenControl( rabi_rates=rabi_rates, @@ -564,7 +554,7 @@ def new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev **kwargs) -def new_corpse_in_scrofulous_control( # pylint: disable=invalid-name +def new_corpse_in_scrofulous_control( # pylint: disable=invalid-name rabi_rotation=None, azimuthal_angle=0., maximum_rabi_rate=2. * np.pi, @@ -639,14 +629,15 @@ def degrees_to_radians(angle_in_degrees): [2. * np.pi - 2. * k, np.pi + phi + azimuthal_angle], [theta / 2. - k, phi + azimuthal_angle]]) total_angles.append(angles) - + total_angles = np.vstack(total_angles) - rabi_rates = total_angles[:, 0] - azimuthal_angles = total_angles[:, 1] + rabi_rotations = total_angles[:, 0] + rabi_rates = [maximum_rabi_rate] * 9 + azimuthal_angles = total_angles[:, 1] detunings = [0] * 9 - durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rotations] return DrivenControl( rabi_rates=rabi_rates, @@ -703,28 +694,12 @@ def new_walsh_amplitude_modulated_filter_1_control( # pylint: disable=invalid-n 'rabi_rotation angle must be either pi, pi/2 or pi/4', {'rabi_rotation': rabi_rotation}) - rabi_rate_plus = maximum_rabi_rate - time_segment = theta_plus / rabi_rate_plus - rabi_rate_minus = theta_minus / time_segment - - segments = np.array([ - [rabi_rate_plus * np.cos(azimuthal_angle), - rabi_rate_plus * np.sin(azimuthal_angle), - 0., time_segment], - [rabi_rate_minus * np.cos(azimuthal_angle), - rabi_rate_minus * np.sin(azimuthal_angle), - 0., time_segment], - [rabi_rate_minus * np.cos(azimuthal_angle), - rabi_rate_minus * np.sin(azimuthal_angle), - 0., time_segment], - [rabi_rate_plus * np.cos(azimuthal_angle), - rabi_rate_plus * np.sin(azimuthal_angle), - 0., time_segment]]) - - rabi_rates = [rabi_rate_plus, rabi_rate_minus, rabi_rate_minus, rabi_rate_plus] + rabi_rotations = [theta_plus, theta_minus, theta_minus, theta_plus] + + rabi_rates = [maximum_rabi_rate] * 4 azimuthal_angles = [azimuthal_angle] * 4 detunings = [0] * 4 - durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rates] + durations = [rabi_rotation_ / maximum_rabi_rate for rabi_rotation_ in rabi_rotations] return DrivenControl( rabi_rates=rabi_rates, diff --git a/tests/test_driven_controls.py b/tests/test_driven_controls.py index 130d2f88..f0b83b3d 100644 --- a/tests/test_driven_controls.py +++ b/tests/test_driven_controls.py @@ -23,11 +23,9 @@ from qctrlopencontrols.exceptions import ArgumentsValueError from qctrlopencontrols import DrivenControl -from qctrlopencontrols.globals import CARTESIAN, CYLINDRICAL from qctrlopencontrols.driven_controls.constants import ( - UPPER_BOUND_SEGMENTS, UPPER_BOUND_RABI_RATE, UPPER_BOUND_DETUNING_RATE, - UPPER_BOUND_DURATION, LOWER_BOUND_DURATION) + UPPER_BOUND_SEGMENTS, UPPER_BOUND_RABI_RATE, UPPER_BOUND_DETUNING_RATE) def _remove_file(filename): @@ -158,51 +156,3 @@ def test_plot_data(): assert np.allclose(plot_data['amplitudes_x'], x_amplitude) assert np.allclose(plot_data['amplitudes_y'], y_amplitude) assert np.allclose(plot_data['detunings'], z_amplitude) - -@pytest.mark.skip('save for later') -def test_dimensionless_segments(): - """ - Test the dimensionless amplitude and angle segments generated - """ - segments = [[1., 0., 0., np.pi / 2], - [0., 1., 0., np.pi / 2], - [1. / np.sqrt(2.), 0., 1. / np.sqrt(2.), np.pi / 2]] - - _on_resonance_amplitudes = np.array([1., 1., 1. / np.sqrt(2.)]) - _azimuthal_angles = np.array([0., np.pi / 2, 0.]) - _detunings = np.array([0, 0, 1. / np.sqrt(2.)]) - _durations = np.pi / 2. * np.array([1., 1., 1.]) - - amplitude_angle_segments = np.stack((_on_resonance_amplitudes, _azimuthal_angles, - _detunings, _durations), axis=1) - - driven_control = DrivenControl(segments=segments) - _max_rabi = driven_control.maximum_rabi_rate - - dimensionless_euclid = segments.copy() - dimensionless_euclid = np.array(dimensionless_euclid) - dimensionless_euclid[:, 0:2] = dimensionless_euclid[:, 0:2] / _max_rabi - - dimensionless_cylinder = amplitude_angle_segments.copy() - dimensionless_cylinder = np.array(dimensionless_cylinder) - dimensionless_cylinder[:, 0] = dimensionless_cylinder[:, 0] / _max_rabi - - transformed_euclidean = driven_control.get_transformed_segments(coordinates=CARTESIAN, - dimensionless_rabi_rate=False) - - assert np.allclose(segments, transformed_euclidean) - - transformed_euclidean = driven_control.get_transformed_segments(coordinates=CARTESIAN, - dimensionless_rabi_rate=True) - - assert np.allclose(dimensionless_euclid, transformed_euclidean) - - transformed_cylindrical = driven_control.get_transformed_segments(coordinates=CYLINDRICAL, - dimensionless_rabi_rate=False) - - assert np.allclose(amplitude_angle_segments, transformed_cylindrical) - - transformed_cylindrical = driven_control.get_transformed_segments(coordinates=CYLINDRICAL, - dimensionless_rabi_rate=True) - - assert np.allclose(amplitude_angle_segments, transformed_cylindrical) diff --git a/tests/test_predefined_driven_controls.py b/tests/test_predefined_driven_controls.py index bbb39a98..11b440cf 100644 --- a/tests/test_predefined_driven_controls.py +++ b/tests/test_predefined_driven_controls.py @@ -63,8 +63,8 @@ def test_primitive_control_segments(): _rabi_rotation = 1.5 _azimuthal_angle = np.pi/2 _segments = [ - np.cos(_azimuthal_angle) * _rabi_rotation, - np.sin(_azimuthal_angle) * _rabi_rotation, + np.cos(_azimuthal_angle), + np.sin(_azimuthal_angle), 0., _rabi_rotation ] @@ -91,7 +91,7 @@ def test_primitive_control_segments(): control.durations[0] ] assert np.allclose(_segments, segments) - assert np.allclose(_rabi_rotation, control.maximum_rabi_rate) + assert np.allclose(_rabi_rate, control.maximum_rabi_rate) def test_wimperis_1_control(): @@ -99,36 +99,38 @@ def test_wimperis_1_control(): """ _rabi_rotation = np.pi _azimuthal_angle = np.pi/2 + _maximum_rabi_rate = 1 phi_p = np.arccos(-_rabi_rotation / (4 * np.pi)) _segments = np.array([ - [np.cos(_azimuthal_angle), np.sin(_azimuthal_angle), 0., _rabi_rotation], - [np.cos(phi_p + _azimuthal_angle), np.sin(phi_p + _azimuthal_angle), 0., np.pi], - [np.cos(3. * phi_p + _azimuthal_angle), - np.sin(3. * phi_p + _azimuthal_angle), 0., 2 * np.pi], - [np.cos(phi_p + _azimuthal_angle), np.sin(phi_p + _azimuthal_angle), 0., np.pi] + [np.cos(_azimuthal_angle), np.sin(_azimuthal_angle)], + [np.cos(phi_p + _azimuthal_angle), np.sin(phi_p + _azimuthal_angle)], + [np.cos(3. * phi_p + _azimuthal_angle), np.sin(3. * phi_p + _azimuthal_angle)], + [np.cos(phi_p + _azimuthal_angle), np.sin(phi_p + _azimuthal_angle)] ]) wimperis_control_1 = new_wimperis_1_control( rabi_rotation=_rabi_rotation, azimuthal_angle=_azimuthal_angle, - maximum_rabi_rate=1 + maximum_rabi_rate=_maximum_rabi_rate ) wimperis_control_2 = new_predefined_driven_control( rabi_rotation=_rabi_rotation, azimuthal_angle=_azimuthal_angle, - maximum_rabi_rate=1, + maximum_rabi_rate=_maximum_rabi_rate, scheme=BB1 ) + durations = [np.pi, np.pi, np.pi * 2, np.pi] + for control in [wimperis_control_1, wimperis_control_2]: segments = np.vstack(( - control.amplitude_x, control.amplitude_y, control.detunings, control.durations + control.amplitude_x, control.amplitude_y )).T - assert np.allclose(segments, _segments) - assert np.allclose(segments, _segments) - + assert np.allclose(segments, _segments) + assert np.allclose(control.durations, durations) + assert np.allclose(control.detunings, 0) def test_solovay_kitaev_1_control(): """Test the segments of the Solovay-Kitaev 1 (SK1) driven control @@ -139,9 +141,12 @@ def test_solovay_kitaev_1_control(): phi_p = np.arccos(-_rabi_rotation / (4 * np.pi)) _segments = [ - [np.cos(_azimuthal_angle), np.sin(_azimuthal_angle), 0., _rabi_rotation], - [np.cos(-phi_p + _azimuthal_angle), np.sin(-phi_p + _azimuthal_angle), 0., 2 * np.pi], - [np.cos(phi_p + _azimuthal_angle), np.sin(phi_p + _azimuthal_angle), 0., 2 * np.pi] + [np.cos(_azimuthal_angle), + np.sin(_azimuthal_angle)], + [np.cos(-phi_p + _azimuthal_angle), + np.sin(-phi_p + _azimuthal_angle)], + [np.cos(phi_p + _azimuthal_angle), + np.sin(phi_p + _azimuthal_angle)] ] sk1_control_1 = new_solovay_kitaev_1_control( @@ -157,8 +162,15 @@ def test_solovay_kitaev_1_control(): maximum_rabi_rate=1 ) - assert np.allclose(sk1_control_1.segments, _segments) - assert np.allclose(sk1_control_2.segments, _segments) + durations = [np.pi, 2 * np.pi, 2 * np.pi] + + for control in [sk1_control_1, sk1_control_2]: + segments = np.vstack(( + control.amplitude_x, control.amplitude_y + )).T + assert np.allclose(segments, _segments) + assert np.allclose(control.durations, durations) + assert np.allclose(control.detunings, 0) def test_scofulous_control(): """Test the segments of the SCROFULOUS driven control. @@ -173,9 +185,14 @@ def test_scofulous_control(): ) # Construct SCROFULOUS controls for target rotations pi/4, pi/2 and pi - pi_segments = new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( + scrofulous_pi = new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( rabi_rotation=np.pi, azimuthal_angle=0.5, maximum_rabi_rate=2*np.pi - ).segments + ) + + pi_segments = np.vstack(( + scrofulous_pi.amplitude_x, scrofulous_pi.amplitude_y, scrofulous_pi.detunings, + scrofulous_pi.durations + )).T _pi_segments = np.array([ [0.14826172, 6.28143583, 0., 0.5], @@ -184,9 +201,14 @@ def test_scofulous_control(): assert np.allclose(pi_segments, _pi_segments) - pi_on_2_segments = new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( + scrofulous_pi2 = new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( rabi_rotation=np.pi/2, azimuthal_angle=-0.5, maximum_rabi_rate=2*np.pi - ).segments + ) + + pi_on_2_segments = np.vstack(( + scrofulous_pi2.amplitude_x, scrofulous_pi2.amplitude_y, scrofulous_pi2.detunings, + scrofulous_pi2.durations + )).T _pi_on_2_segments = np.array([ [5.25211762, 3.44872124, 0., 0.32], @@ -195,9 +217,14 @@ def test_scofulous_control(): assert np.allclose(pi_on_2_segments, _pi_on_2_segments) - pi_on_4_segments = new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( + scrofulous_pi4 = new_short_composite_rotation_for_undoing_length_over_and_under_shoot_control( rabi_rotation=np.pi/4, azimuthal_angle=0, maximum_rabi_rate=2*np.pi - ).segments + ) + + pi_on_4_segments = np.vstack(( + scrofulous_pi4.amplitude_x, scrofulous_pi4.amplitude_y, scrofulous_pi4.detunings, + scrofulous_pi4.durations + )).T _pi_on_4_segments = np.array([ [1.78286387, 6.0249327, 0., 0.26861111], @@ -212,9 +239,14 @@ def test_corpse_in_scrofulous_control(): defined numerically as well. """ # Test pi and pi/2 rotations - pi_segments = new_corpse_in_scrofulous_control( + cs_pi = new_corpse_in_scrofulous_control( rabi_rotation=np.pi, azimuthal_angle=0.5, maximum_rabi_rate=2*np.pi - ).segments + ) + + pi_segments = np.vstack(( + cs_pi.amplitude_x, cs_pi.amplitude_y, cs_pi.detunings, + cs_pi.durations + )).T _pi_segments = np.array([ [0.14826172, 6.28143583, 0., 1.16666667], @@ -229,9 +261,14 @@ def test_corpse_in_scrofulous_control(): assert np.allclose(pi_segments, _pi_segments) - pi_on_2_segments = new_corpse_in_scrofulous_control( + cs_pi_on_2 = new_corpse_in_scrofulous_control( rabi_rotation=np.pi/2, azimuthal_angle=0.25, maximum_rabi_rate=np.pi - ).segments + ) + + pi_on_2_segments = np.vstack(( + cs_pi_on_2.amplitude_x, cs_pi_on_2.amplitude_y, cs_pi_on_2.detunings, + cs_pi_on_2.durations + )).T _pi_on_2_segments = np.array([ [0.74606697, 3.05171894, 0., 2.18127065], @@ -276,15 +313,24 @@ def test_corpse_control(): maximum_rabi_rate=1 ) - assert np.allclose(corpse_control_1.segments, _segments) - assert np.allclose(corpse_control_2.segments, _segments) + for control in [corpse_control_1, corpse_control_2]: + segments = np.vstack(( + control.amplitude_x, control.amplitude_y, control.detunings, control.durations + )).T + assert np.allclose(segments, _segments) + def test_cinbb_control(): """Test the segments of the CinBB (BB1 made up of CORPSEs) driven control """ - segments = new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( + cinbb = new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( rabi_rotation=np.pi/3, azimuthal_angle=0.25, maximum_rabi_rate=np.pi - ).segments + ) + + segments = np.vstack(( + cinbb.amplitude_x, cinbb.amplitude_y, cinbb.detunings, + cinbb.durations + )).T _segments = np.array([ [3.04392815, 0.77724246, 0., 2.08623604], @@ -296,9 +342,14 @@ def test_cinbb_control(): assert np.allclose(segments, _segments) - segments = new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( + cinbb = new_compensating_for_off_resonance_with_a_pulse_sequence_with_wimperis_control( rabi_rotation=np.pi/5, azimuthal_angle=-0.25, maximum_rabi_rate=np.pi - ).segments + ) + + segments = np.vstack(( + cinbb.amplitude_x, cinbb.amplitude_y, cinbb.detunings, + cinbb.durations + )).T _segments = np.array([ [3.04392815, -0.77724246, 0., 2.0506206], @@ -314,9 +365,14 @@ def test_cinbb_control(): def test_cinsk1_control(): """Test the segments of the CinSK1 (SK1 made up of CORPSEs) driven control """ - segments = new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( + cinsk = new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( rabi_rotation=np.pi/2, azimuthal_angle=0.5, maximum_rabi_rate=2*np.pi - ).segments + ) + + segments = np.vstack(( + cinsk.amplitude_x, cinsk.amplitude_y, cinsk.detunings, + cinsk.durations + )).T _segments = np.array([ [5.51401386, 3.0123195, 0., 1.06748664], @@ -327,9 +383,14 @@ def test_cinsk1_control(): assert np.allclose(segments, _segments) - segments = new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( + cinsk = new_compensating_for_off_resonance_with_a_pulse_sequence_with_solovay_kitaev_control( rabi_rotation=2*np.pi, azimuthal_angle=-0.5, maximum_rabi_rate=2*np.pi - ).segments + ) + + segments = np.vstack(( + cinsk.amplitude_x, cinsk.amplitude_y, cinsk.detunings, + cinsk.durations + )).T _segments = np.array([ [5.51401386, -3.0123195, 0., 1.5], @@ -350,40 +411,54 @@ def test_walsh_control(): rabi_rotation=0.3 ) # test pi rotation - pi_segments = new_walsh_amplitude_modulated_filter_1_control( + walsh_pi = new_walsh_amplitude_modulated_filter_1_control( rabi_rotation=np.pi, azimuthal_angle=-0.35, maximum_rabi_rate=2*np.pi - ).segments + ) + + pi_segments = np.vstack(( + walsh_pi.amplitude_x, walsh_pi.amplitude_y, walsh_pi.detunings, + walsh_pi.durations + )).T _pi_segments = np.array([ [5.90225283, -2.15449047, 0., 0.5], - [2.95112641, -1.07724523, 0., 0.5], - [2.95112641, -1.07724523, 0., 0.5], + [5.90225283, -2.15449047, 0., 0.25], + [5.90225283, -2.15449047, 0., 0.25], [5.90225283, -2.15449047, 0., 0.5]]) assert np.allclose(pi_segments, _pi_segments) # test pi/2 rotation - pi_on_2_segments = new_walsh_amplitude_modulated_filter_1_control( + walsh_pi_on_2 = new_walsh_amplitude_modulated_filter_1_control( rabi_rotation=np.pi/2, azimuthal_angle=0.57, maximum_rabi_rate=2*np.pi - ).segments + ) + + pi_on_2_segments = np.vstack(( + walsh_pi_on_2.amplitude_x, walsh_pi_on_2.amplitude_y, walsh_pi_on_2.detunings, + walsh_pi_on_2.durations + )).T _pi_on_2_segments = np.array([ [5.28981984, 3.39060816, 0., 0.39458478], - [3.08895592, 1.9799236, 0., 0.39458478], - [3.08895592, 1.9799236, 0., 0.39458478], + [5.28981984, 3.39060816, 0., 0.23041522], + [5.28981984, 3.39060816, 0., 0.23041522], [5.28981984, 3.39060816, 0., 0.39458478]]) assert np.allclose(pi_on_2_segments, _pi_on_2_segments) # test pi/4 rotation - pi_on_4_segments = new_walsh_amplitude_modulated_filter_1_control( + walsh_pi_on_4 = new_walsh_amplitude_modulated_filter_1_control( rabi_rotation=np.pi/4, azimuthal_angle=-0.273, maximum_rabi_rate=2*np.pi - ).segments + ) + pi_on_4_segments = np.vstack(( + walsh_pi_on_4.amplitude_x, walsh_pi_on_4.amplitude_y, walsh_pi_on_4.detunings, + walsh_pi_on_4.durations + )).T _pi_on_4_segments = np.array([ [6.05049612, -1.69408213, 0., 0.3265702], - [4.37116538, -1.22388528, 0., 0.3265702], - [4.37116538, -1.22388528, 0., 0.3265702], + [6.05049612, -1.69408213, 0., 0.2359298], + [6.05049612, -1.69408213, 0., 0.2359298], [6.05049612, -1.69408213, 0., 0.3265702]]) assert np.allclose(pi_on_4_segments, _pi_on_4_segments) From 98d85b9be39982061914688f243bba5e0b576850 Mon Sep 17 00:00:00 2001 From: virginia-m Date: Thu, 9 May 2019 17:35:34 +1000 Subject: [PATCH 49/51] fix minor linting issue in pretty print --- qctrlopencontrols/driven_controls/driven_control.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qctrlopencontrols/driven_controls/driven_control.py b/qctrlopencontrols/driven_controls/driven_control.py index c411a259..7ffda00e 100644 --- a/qctrlopencontrols/driven_controls/driven_control.py +++ b/qctrlopencontrols/driven_controls/driven_control.py @@ -611,14 +611,14 @@ def __str__(self): driven_control_string.append( 'Rabi Rates = [{}] x {}'.format(pretty_rabi_rates, - self.maximum_rabi_rate)) + self.maximum_rabi_rate)) driven_control_string.append( 'Azimuthal Angles = [{}] x pi'.format(pretty_azimuthal_angles)) driven_control_string.append( 'Detunings = [{}] x {}'.format(pretty_detuning, - self.maximum_detuning)) + self.maximum_detuning)) driven_control_string.append('Durations = [{}] x {}'.format(pretty_durations, - self.duration)) + self.duration)) driven_control_string = '\n'.join(driven_control_string) return driven_control_string From ce26d9948b65cf63543b244b9b1c8a16cf7b3367 Mon Sep 17 00:00:00 2001 From: Rajib Chakravorty Date: Thu, 9 May 2019 18:00:55 +1000 Subject: [PATCH 50/51] linted and tested --- .../dynamic_decoupling_sequences/driven_controls.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py b/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py index 9aaeb6bf..efec0122 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py @@ -258,9 +258,11 @@ def convert_dds_to_driven_controls( if np.allclose(pulse_start_ends, 0.0): # the original sequence should be a free evolution - control_segments = np.reshape( - np.array([0., 0., 0., sequence_duration]), (1, 4)) - return DrivenControl(segments=control_segments, **kwargs) + 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,)) From 08638041ad09fa6ae6ad1f9977f046bf41bb7235 Mon Sep 17 00:00:00 2001 From: Michael Hush Date: Thu, 9 May 2019 18:43:45 +1000 Subject: [PATCH 51/51] Updated jupyter notebook --- examples/creating_a_driven_control.ipynb | 94 ++++++++++++------------ 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/examples/creating_a_driven_control.ipynb b/examples/creating_a_driven_control.ipynb index ba0b468b..66df69f1 100644 --- a/examples/creating_a_driven_control.ipynb +++ b/examples/creating_a_driven_control.ipynb @@ -69,20 +69,18 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [ { - "ename": "TypeError", - "evalue": "unsupported format string passed to numpy.ndarray.__format__", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'Primitive X-pi'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m )\n\u001b[0;32m----> 9\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mprim\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m~/Dropbox/Q-Ctrl/Code/OpenControls/python-open-controls/qctrlopencontrols/driven_controls/driven_control.py\u001b[0m in \u001b[0;36m__str__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 590\u001b[0m pretty_amplitudes_str = ', '.join(\n\u001b[1;32m 591\u001b[0m [decimals_format_str.format(amplitude/np.pi).rstrip('0').rstrip('.')\n\u001b[0;32m--> 592\u001b[0;31m for amplitude in [self.amplitude_x, self.amplitude_y, self.detunings]]\n\u001b[0m\u001b[1;32m 593\u001b[0m )\n\u001b[1;32m 594\u001b[0m driven_control_string.append(\n", - "\u001b[0;32m~/Dropbox/Q-Ctrl/Code/OpenControls/python-open-controls/qctrlopencontrols/driven_controls/driven_control.py\u001b[0m in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 590\u001b[0m pretty_amplitudes_str = ', '.join(\n\u001b[1;32m 591\u001b[0m [decimals_format_str.format(amplitude/np.pi).rstrip('0').rstrip('.')\n\u001b[0;32m--> 592\u001b[0;31m for amplitude in [self.amplitude_x, self.amplitude_y, self.detunings]]\n\u001b[0m\u001b[1;32m 593\u001b[0m )\n\u001b[1;32m 594\u001b[0m driven_control_string.append(\n", - "\u001b[0;31mTypeError\u001b[0m: unsupported format string passed to numpy.ndarray.__format__" + "name": "stdout", + "output_type": "stream", + "text": [ + "Primitive X-pi:\n", + "Rabi Rates = [1.0] x 6.283185307179586\n", + "Azimuthal Angles = [0.0] x pi\n", + "Detunings = [0] x 0.0\n", + "Durations = [1.0] x 0.5\n" ] } ], @@ -104,16 +102,14 @@ "metadata": {}, "outputs": [ { - "ename": "TypeError", - "evalue": "unsupported format string passed to numpy.ndarray.__format__", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'BB1 X-pi'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m )\n\u001b[0;32m----> 9\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbb1_x\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m~/Dropbox/Q-Ctrl/Code/OpenControls/python-open-controls/qctrlopencontrols/driven_controls/driven_control.py\u001b[0m in \u001b[0;36m__str__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 590\u001b[0m pretty_amplitudes_str = ', '.join(\n\u001b[1;32m 591\u001b[0m [decimals_format_str.format(amplitude/np.pi).rstrip('0').rstrip('.')\n\u001b[0;32m--> 592\u001b[0;31m for amplitude in [self.amplitude_x, self.amplitude_y, self.detunings]]\n\u001b[0m\u001b[1;32m 593\u001b[0m )\n\u001b[1;32m 594\u001b[0m driven_control_string.append(\n", - "\u001b[0;32m~/Dropbox/Q-Ctrl/Code/OpenControls/python-open-controls/qctrlopencontrols/driven_controls/driven_control.py\u001b[0m in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 590\u001b[0m pretty_amplitudes_str = ', '.join(\n\u001b[1;32m 591\u001b[0m [decimals_format_str.format(amplitude/np.pi).rstrip('0').rstrip('.')\n\u001b[0;32m--> 592\u001b[0;31m for amplitude in [self.amplitude_x, self.amplitude_y, self.detunings]]\n\u001b[0m\u001b[1;32m 593\u001b[0m )\n\u001b[1;32m 594\u001b[0m driven_control_string.append(\n", - "\u001b[0;31mTypeError\u001b[0m: unsupported format string passed to numpy.ndarray.__format__" + "name": "stdout", + "output_type": "stream", + "text": [ + "BB1 X-pi:\n", + "Rabi Rates = [1.0,1.0,1.0,1.0] x 6.283185307179586\n", + "Azimuthal Angles = [0.0,0.5804306232551663,1.7412918697654987,0.5804306232551663] x pi\n", + "Detunings = [0,0,0,0] x 0.0\n", + "Durations = [0.2,0.2,0.4,0.2] x 2.5\n" ] } ], @@ -131,7 +127,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -139,10 +135,10 @@ "output_type": "stream", "text": [ "BB1 Y-pi/2:\n", - "X Amplitudes: [+0, -1.984, +1.86, -1.984] x pi\n", - "Y Amplitudes: [+2, -0.25, +0.734, -0.25] x pi\n", - "Z Amplitudes: [+0, +0, +0, +0] x pi\n", - "Durations: [+0.111,+0.222,+0.444,+0.222] x 2.25s\n" + "Rabi Rates = [1.0,1.0,1.0,1.0] x 6.283185307179586\n", + "Azimuthal Angles = [0.5,1.0398930876747683,2.1196792630243046,1.0398930876747683] x pi\n", + "Detunings = [0,0,0,0] x 0.0\n", + "Durations = [0.1111111111111111,0.2222222222222222,0.4444444444444444,0.2222222222222222] x 2.25\n" ] } ], @@ -160,7 +156,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -168,10 +164,10 @@ "output_type": "stream", "text": [ "SK1 Y-pi/2:\n", - "X Amplitudes: [+0, +1.984, -1.984] x pi\n", - "Y Amplitudes: [+2, -0.25, -0.25] x pi\n", - "Z Amplitudes: [+0, +0, +0] x pi\n", - "Durations: [+0.111,+0.444,+0.444] x 2.25s\n" + "Rabi Rates = [1.0,1.0,1.0] x 6.283185307179586\n", + "Azimuthal Angles = [0.5,-0.03989308767476825,1.0398930876747683] x pi\n", + "Detunings = [0,0,0] x 0.0\n", + "Durations = [0.1111111111111111,0.4444444444444444,0.4444444444444444] x 2.25\n" ] } ], @@ -189,7 +185,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -197,10 +193,10 @@ "output_type": "stream", "text": [ "CORPSE X-pi/2:\n", - "X Amplitudes: [+2, -2, +2] x pi\n", - "Y Amplitudes: [+0, +0, +0] x pi\n", - "Z Amplitudes: [+0, +0, +0] x pi\n", - "Durations: [+0.528,+0.438,+0.033] x 2.0199465438373845s\n" + "Rabi Rates = [1.0,1.0,1.0] x 6.283185307179586\n", + "Azimuthal Angles = [0.0,1.0,0.0] x pi\n", + "Detunings = [0,0,0] x 0.0\n", + "Durations = [0.5284727158825664,0.43811717424831853,0.033410109869114954] x 2.0199465438373845\n" ] } ], @@ -227,14 +223,14 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "
" + "" ] }, "metadata": {}, @@ -283,8 +279,10 @@ }, { "cell_type": "code", - "execution_count": 16, - "metadata": {}, + "execution_count": 9, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "file_type='CSV'\n", @@ -298,7 +296,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -307,13 +305,13 @@ "text": [ "amplitude_x,amplitude_y,detuning,duration,maximum_rabi_rate\n", "\n", - "3.141592653589793,0.0,0.0,0.5,6.283185307179586\n", + "6.283185307179586,0.0,0.0,0.5,6.283185307179586\n", "\n", - "-0.785398163397448,3.041834006980209,0.0,0.5,6.283185307179586\n", + "-1.570796326794896,6.083668013960418,0.0,0.5,6.283185307179586\n", "\n", "4.319689898685962,-4.562751010470316,0.0,1.0,6.283185307179586\n", "\n", - "-0.785398163397448,3.041834006980209,0.0,0.5,6.283185307179586\n" + "-1.570796326794896,6.083668013960418,0.0,0.5,6.283185307179586\n" ] } ], @@ -355,9 +353,9 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAABJkAAAFACAYAAAAfw61rAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3Xu0XXV97/33JwEEFUSbIJcQQZragwrK2AZUtFCrBY5K\ntaLgrRU0xUqP1aet1J56O+cZwz5Wn6qgMVoE2yrVo2DUKIoPFdRaExC506YUJSk2EblfDXyfP9YM\nLDZ776ydtdeaa+/9fo2xxp63NeeHyU5+Wd/1m79fqgpJkiRJkiSpHwvaDiBJkiRJkqTZzyKTJEmS\nJEmS+maRSZIkSZIkSX2zyCRJkiRJkqS+WWSSJEmSJElS3ywySZIkSZIkqW8WmSRJkiRJktQ3i0yS\nJEmSJEnqm0UmSZIkSZIk9W2HtgPMpEWLFtV+++3XdgxJGjkXX3zxz6tqcds52mQbIUmTs52wnZCk\nqfTaTsypItN+++3HunXr2o4hSSMnyU/aztA22whJmpzthO2EJE2l13bCx+UkSZIkSZLUN4tMkiRJ\nkiRJ6ptFJkmSJEmSJPXNIpMkSZIkSZL6ZpFJkiRJkiRJfbPIJEmSJEmSpL5ZZJIkSZIkSVLfLDJJ\nkkZOkjOSbEpyxST7k+QjSdYnuSzJIcPOKEmSJOnhLDJJkkbRmcBRU+w/GljWvFYAHx9CJkmSJElT\n2KHtAJI0ai46427WfuHetmM8wrOOexTPO3GXtmMMRVVdmGS/KQ45FvhMVRXwgyS7J9mrqm4cSkBJ\n89aothEwv9qJUeLvhCQ9xCKTJI2z9gv38tNLt7DHAaPT2XPT+vu5767yH4oP2Qe4oWt9Q7PNIpOk\ngRrFNgJsJ9rk74QkPcQikyRNYI8DFnDSp3drO8aDPv2m23lgS7UdY9ZJsoLO43QsXbq05TSS5opR\nayPAdqJt/k5IUsdoldslSerNRmDfrvUlzbaHqapVVTVWVWOLFy8eWjhJkiRpPrLIJEmajVYDr29m\nmTsMuNXxmCRpfuhhBtIjktya5NLm9a5hZ5Sk+crH5SRJIyfJ54AjgEVJNgDvBnYEqKqVwBrgGGA9\ncBfwhnaSSpJacCZwGvCZKY65qKpePJw4kqStLDJJkkZOVZ2wjf0FvGVIcSRJI6SHGUglSS3xcTlJ\nkiRJc81zklyW5OtJnjrZQUlWJFmXZN3mzZuHmU+S5iSLTJIkSZLmkkuApVV1EPBR4NzJDnSCCEma\nWRaZJEmSJM0ZVXVbVd3RLK8BdkyyqOVYkjQvWGSSJEmSNGck2TNJmuXldD7z3NRuKkmaHxz4W5Ik\nSdKs0cMMpK8A3pxkC3A3cHwzYYQkacAsMkmSJEmaNXqYgfQ04LQhxZEkdRlYkSnJGcCLgU1V9bQJ\n9v8p8JquHP8NWFxVv0hyPXA7cD+wparGBpVTkiRJkiRJ/RvkmExnAkdNtrOqPlBVz6iqZwB/Dnyn\nqn7RdciRzX4LTJIkSZIkSSNuYEWmqroQ+MU2D+w4AfjcoLJIkiRJkiRpsFqfXS7Jo+n0ePpi1+YC\nzk9ycZIV23j/iiTrkqzbvHnzIKNKkiRJkiRpEq0XmYCXAN8b96jc4c1jdEcDb0ny/MneXFWrqmqs\nqsYWL1486KySJEmSJEmawCgUmY5n3KNyVbWx+bkJOAdY3kIuSZIkSZIk9ajVIlOSxwG/AXy5a9tj\nkuy6dRl4EXBFOwklSZIkSZLUix0GdeIknwOOABYl2QC8G9gRoKpWNoe9DPhmVd3Z9dYnAuck2Zrv\ns1X1jUHllCRJkiRJUv8GVmSqqhN6OOZM4Mxx264DDh5MKkmSJEmSJA3CKIzJJEmSJEmSpFnOIpMk\nSZIkSZL6ZpFJkiRJkiRJfbPIJEmSJEmSpL5ZZJIkSZIkSVLfLDJJkiRJkiSpbxaZJEmSJEmS1Lcd\n2g6giV10xt2s/cK9bceYFZ513KN43om7tB1DkiRJkqR5zSLTiFr7hXv56aVb2OMAO5tNZdP6+7nv\nrrLIJEmSJElSyywyjbA9DljASZ/ere0YI+3Tb7qdB7ZU2zEkSZIkSZr37CYjSZIkSZKkvllkkiRJ\nkiRJUt8sMkmSJEmSJKlvFpkkSZIkSZLUN4tMkiRJkiRJ6ptFJkmSJEmSJPXNIpMkSZIkSZL6ZpFJ\nkiRJkiRJfbPIJEmSJEmSpL5ZZJIkSZIkSVLfLDJJkiRJkiSpbxaZJEmSJM0aSc5IsinJFZPsT5KP\nJFmf5LIkhww7oyTNVxaZJEmSJM0mZwJHTbH/aGBZ81oBfHwImSRJWGSSJEmSNItU1YXAL6Y45Fjg\nM9XxA2D3JHsNJ50kzW8DKzL10I31iCS3Jrm0eb2ra99RSa5turieOqiMkiRJkuacfYAbutY3NNse\nIcmKJOuSrNu8efNQwknSXDbInkxnMnU3VoCLquoZzet9AEkWAqfT6eZ6IHBCkgMHmFOSJEnSPFRV\n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\n", "text/plain": [ - "
" + "" ] }, "metadata": {}, @@ -421,7 +419,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.6.8" } }, "nbformat": 4,