Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 103 additions & 1 deletion poetry.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ scipy = "^1.3"
qiskit-terra = "^0.8.1"
qiskit-ibmq-provider = "^0.2.2"
cirq = "^0.5.0"
pyquil = "^2.9"

[tool.poetry.dev-dependencies]
pytest = "*"
Expand Down
3 changes: 3 additions & 0 deletions qctrlopencontrols/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@
from .dynamic_decoupling_sequences.predefined import new_predefined_dds
from .dynamic_decoupling_sequences.driven_controls import convert_dds_to_driven_control

from .pyquil.program import convert_dds_to_pyquil_program

from .qiskit.quantum_circuit import convert_dds_to_qiskit_quantum_circuit

__all__ = ['convert_dds_to_cirq_circuit',
'convert_dds_to_cirq_schedule',
'convert_dds_to_driven_control',
'convert_dds_to_pyquil_program',
'convert_dds_to_qiskit_quantum_circuit',
'new_predefined_dds',
'new_predefined_driven_control',
Expand Down
68 changes: 29 additions & 39 deletions qctrlopencontrols/cirq/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ def convert_dds_to_cirq_circuit(
'Time delay of gates must be greater than zero.',
{'gate_time': gate_time})

if target_qubits is None:
target_qubits = [cirq.LineQubit(0)]
target_qubits = target_qubits or [cirq.LineQubit(0)]

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

if len(rabi_rotations.shape) == 1:
rabi_rotations = rabi_rotations[np.newaxis, :]
if len(azimuthal_angles.shape) == 1:
azimuthal_angles = azimuthal_angles[np.newaxis, :]
if len(detuning_rotations.shape) == 1:
detuning_rotations = detuning_rotations[np.newaxis, :]

operations = np.vstack((rabi_rotations, azimuthal_angles, detuning_rotations))
offsets = dynamic_decoupling_sequence.offsets

time_covered = 0
circuit = cirq.Circuit()
for operation_idx in range(operations.shape[1]):
for offset, rabi_rotation, azimuthal_angle, detuning_rotation in zip(
list(offsets), list(rabi_rotations),
list(azimuthal_angles), list(detuning_rotations)):

offset_distance = offsets[operation_idx] - time_covered
offset_distance = offset - time_covered

if np.isclose(offset_distance, 0.0):
offset_distance = 0.0

if offset_distance < 0:
raise ArgumentsValueError("Offsets cannot be placed properly",
{'sequence_operations': operations})

if offset_distance > 0:
while (time_covered+gate_time) <= offsets[operation_idx]:
gate_list = []
for qubit in target_qubits:
gate_list.append(cirq.I(qubit))
time_covered += gate_time
circuit.append(gate_list)

rabi_rotation = operations[0, operation_idx]
azimuthal_angle = operations[1, operation_idx]
raise ArgumentsValueError(
"Offsets cannot be placed properly. Spacing between the rotations"
"is smaller than the time required to perform the rotation. Provide"
"a longer dynamic decoupling sequence or shorted gate time.",
{'dynamic_decoupling_sequence': dynamic_decoupling_sequence,
'gate_time': gate_time})

while (time_covered+gate_time) <= offset:
gate_list = []
for qubit in target_qubits:
gate_list.append(cirq.I(qubit))
time_covered += gate_time
circuit.append(gate_list)

x_rotation = rabi_rotation * np.cos(azimuthal_angle)
y_rotation = rabi_rotation * np.sin(azimuthal_angle)
z_rotation = operations[2, operation_idx]
z_rotation = detuning_rotation

rotations = np.array([x_rotation, y_rotation, z_rotation])
zero_pulses = np.isclose(rotations, 0.0).astype(np.int)
nonzero_pulse_counts = 3 - np.sum(zero_pulses)
if nonzero_pulse_counts > 1:
raise ArgumentsValueError(
'Open Controls support a sequence with one '
'valid pulse at any offset. Found sequence '
'valid rotation at any offset. Found a sequence '
'with multiple rotation operations at an offset.',
{'dynamic_decoupling_sequence': str(dynamic_decoupling_sequence),
'offset': dynamic_decoupling_sequence.offsets[operation_idx],
'rabi_rotation': dynamic_decoupling_sequence.rabi_rotations[
operation_idx],
'azimuthal_angle': dynamic_decoupling_sequence.azimuthal_angles[
operation_idx],
'detuning_rotaion': dynamic_decoupling_sequence.detuning_rotations[
operation_idx]}
{'dynamic_decoupling_sequence': dynamic_decoupling_sequence},
extras={'offset': offset,
'rabi_rotation': rabi_rotation,
'azimuthal_angle': azimuthal_angle,
'detuning_rotation': detuning_rotation}
)

gate_list = []
Expand All @@ -190,10 +181,9 @@ def convert_dds_to_cirq_circuit(
elif not np.isclose(rotations[2], 0.):
gate_list.append(cirq.Rz(rotations[2])(qubit))
circuit.append(gate_list)
if np.isclose(np.sum(rotations), 0.0):
time_covered = offsets[operation_idx]
else:
time_covered = offsets[operation_idx] + unitary_time

time_covered = offset + unitary_time

if add_measurement:
gate_list = []
for idx, qubit in enumerate(target_qubits):
Expand Down
7 changes: 3 additions & 4 deletions qctrlopencontrols/driven_controls/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@
# limitations under the License.

"""
===============
driven_controls
===============
======================
driven_controls module
======================
"""

##### Maximum and Minimum bounds ######

UPPER_BOUND_RABI_RATE = 1e10
Expand Down
2 changes: 1 addition & 1 deletion qctrlopencontrols/driven_controls/predefined.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ def new_predefined_driven_control(
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 scheme == PRIMITIVE:
driven_control = _new_primitive_control(**kwargs)
Expand Down Expand Up @@ -99,6 +98,7 @@ def new_predefined_driven_control(
{'scheme': scheme})
return driven_control


def _predefined_common_attributes(maximum_rabi_rate,
rabi_rotation,
azimuthal_angle):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

from ..base.utils import create_repr_from_attributes
from ..exceptions.exceptions import ArgumentsValueError

from ..globals import (
QCTRL_EXPANDED, CSV, CYLINDRICAL)

Expand Down
13 changes: 13 additions & 0 deletions qctrlopencontrols/pyquil/__init__.py
Original file line number Diff line number Diff line change
@@ -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.
197 changes: 197 additions & 0 deletions qctrlopencontrols/pyquil/program.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# 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.

"""
==============
pyquil.program
==============
"""

import numpy as np

from pyquil import Program
from pyquil.gates import I, RX, RY, RZ, MEASURE
from pyquil.quil import Pragma

from ..dynamic_decoupling_sequences.dynamic_decoupling_sequence import DynamicDecouplingSequence
from ..exceptions.exceptions import ArgumentsValueError
from ..globals import (
FIX_DURATION_UNITARY, INSTANT_UNITARY)


def convert_dds_to_pyquil_program(
dynamic_decoupling_sequence,
target_qubits=None,
gate_time=0.1,
add_measurement=True,
algorithm=INSTANT_UNITARY):

"""Converts a Dynamic Decoupling Sequence into quantum program
as defined in Pyquil

Parameters
----------
dynamic_decoupling_sequence : DynamicDecouplingSequence
The dynamic decoupling sequence
target_qubits : list, optional
List of integers specifying target qubits for the sequence operation;
defaults to None in which case 0-th Qubit is used
gate_time : float, optional
Time (in seconds) delay introduced by a gate; defaults to 0.1
add_measurement : bool, optional
If True, the circuit contains a measurement operation for each of the
target qubits and a set of ClassicalRegister objects created with length
equal to `len(target_qubits)`
algorithm : str, optional
One of 'fixed duration unitary' or 'instant unitary'; In the case of
'fixed duration unitary', the sequence operations are assumed to be
taking the amount of gate_time while 'instant unitary' assumes the sequence
operations are instantaneous (and hence does not contribute to the delay between
offsets). Defaults to 'instant unitary'.

Returns
-------
pyquil.Program
The Pyquil program containting gates specified by the rotations of
dynamic decoupling sequence


Raises
------
ArgumentsValueError
If any of the input parameters are invalid

Notes
-----

Dynamic Decoupling Sequences (DDS) consist of idealized pulse operation. Theoretically,
these operations (pi-pulses in X,Y or Z) occur instantaneously. However, in practice,
pulses require time. Therefore, this method of converting an idealized sequence
results to a circuit that is only an approximate implementation of the idealized sequence.

In idealized definition of DDS, `offsets` represents the instances within sequence
`duration` where a pulse occurs instantaneously. A series of appropriate gatges
is placed in order to represent these pulses. The `gaps` or idle time in between active
pulses are filled up with `identity` gates. Each identity gate introduces a delay of
`gate_time`. In this implementation, the number of identity gates is determined by
:math:`np.int(np.floor(offset_distance / gate_time))`. As a consequence, the duration of
the real-circuit is :math:`gate_time \\times number_of_identity_gates +
pulse_gate_time \\times number_of_pulses`.

Q-CTRL Open Controls support operation resulting in rotation around at most one axis at
any offset.
"""

if dynamic_decoupling_sequence is None:
raise ArgumentsValueError('No dynamic decoupling sequence provided.',
{'dynamic_decoupling_sequence': dynamic_decoupling_sequence})

if not isinstance(dynamic_decoupling_sequence, DynamicDecouplingSequence):
raise ArgumentsValueError('Dynamical decoupling sequence is not recognized.'
'Expected DynamicDecouplingSequence instance',
{'type(dynamic_decoupling_sequence)':
type(dynamic_decoupling_sequence)})

target_qubits = target_qubits or [0]

if gate_time <= 0:
raise ArgumentsValueError(
'Time delay of identity gate must be greater than zero.',
{'gate_time': gate_time})

if np.any(target_qubits) < 0:
raise ArgumentsValueError(
'Every target qubits index must be non-negative.',
{'target_qubits': target_qubits})

if algorithm not in [FIX_DURATION_UNITARY, INSTANT_UNITARY]:
raise ArgumentsValueError('Algorithm must be one of {} or {}'.format(
INSTANT_UNITARY, FIX_DURATION_UNITARY), {'algorithm': algorithm})

unitary_time = 0.
if algorithm == FIX_DURATION_UNITARY:
unitary_time = gate_time

rabi_rotations = dynamic_decoupling_sequence.rabi_rotations
azimuthal_angles = dynamic_decoupling_sequence.azimuthal_angles
detuning_rotations = dynamic_decoupling_sequence.detuning_rotations

offsets = dynamic_decoupling_sequence.offsets

time_covered = 0
program = Program()
program += Pragma('PRESERVE_BLOCK')

for offset, rabi_rotation, azimuthal_angle, detuning_rotation in zip(
list(offsets), list(rabi_rotations),
list(azimuthal_angles), list(detuning_rotations)):

offset_distance = offset - time_covered

if np.isclose(offset_distance, 0.0):
offset_distance = 0.0

if offset_distance < 0:
raise ArgumentsValueError(
"Offsets cannot be placed properly. Spacing between the rotations"
"is smaller than the time required to perform the rotation. Provide"
"a longer dynamic decoupling sequence or shorted gate time.",
{'dynamic_decoupling_sequence': dynamic_decoupling_sequence,
'gate_time': gate_time})

while (time_covered+gate_time) <= offset:
for qubit in target_qubits:
program += I(qubit)
time_covered += gate_time

x_rotation = rabi_rotation * np.cos(azimuthal_angle)
y_rotation = rabi_rotation * np.sin(azimuthal_angle)
z_rotation = detuning_rotation

rotations = np.array([x_rotation, y_rotation, z_rotation])
zero_pulses = np.isclose(rotations, 0.0).astype(np.int)
nonzero_pulse_counts = 3 - np.sum(zero_pulses)
if nonzero_pulse_counts > 1:
raise ArgumentsValueError(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this isn't really an ArgumentsValueError is it? In this case I think it means we messed something up, because we produced an invalid DDS?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, Open-Controls only has ArgumentsValueError

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See before.

Specifically here, the offset, rabi_rotation, azimuthal_angle and detuning rotation should all be in the extras dictionary.

'Open Controls support a sequence with one '
'valid rotation at any offset. Found a sequence '
'with multiple rotation operations at an offset.',
{'dynamic_decoupling_sequence': dynamic_decoupling_sequence},
extras={'offset': offset,
'rabi_rotation': rabi_rotation,
'azimuthal_angle': azimuthal_angle,
'detuning_rotation': detuning_rotation}
)

for qubit in target_qubits:
if nonzero_pulse_counts == 0:
program += I(qubit)
else:
if not np.isclose(rotations[0], 0.0):
program += RX(rotations[0], qubit)
elif not np.isclose(rotations[1], 0.0):
program += RY(rotations[1], qubit)
elif not np.isclose(rotations[2], 0.):
program += RZ(rotations[2], qubit)

time_covered = offset + unitary_time

if add_measurement:
readout = program.declare('ro', 'BIT', len(target_qubits))
for idx, qubit in enumerate(target_qubits):
program += MEASURE(qubit, readout[idx])

program += Pragma('END_PRESERVE_BLOCK')

return program
Loading