Skip to content

Remove uses of .device in cirq-pasqal in preparation for larger circuit.device deprecation #4757

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
3 changes: 3 additions & 0 deletions cirq-core/cirq/circuits/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ def moments(self) -> Sequence['cirq.Moment']:
def device(self) -> 'cirq.Device':
pass

# This is going away once device deprecation is finished.
_device = None # type: devices.Device

def freeze(self) -> 'cirq.FrozenCircuit':
"""Creates a FrozenCircuit from this circuit.

Expand Down
16 changes: 10 additions & 6 deletions cirq-pasqal/cirq_pasqal/pasqal_device_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ def with_qubits(self, *new_qubits):

def test_validate_operation_errors():
d = generic_device(3)
circuit = cirq.Circuit(device=d)

with pytest.raises(ValueError, match="Unsupported operation"):
d.validate_operation(cirq.NamedQubit('q0'))
Expand All @@ -178,15 +177,20 @@ def test_validate_operation_errors():
with pytest.raises(ValueError, match="is not part of the device."):
d.validate_operation(cirq.X.on(cirq.NamedQubit('q6')))

d = square_virtual_device(control_r=1.0, num_qubits=3)
with pytest.raises(ValueError, match="are too far away"):
d.validate_operation(cirq.CZ.on(TwoDQubit(0, 0), TwoDQubit(2, 2)))


def test_validate_operation_errors_deprecated():
d = generic_device(3)
circuit = cirq.Circuit()
circuit._device = d
with pytest.raises(
NotImplementedError, match="Measurements on Pasqal devices don't support invert_mask."
):
circuit.append(cirq.measure(*d.qubits, invert_mask=(True, False, False)))

d = square_virtual_device(control_r=1.0, num_qubits=3)
with pytest.raises(ValueError, match="are too far away"):
d.validate_operation(cirq.CZ.on(TwoDQubit(0, 0), TwoDQubit(2, 2)))


def test_validate_moment():
d = square_virtual_device(control_r=1.0, num_qubits=2)
Expand All @@ -202,7 +206,7 @@ def test_validate_moment():

def test_validate_circuit():
d = generic_device(2)
circuit1 = cirq.Circuit(device=d)
circuit1 = cirq.Circuit()
circuit1.append(cirq.X(cirq.NamedQubit('q1')))
circuit1.append(cirq.measure(cirq.NamedQubit('q1')))
d.validate_circuit(circuit1)
Expand Down
4 changes: 2 additions & 2 deletions cirq-pasqal/cirq_pasqal/pasqal_noise_model_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def test_noisy_moments():
circuit = cirq.Circuit()
circuit.append(cirq.ops.CZ(p_qubits[0], p_qubits[1]))
circuit.append(cirq.ops.Z(p_qubits[1]))
p_circuit = cirq.Circuit(circuit, device=p_device)
p_circuit = cirq.Circuit(circuit)

n_mts = []
for moment in p_circuit._moments:
Expand All @@ -65,7 +65,7 @@ def test_default_noise():
circuit = cirq.Circuit()
Gate_l = cirq.ops.CZPowGate(exponent=2)
circuit.append(Gate_l.on(p_qubits[0], p_qubits[1]))
p_circuit = cirq.Circuit(circuit, device=p_device)
p_circuit = cirq.Circuit(circuit)
n_mts = []
for moment in p_circuit._moments:
n_mts.append(noise_model.noisy_moment(moment, p_qubits))
Expand Down
28 changes: 25 additions & 3 deletions cirq-pasqal/cirq_pasqal/pasqal_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,20 @@


class PasqalSampler(cirq.work.Sampler):
def __init__(self, remote_host: str, access_token: str = '') -> None:
def __init__(
self, remote_host: str, access_token: str = '', device: cirq_pasqal.PasqalDevice = None
Copy link
Collaborator

@HGSilveri HGSilveri Dec 15, 2021

Choose a reason for hiding this comment

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

We do require a PasqalDevice to be specified. If that's not going to be done in Circuit anymore, device shouldn't be optional and it must be a PasqalDevice instance.

Copy link
Collaborator Author

@MichaelBroughton MichaelBroughton Dec 15, 2021

Choose a reason for hiding this comment

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

We do require a PasqalDevice to be specified.

I can add the assertion checks at init to make sure it's a pasqal device.

If that's not going to be done in Circuit anymore, device shouldn't be optional and it must be a PasqalDevice instance.

This would make this a breaking change. If this PR were to be merged, both the old and new functionality would still work. Until the deprecation warning is up a user could still attach a device to a circuit or construct a sampler with a device (sampler device taking precedence here). Would you prefer we do a breaking style change and make this argument required ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see what you mean. While the option to attach a device to a circuit is still there, this doesn't need to be required. What if you keep it Optional for now, but demand that there is a PasqalDevice associated? If a PasqalDevice is attached to the circuit, this doesn't have to be specified. Otherwise, it is required. Later on, when the option to have a device attached to a circuit is gone, this is turned into a required argument.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Did you consider this option?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I believe so. To me this sounds like what is currently implemented in the code. Re-reading it now I'm having trouble figuring out what we're missing, would you mind adding a little snippet here in python or pseudocode of what changes we should consider to help me understand ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sure, see my comment on lines 136 to 142.

) -> None:
"""Inits PasqalSampler.

