Skip to content

Commit

Permalink
Update cirq-rigetti to use pyquil v4 (#6281)
Browse files Browse the repository at this point in the history
- Add support for Kraus operators, POVMs and parametric defgates
- Update quil->cirq conversion

Fixes #6500
Partially implements #6464
  • Loading branch information
jselig-rigetti authored and pavoljuhas committed Jun 26, 2024
1 parent e228340 commit 6f5b4b2
Show file tree
Hide file tree
Showing 13 changed files with 815 additions and 363 deletions.
43 changes: 0 additions & 43 deletions cirq-rigetti/cirq_rigetti/_qcs_api_client_decorator.py

This file was deleted.

31 changes: 13 additions & 18 deletions cirq-rigetti/cirq_rigetti/aspen_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,16 @@
# 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.
from typing import List, cast, Optional, Union, Dict, Any
from typing import List, Optional, Union, Dict, Any
import functools
from math import sqrt
import httpx
import json
import numpy as np
import networkx as nx
import cirq
from pyquil.quantum_processor import QCSQuantumProcessor
from qcs_api_client.models import InstructionSetArchitecture
from qcs_api_client.operations.sync import get_instruction_set_architecture
from cirq_rigetti._qcs_api_client_decorator import _provide_default_client
from qcs_sdk.client import QCSClient
from qcs_sdk.qpu.isa import get_instruction_set_architecture, InstructionSetArchitecture, Family


class UnsupportedQubit(ValueError):
Expand Down Expand Up @@ -50,6 +49,8 @@ class UnsupportedRigettiQCSQuantumProcessor(ValueError):
class RigettiQCSAspenDevice(cirq.devices.Device):
"""A cirq.Qid supporting Rigetti QCS Aspen device topology."""

isa: InstructionSetArchitecture

def __init__(self, isa: Union[InstructionSetArchitecture, Dict[str, Any]]) -> None:
"""Initializes a RigettiQCSAspenDevice with its Rigetti QCS `InstructionSetArchitecture`.
Expand All @@ -63,9 +64,9 @@ def __init__(self, isa: Union[InstructionSetArchitecture, Dict[str, Any]]) -> No
if isinstance(isa, InstructionSetArchitecture):
self.isa = isa
else:
self.isa = InstructionSetArchitecture.from_dict(isa)
self.isa = InstructionSetArchitecture.from_raw(json.dumps(isa))

if self.isa.architecture.family.lower() != 'aspen':
if self.isa.architecture.family != Family.Aspen:
raise UnsupportedRigettiQCSQuantumProcessor(
'this integration currently only supports Aspen devices, '
f'but client provided a {self.isa.architecture.family} device'
Expand Down Expand Up @@ -224,23 +225,22 @@ def __repr__(self):
return f'cirq_rigetti.RigettiQCSAspenDevice(isa={self.isa!r})'

def _json_dict_(self):
return {'isa': self.isa.to_dict()}
return {'isa': json.loads(self.isa.json())}

@classmethod
def _from_json_dict_(cls, isa, **kwargs):
return cls(isa=InstructionSetArchitecture.from_dict(isa))
return cls(isa=InstructionSetArchitecture.from_raw(json.dumps(isa)))


@_provide_default_client # pragma: no cover
def get_rigetti_qcs_aspen_device(
quantum_processor_id: str, client: Optional[httpx.Client]
quantum_processor_id: str, client: Optional[QCSClient] = None
) -> RigettiQCSAspenDevice:
"""Retrieves a `qcs_api_client.models.InstructionSetArchitecture` from the Rigetti
QCS API and uses it to initialize a RigettiQCSAspenDevice.
Args:
quantum_processor_id: The identifier of the Rigetti QCS quantum processor.
client: Optional; A `httpx.Client` initialized with Rigetti QCS credentials
client: Optional; A `QCSClient` initialized with Rigetti QCS credentials
and configuration. If not provided, `qcs_api_client` will initialize a
configured client based on configured values in the current user's
`~/.qcs` directory or default values.
Expand All @@ -250,12 +250,7 @@ def get_rigetti_qcs_aspen_device(
set and architecture.
"""
isa = cast(
InstructionSetArchitecture,
get_instruction_set_architecture(
client=client, quantum_processor_id=quantum_processor_id
).parsed,
)
isa = get_instruction_set_architecture(client=client, quantum_processor_id=quantum_processor_id)
return RigettiQCSAspenDevice(isa=isa)


Expand Down
36 changes: 17 additions & 19 deletions cirq-rigetti/cirq_rigetti/aspen_device_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from unittest.mock import patch, PropertyMock
from math import sqrt
import pathlib
import json
import pytest
import cirq
from cirq_rigetti import (
Expand All @@ -12,9 +11,8 @@
RigettiQCSAspenDevice,
UnsupportedQubit,
UnsupportedRigettiQCSOperation,
UnsupportedRigettiQCSQuantumProcessor,
)
from qcs_api_client.models import InstructionSetArchitecture, Node
from qcs_sdk.qpu.isa import InstructionSetArchitecture, Family
import numpy as np

dir_path = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
Expand All @@ -24,7 +22,7 @@
@pytest.fixture
def qcs_aspen8_isa() -> InstructionSetArchitecture:
with open(fixture_path / 'QCS-Aspen-8-ISA.json', 'r') as f:
return InstructionSetArchitecture.from_dict(json.load(f))
return InstructionSetArchitecture.from_raw(f.read())


def test_octagonal_qubit_index():
Expand Down Expand Up @@ -204,17 +202,6 @@ def test_rigetti_qcs_aspen_device_invalid_qubit(
device.validate_operation(cirq.I(qubit))


def test_rigetti_qcs_aspen_device_non_existent_qubit(qcs_aspen8_isa: InstructionSetArchitecture):
"""test RigettiQCSAspenDevice throws error when qubit does not exist on device"""
# test device may only be initialized with Aspen ISA.
device_with_limited_nodes = RigettiQCSAspenDevice(
isa=InstructionSetArchitecture.from_dict(qcs_aspen8_isa.to_dict())
)
device_with_limited_nodes.isa.architecture.nodes = [Node(node_id=10)]
with pytest.raises(UnsupportedQubit):
device_with_limited_nodes.validate_qubit(cirq.GridQubit(0, 0))


@pytest.mark.parametrize(
'operation',
[
Expand Down Expand Up @@ -265,7 +252,18 @@ def test_rigetti_qcs_aspen_device_repr(qcs_aspen8_isa: InstructionSetArchitectur

def test_rigetti_qcs_aspen_device_family_validation(qcs_aspen8_isa: InstructionSetArchitecture):
"""test RigettiQCSAspenDevice validates architecture family on initialization"""
non_aspen_isa = InstructionSetArchitecture.from_dict(qcs_aspen8_isa.to_dict())
non_aspen_isa.architecture.family = "not-aspen" # type: ignore
with pytest.raises(UnsupportedRigettiQCSQuantumProcessor):
RigettiQCSAspenDevice(isa=non_aspen_isa)
non_aspen_isa = InstructionSetArchitecture.from_raw(qcs_aspen8_isa.json())
non_aspen_isa.architecture.family = Family.NONE

assert (
non_aspen_isa.architecture.family == Family.Aspen
), 'ISA family is read-only and should still be Aspen'


def test_get_rigetti_qcs_aspen_device(qcs_aspen8_isa: InstructionSetArchitecture):
with patch('cirq_rigetti.aspen_device.get_instruction_set_architecture') as mock:
mock.return_value = qcs_aspen8_isa

from cirq_rigetti.aspen_device import get_rigetti_qcs_aspen_device

assert get_rigetti_qcs_aspen_device('Aspen-8') == RigettiQCSAspenDevice(isa=qcs_aspen8_isa)
23 changes: 11 additions & 12 deletions cirq-rigetti/cirq_rigetti/circuit_sweep_executors.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,22 @@ def _execute_and_read_result(
Raises:
ValueError: measurement_id_map references an undefined pyQuil readout region.
"""
if memory_map is None:
memory_map = {}

for region_name, values in memory_map.items():
if isinstance(region_name, str):
executable.write_memory(region_name=region_name, value=values)
else:
raise ValueError(f'Symbols not valid for region name {region_name}')
qam_execution_result = quantum_computer.qam.run(executable)
# convert all atomic memory values into 1-length lists
if memory_map is not None:
for region_name, value in memory_map.items():
if not isinstance(region_name, str):
raise ValueError(f'Symbols not valid for region name {region_name}')
value = [value] if not isinstance(value, Sequence) else value
memory_map[region_name] = value

qam_execution_result = quantum_computer.qam.run(executable, memory_map) # type: ignore

measurements = {}
# For every key, value in QuilOutput#measurement_id_map, use the value to read
# Rigetti QCS results and assign to measurements by key.
for cirq_memory_key, pyquil_region in measurement_id_map.items():
readout = qam_execution_result.readout_data.get(pyquil_region)
readout = qam_execution_result.get_register_map().get(pyquil_region)
if readout is None:
raise ValueError(f'readout data does not have values for region "{pyquil_region}"')
measurements[cirq_memory_key] = readout
Expand Down Expand Up @@ -122,9 +123,7 @@ def _prepend_real_declarations(
param_dict = _get_param_dict(resolver)
for key in param_dict.keys():
declaration = Declare(str(key), "REAL")
program._instructions.insert(0, declaration)
program._synthesized_instructions = None
program.declarations[declaration.name] = declaration
program = Program(declaration) + program
logger.debug(f"prepended declaration {declaration}")
return program

Expand Down
5 changes: 3 additions & 2 deletions cirq-rigetti/cirq_rigetti/circuit_transformers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from unittest.mock import create_autospec
import cirq
import numpy as np
from pyquil import Program
from pyquil.gates import MEASURE, RX, DECLARE, H, CNOT, I
from pyquil.quilbase import Pragma, Reset
from cirq_rigetti import circuit_transformers as transformers
Expand Down Expand Up @@ -63,15 +64,15 @@ def test_transform_with_post_transformation_hooks(
bell_circuit, qubits = bell_circuit_with_qids

def reset_hook(program, measurement_id_map):
program._instructions.insert(0, Reset())
program = Program(Reset()) + program
return program, measurement_id_map

reset_hook_spec = create_autospec(reset_hook, side_effect=reset_hook)

pragma = Pragma('INTIAL_REWIRING', freeform_string='GREEDY')

def rewire_hook(program, measurement_id_map):
program._instructions.insert(0, pragma)
program = Program(pragma) + program
return program, measurement_id_map

rewire_hook_spec = create_autospec(rewire_hook, side_effect=rewire_hook)
Expand Down
Loading

0 comments on commit 6f5b4b2

Please sign in to comment.