Skip to content

Commit 00fe233

Browse files
authored
Merge pull request #14 from qctrl/pyquil
pyQuil support in Open-Controls
2 parents d6fd7eb + fb4abca commit 00fe233

File tree

13 files changed

+446
-87
lines changed

13 files changed

+446
-87
lines changed

poetry.lock

Lines changed: 103 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ scipy = "^1.3"
3838
qiskit-terra = "^0.8.1"
3939
qiskit-ibmq-provider = "^0.2.2"
4040
cirq = "^0.5.0"
41+
pyquil = "^2.9"
4142

4243
[tool.poetry.dev-dependencies]
4344
pytest = "*"

qctrlopencontrols/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,14 @@
2828
from .dynamic_decoupling_sequences.predefined import new_predefined_dds
2929
from .dynamic_decoupling_sequences.driven_controls import convert_dds_to_driven_control
3030

31+
from .pyquil.program import convert_dds_to_pyquil_program
32+
3133
from .qiskit.quantum_circuit import convert_dds_to_qiskit_quantum_circuit
3234

3335
__all__ = ['convert_dds_to_cirq_circuit',
3436
'convert_dds_to_cirq_schedule',
3537
'convert_dds_to_driven_control',
38+
'convert_dds_to_pyquil_program',
3639
'convert_dds_to_qiskit_quantum_circuit',
3740
'new_predefined_dds',
3841
'new_predefined_driven_control',

qctrlopencontrols/cirq/circuit.py

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,7 @@ def convert_dds_to_cirq_circuit(
108108
'Time delay of gates must be greater than zero.',
109109
{'gate_time': gate_time})
110110

111-
if target_qubits is None:
112-
target_qubits = [cirq.LineQubit(0)]
111+
target_qubits = target_qubits or [cirq.LineQubit(0)]
113112

114113
if algorithm not in [FIX_DURATION_UNITARY, INSTANT_UNITARY]:
115114
raise ArgumentsValueError('Algorithm must be one of {} or {}'.format(
@@ -123,59 +122,51 @@ def convert_dds_to_cirq_circuit(
123122
azimuthal_angles = dynamic_decoupling_sequence.azimuthal_angles
124123
detuning_rotations = dynamic_decoupling_sequence.detuning_rotations
125124

126-
if len(rabi_rotations.shape) == 1:
127-
rabi_rotations = rabi_rotations[np.newaxis, :]
128-
if len(azimuthal_angles.shape) == 1:
129-
azimuthal_angles = azimuthal_angles[np.newaxis, :]
130-
if len(detuning_rotations.shape) == 1:
131-
detuning_rotations = detuning_rotations[np.newaxis, :]
132-
133-
operations = np.vstack((rabi_rotations, azimuthal_angles, detuning_rotations))
134125
offsets = dynamic_decoupling_sequence.offsets
135126

136127
time_covered = 0
137128
circuit = cirq.Circuit()
138-
for operation_idx in range(operations.shape[1]):
129+
for offset, rabi_rotation, azimuthal_angle, detuning_rotation in zip(
130+
list(offsets), list(rabi_rotations),
131+
list(azimuthal_angles), list(detuning_rotations)):
139132

140-
offset_distance = offsets[operation_idx] - time_covered
133+
offset_distance = offset - time_covered
141134

142135
if np.isclose(offset_distance, 0.0):
143136
offset_distance = 0.0
144137

145138
if offset_distance < 0:
146-
raise ArgumentsValueError("Offsets cannot be placed properly",
147-
{'sequence_operations': operations})
148-
149-
if offset_distance > 0:
150-
while (time_covered+gate_time) <= offsets[operation_idx]:
151-
gate_list = []
152-
for qubit in target_qubits:
153-
gate_list.append(cirq.I(qubit))
154-
time_covered += gate_time
155-
circuit.append(gate_list)
156-
157-
rabi_rotation = operations[0, operation_idx]
158-
azimuthal_angle = operations[1, operation_idx]
139+
raise ArgumentsValueError(
140+
"Offsets cannot be placed properly. Spacing between the rotations"
141+
"is smaller than the time required to perform the rotation. Provide"
142+
"a longer dynamic decoupling sequence or shorted gate time.",
143+
{'dynamic_decoupling_sequence': dynamic_decoupling_sequence,
144+
'gate_time': gate_time})
145+
146+
while (time_covered+gate_time) <= offset:
147+
gate_list = []
148+
for qubit in target_qubits:
149+
gate_list.append(cirq.I(qubit))
150+
time_covered += gate_time
151+
circuit.append(gate_list)
152+
159153
x_rotation = rabi_rotation * np.cos(azimuthal_angle)
160154
y_rotation = rabi_rotation * np.sin(azimuthal_angle)
161-
z_rotation = operations[2, operation_idx]
155+
z_rotation = detuning_rotation
162156

163157
rotations = np.array([x_rotation, y_rotation, z_rotation])
164158
zero_pulses = np.isclose(rotations, 0.0).astype(np.int)
165159
nonzero_pulse_counts = 3 - np.sum(zero_pulses)
166160
if nonzero_pulse_counts > 1:
167161
raise ArgumentsValueError(
168162
'Open Controls support a sequence with one '
169-
'valid pulse at any offset. Found sequence '
163+
'valid rotation at any offset. Found a sequence '
170164
'with multiple rotation operations at an offset.',
171-
{'dynamic_decoupling_sequence': str(dynamic_decoupling_sequence),
172-
'offset': dynamic_decoupling_sequence.offsets[operation_idx],
173-
'rabi_rotation': dynamic_decoupling_sequence.rabi_rotations[
174-
operation_idx],
175-
'azimuthal_angle': dynamic_decoupling_sequence.azimuthal_angles[
176-
operation_idx],
177-
'detuning_rotaion': dynamic_decoupling_sequence.detuning_rotations[
178-
operation_idx]}
165+
{'dynamic_decoupling_sequence': dynamic_decoupling_sequence},
166+
extras={'offset': offset,
167+
'rabi_rotation': rabi_rotation,
168+
'azimuthal_angle': azimuthal_angle,
169+
'detuning_rotation': detuning_rotation}
179170
)
180171

181172
gate_list = []
@@ -190,10 +181,9 @@ def convert_dds_to_cirq_circuit(
190181
elif not np.isclose(rotations[2], 0.):
191182
gate_list.append(cirq.Rz(rotations[2])(qubit))
192183
circuit.append(gate_list)
193-
if np.isclose(np.sum(rotations), 0.0):
194-
time_covered = offsets[operation_idx]
195-
else:
196-
time_covered = offsets[operation_idx] + unitary_time
184+
185+
time_covered = offset + unitary_time
186+
197187
if add_measurement:
198188
gate_list = []
199189
for idx, qubit in enumerate(target_qubits):

qctrlopencontrols/driven_controls/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@
1313
# limitations under the License.
1414

1515
"""
16-
===============
17-
driven_controls
18-
===============
16+
======================
17+
driven_controls module
18+
======================
1919
"""
20-
2120
##### Maximum and Minimum bounds ######
2221

2322
UPPER_BOUND_RABI_RATE = 1e10

qctrlopencontrols/driven_controls/predefined.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ def new_predefined_driven_control(
6565
Raised when an argument is invalid.
6666
"""
6767

68-
# Forced to import here to avoid cyclic imports, need to review
6968
# Raise error if the input driven_control_type is not known
7069
if scheme == PRIMITIVE:
7170
driven_control = _new_primitive_control(**kwargs)
@@ -99,6 +98,7 @@ def new_predefined_driven_control(
9998
{'scheme': scheme})
10099
return driven_control
101100

101+
102102
def _predefined_common_attributes(maximum_rabi_rate,
103103
rabi_rotation,
104104
azimuthal_angle):

qctrlopencontrols/dynamic_decoupling_sequences/dynamic_decoupling_sequence.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323
from ..base.utils import create_repr_from_attributes
2424
from ..exceptions.exceptions import ArgumentsValueError
25-
2625
from ..globals import (
2726
QCTRL_EXPANDED, CSV, CYLINDRICAL)
2827

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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.
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
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

Comments
 (0)