Args:
remote_host: Address of the remote device.
access_token: Access token for the remote api.
device: Optional cirq_pasqal.PasqalDevice to use with
the sampler.
"""
self.remote_host = remote_host
self._authorization_header = {"Authorization": access_token}
self._device = device

def _serialize_circuit(
self,
Expand Down Expand Up @@ -100,6 +105,20 @@ def _send_serialized_circuit(

return result

@cirq._compat.deprecated_parameter(
deadline='v0.15',
fix='The program.device component is going away.'
'Attaching a device to PasqalSampler is now done in __init__.',
parameter_desc='program',
match=lambda args, kwargs: (
len(args) >= 2
and isinstance(args[1], cirq.AbstractCircuit)
and args[1]._device != cirq.UNCONSTRAINED_DEVICE
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can _device be None? I see it set to None above but not to cirq.UNCONSTRAINED_DEVICE. Same with checks below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The _device on a cirq.Circuit cannot be None. It can either be cirq.UUNCONSTRAINED_DEVICE or a user set one.

The _device property on this sampler is an optional[PasqalDevice] so it can be none.

)
or 'program' in kwargs
and isinstance(kwargs['program'], cirq.AbstractCircuit)
and kwargs['program']._device != cirq.UNCONSTRAINED_DEVICE,
)
def run_sweep(
self, program: cirq.AbstractCircuit, params: cirq.study.Sweepable, repetitions: int = 1
) -> List[cirq.study.Result]:
Expand All @@ -114,8 +133,11 @@ def run_sweep(
Result list for this run; one for each possible parameter
resolver.
"""
assert isinstance(program.device, cirq_pasqal.PasqalDevice)
program.device.validate_circuit(program)
device = program._device if program._device != cirq.UNCONSTRAINED_DEVICE else self._device
assert isinstance(
device, cirq_pasqal.PasqalDevice
), "Device must inherit from cirq.PasqalDevice."
device.validate_circuit(program)
trial_results = []

for param_resolver in cirq.study.to_resolvers(params):
Expand Down
60 changes: 50 additions & 10 deletions cirq-pasqal/cirq_pasqal/pasqal_sampler_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,19 @@ def text(self):
return self.json


def _make_sampler() -> cirq_pasqal.PasqalSampler:
def _make_sampler(device) -> cirq_pasqal.PasqalSampler:

sampler = cirq_pasqal.PasqalSampler(remote_host='http://00.00.00/', access_token='N/A')
sampler = cirq_pasqal.PasqalSampler(
remote_host='http://00.00.00/', access_token='N/A', device=device
)
return sampler


