|
| 1 | +# Copyright 2019 Q-CTRL Pty Ltd & Q-CTRL Inc |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +""" |
| 16 | +============== |
| 17 | +pyquil.program |
| 18 | +============== |
| 19 | +""" |
| 20 | + |
| 21 | +import numpy as np |
| 22 | + |
| 23 | +from pyquil import Program |
| 24 | +from pyquil.gates import I, RX, RY, RZ, MEASURE |
| 25 | +from pyquil.quil import Pragma |
| 26 | + |
| 27 | +from ..dynamic_decoupling_sequences.dynamic_decoupling_sequence import DynamicDecouplingSequence |
| 28 | +from ..exceptions.exceptions import ArgumentsValueError |
| 29 | +from ..globals import ( |
| 30 | + FIX_DURATION_UNITARY, INSTANT_UNITARY) |
| 31 | + |
| 32 | + |
| 33 | +def convert_dds_to_pyquil_program( |
| 34 | + dynamic_decoupling_sequence, |
| 35 | + target_qubits=None, |
| 36 | + gate_time=0.1, |
| 37 | + add_measurement=True, |
| 38 | + algorithm=INSTANT_UNITARY): |
| 39 | + |
| 40 | + """Converts a Dynamic Decoupling Sequence into quantum program |
| 41 | + as defined in Pyquil |
| 42 | +
|
| 43 | + Parameters |
| 44 | + ---------- |
| 45 | + dynamic_decoupling_sequence : DynamicDecouplingSequence |
| 46 | + The dynamic decoupling sequence |
| 47 | + target_qubits : list, optional |
| 48 | + List of integers specifying target qubits for the sequence operation; |
| 49 | + defaults to None in which case 0-th Qubit is used |
| 50 | + gate_time : float, optional |
| 51 | + Time (in seconds) delay introduced by a gate; defaults to 0.1 |
| 52 | + add_measurement : bool, optional |
| 53 | + If True, the circuit contains a measurement operation for each of the |
| 54 | + target qubits and a set of ClassicalRegister objects created with length |
| 55 | + equal to `len(target_qubits)` |
| 56 | + algorithm : str, optional |
| 57 | + One of 'fixed duration unitary' or 'instant unitary'; In the case of |
| 58 | + 'fixed duration unitary', the sequence operations are assumed to be |
| 59 | + taking the amount of gate_time while 'instant unitary' assumes the sequence |
| 60 | + operations are instantaneous (and hence does not contribute to the delay between |
| 61 | + offsets). Defaults to 'instant unitary'. |
| 62 | +
|
| 63 | + Returns |
| 64 | + ------- |
| 65 | + pyquil.Program |
| 66 | + The Pyquil program containting gates specified by the rotations of |
| 67 | + dynamic decoupling sequence |
| 68 | +
|
| 69 | +
|
| 70 | + Raises |
| 71 | + ------ |
| 72 | + ArgumentsValueError |
| 73 | + If any of the input parameters are invalid |
| 74 | +
|
| 75 | + Notes |
| 76 | + ----- |
| 77 | +
|
| 78 | + Dynamic Decoupling Sequences (DDS) consist of idealized pulse operation. Theoretically, |
| 79 | + these operations (pi-pulses in X,Y or Z) occur instantaneously. However, in practice, |
| 80 | + pulses require time. Therefore, this method of converting an idealized sequence |
| 81 | + results to a circuit that is only an approximate implementation of the idealized sequence. |
| 82 | +
|
| 83 | + In idealized definition of DDS, `offsets` represents the instances within sequence |
| 84 | + `duration` where a pulse occurs instantaneously. A series of appropriate gatges |
| 85 | + is placed in order to represent these pulses. The `gaps` or idle time in between active |
| 86 | + pulses are filled up with `identity` gates. Each identity gate introduces a delay of |
| 87 | + `gate_time`. In this implementation, the number of identity gates is determined by |
| 88 | + :math:`np.int(np.floor(offset_distance / gate_time))`. As a consequence, the duration of |
| 89 | + the real-circuit is :math:`gate_time \\times number_of_identity_gates + |
| 90 | + pulse_gate_time \\times number_of_pulses`. |
| 91 | +
|
| 92 | + Q-CTRL Open Controls support operation resulting in rotation around at most one axis at |
| 93 | + any offset. |
| 94 | + """ |
| 95 | + |
| 96 | + if dynamic_decoupling_sequence is None: |
| 97 | + raise ArgumentsValueError('No dynamic decoupling sequence provided.', |
| 98 | + {'dynamic_decoupling_sequence': dynamic_decoupling_sequence}) |
| 99 | + |
| 100 | + if not isinstance(dynamic_decoupling_sequence, DynamicDecouplingSequence): |
| 101 | + raise ArgumentsValueError('Dynamical decoupling sequence is not recognized.' |
| 102 | + 'Expected DynamicDecouplingSequence instance', |
| 103 | + {'type(dynamic_decoupling_sequence)': |
| 104 | + type(dynamic_decoupling_sequence)}) |
| 105 | + |
| 106 | + target_qubits = target_qubits or [0] |
| 107 | + |
| 108 | + if gate_time <= 0: |
| 109 | + raise ArgumentsValueError( |
| 110 | + 'Time delay of identity gate must be greater than zero.', |
| 111 | + {'gate_time': gate_time}) |
| 112 | + |
| 113 | + if np.any(target_qubits) < 0: |
| 114 | + raise ArgumentsValueError( |
| 115 | + 'Every target qubits index must be non-negative.', |
| 116 | + {'target_qubits': target_qubits}) |
| 117 | + |
| 118 | + if algorithm not in [FIX_DURATION_UNITARY, INSTANT_UNITARY]: |
| 119 | + raise ArgumentsValueError('Algorithm must be one of {} or {}'.format( |
| 120 | + INSTANT_UNITARY, FIX_DURATION_UNITARY), {'algorithm': algorithm}) |
| 121 | + |
| 122 | + unitary_time = 0. |
| 123 | + if algorithm == FIX_DURATION_UNITARY: |
| 124 | + unitary_time = gate_time |
| 125 | + |
| 126 | + rabi_rotations = dynamic_decoupling_sequence.rabi_rotations |
| 127 | + azimuthal_angles = dynamic_decoupling_sequence.azimuthal_angles |
| 128 | + detuning_rotations = dynamic_decoupling_sequence.detuning_rotations |
| 129 | + |
| 130 | + offsets = dynamic_decoupling_sequence.offsets |
| 131 | + |
| 132 | + time_covered = 0 |
| 133 | + program = Program() |
| 134 | + program += Pragma('PRESERVE_BLOCK') |
| 135 | + |
| 136 | + for offset, rabi_rotation, azimuthal_angle, detuning_rotation in zip( |
| 137 | + list(offsets), list(rabi_rotations), |
| 138 | + list(azimuthal_angles), list(detuning_rotations)): |
| 139 | + |
| 140 | + offset_distance = offset - time_covered |
| 141 | + |
| 142 | + if np.isclose(offset_distance, 0.0): |
| 143 | + offset_distance = 0.0 |
| 144 | + |
| 145 | + if offset_distance < 0: |
| 146 | + raise ArgumentsValueError( |
| 147 | + "Offsets cannot be placed properly. Spacing between the rotations" |
| 148 | + "is smaller than the time required to perform the rotation. Provide" |
| 149 | + "a longer dynamic decoupling sequence or shorted gate time.", |
| 150 | + {'dynamic_decoupling_sequence': dynamic_decoupling_sequence, |
| 151 | + 'gate_time': gate_time}) |
| 152 | + |
| 153 | + while (time_covered+gate_time) <= offset: |
| 154 | + for qubit in target_qubits: |
| 155 | + program += I(qubit) |
| 156 | + time_covered += gate_time |
| 157 | + |
| 158 | + x_rotation = rabi_rotation * np.cos(azimuthal_angle) |
| 159 | + y_rotation = rabi_rotation * np.sin(azimuthal_angle) |
| 160 | + z_rotation = detuning_rotation |
| 161 | + |
| 162 | + rotations = np.array([x_rotation, y_rotation, z_rotation]) |
| 163 | + zero_pulses = np.isclose(rotations, 0.0).astype(np.int) |
| 164 | + nonzero_pulse_counts = 3 - np.sum(zero_pulses) |
| 165 | + if nonzero_pulse_counts > 1: |
| 166 | + raise ArgumentsValueError( |
| 167 | + 'Open Controls support a sequence with one ' |
| 168 | + 'valid rotation at any offset. Found a sequence ' |
| 169 | + 'with multiple rotation operations at an offset.', |
| 170 | + {'dynamic_decoupling_sequence': dynamic_decoupling_sequence}, |
| 171 | + extras={'offset': offset, |
| 172 | + 'rabi_rotation': rabi_rotation, |
| 173 | + 'azimuthal_angle': azimuthal_angle, |
| 174 | + 'detuning_rotation': detuning_rotation} |
| 175 | + ) |
| 176 | + |
| 177 | + for qubit in target_qubits: |
| 178 | + if nonzero_pulse_counts == 0: |
| 179 | + program += I(qubit) |
| 180 | + else: |
| 181 | + if not np.isclose(rotations[0], 0.0): |
| 182 | + program += RX(rotations[0], qubit) |
| 183 | + elif not np.isclose(rotations[1], 0.0): |
| 184 | + program += RY(rotations[1], qubit) |
| 185 | + elif not np.isclose(rotations[2], 0.): |
| 186 | + program += RZ(rotations[2], qubit) |
| 187 | + |
| 188 | + time_covered = offset + unitary_time |
| 189 | + |
| 190 | + if add_measurement: |
| 191 | + readout = program.declare('ro', 'BIT', len(target_qubits)) |
| 192 | + for idx, qubit in enumerate(target_qubits): |
| 193 | + program += MEASURE(qubit, readout[idx]) |
| 194 | + |
| 195 | + program += Pragma('END_PRESERVE_BLOCK') |
| 196 | + |
| 197 | + return program |
0 commit comments