def test_pasqal_circuit_init():
qs = cirq.NamedQubit.range(3, prefix='q')
ex_circuit = cirq.Circuit()
ex_circuit.append([[cirq.CZ(qs[i], qs[i + 1]), cirq.X(qs[i + 1])] for i in range(len(qs) - 1)])
device = cirq_pasqal.PasqalDevice(qubits=qs)
test_circuit = cirq.Circuit(device=device)
test_circuit = cirq.Circuit()
test_circuit.append(
[[cirq.CZ(qs[i], qs[i + 1]), cirq.X(qs[i + 1])] for i in range(len(qs) - 1)]
)
Expand All @@ -74,22 +75,22 @@ def test_run_sweep(mock_post, mock_get):
binary = bin(num)[2:].zfill(9)

device = cirq_pasqal.PasqalVirtualDevice(control_radius=1, qubits=qs)
ex_circuit = cirq.Circuit(device=device)
ex_circuit = cirq.Circuit()

for i, b in enumerate(binary[:-1]):
if b == '1':
ex_circuit.append(cirq.X(qs[-i - 1]), strategy=cirq.InsertStrategy.NEW)

ex_circuit_odd = copy.deepcopy(ex_circuit)
ex_circuit_odd.append(cirq.X(qs[0]))
ex_circuit_odd.append(cirq.measure(*qs))
ex_circuit_odd.append(cirq.X(qs[0]), strategy=cirq.InsertStrategy.NEW)
ex_circuit_odd.append(cirq.measure(*qs), strategy=cirq.InsertStrategy.NEW)

xpow = cirq.XPowGate(exponent=par)
ex_circuit.append([xpow(qs[0])])
ex_circuit.append(cirq.measure(*qs))
ex_circuit.append([xpow(qs[0])], strategy=cirq.InsertStrategy.NEW)
ex_circuit.append(cirq.measure(*qs), strategy=cirq.InsertStrategy.NEW)

mock_get.return_value = MockGet(cirq.to_json(ex_circuit_odd))
sampler = _make_sampler()
sampler = _make_sampler(device)

with pytest.raises(ValueError, match="Non-empty moment after measurement"):
wrong_circuit = copy.deepcopy(ex_circuit)
Expand All @@ -102,3 +103,42 @@ def test_run_sweep(mock_post, mock_get):
assert cirq.read_json(json_text=submitted_json) == ex_circuit_odd
assert mock_post.call_count == 2
assert data[1] == ex_circuit_odd


@patch('cirq_pasqal.pasqal_sampler.requests.get')
@patch('cirq_pasqal.pasqal_sampler.requests.post')
def test_run_sweep_device_deprecated(mock_post, mock_get):
"""Test running a sweep.

Encodes a random binary number in the qubits, sweeps between odd and even
without noise and checks if the results match.
"""

qs = [cirq_pasqal.ThreeDQubit(i, j, 0) for i in range(3) for j in range(3)]

par = sympy.Symbol('par')
sweep = cirq.Linspace(key='par', start=0.0, stop=1.0, length=2)

num = np.random.randint(0, 2 ** 9)
binary = bin(num)[2:].zfill(9)

device = cirq_pasqal.PasqalVirtualDevice(control_radius=1, qubits=qs)
ex_circuit = cirq.Circuit()

for i, b in enumerate(binary[:-1]):
if b == '1':
ex_circuit.append(cirq.X(qs[-i - 1]), strategy=cirq.InsertStrategy.NEW)

ex_circuit_odd = copy.deepcopy(ex_circuit)
ex_circuit_odd.append(cirq.X(qs[0]), strategy=cirq.InsertStrategy.NEW)
ex_circuit_odd.append(cirq.measure(*qs), strategy=cirq.InsertStrategy.NEW)

xpow = cirq.XPowGate(exponent=par)
ex_circuit.append([xpow(qs[0])], strategy=cirq.InsertStrategy.NEW)
ex_circuit.append(cirq.measure(*qs), strategy=cirq.InsertStrategy.NEW)

mock_get.return_value = MockGet(cirq.to_json(ex_circuit_odd))
sampler = _make_sampler(device)
ex_circuit._device = device
with cirq.testing.assert_deprecated('The program.device component', deadline='v0.15'):
_ = sampler.run_sweep(program=ex_circuit, params=sweep, repetitions=1)