Skip to content

Commit

Permalink
Allow specifying settings field from Cirq-ionq (#5817)
Browse files Browse the repository at this point in the history
  • Loading branch information
Cynocracy authored Sep 28, 2023
1 parent fd18da5 commit bef756e
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 15 deletions.
2 changes: 2 additions & 0 deletions cirq-ionq/cirq_ionq/ionq_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ def create_job(
json['name'] = name
# We have to pass measurement keys through the metadata.
json['metadata'] = serialized_program.metadata
if serialized_program.settings:
json['settings'] = serialized_program.settings

# Shots are ignored by simulator, but pass them anyway.
json['shots'] = str(repetitions)
Expand Down
40 changes: 28 additions & 12 deletions cirq-ionq/cirq_ionq/ionq_client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ def test_ionq_client_create_job(mock_post):

client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
program = ionq.SerializedProgram(
body={'job': 'mine'}, metadata={'a': '0,1'}, error_mitigation={'debias': True}
body={'job': 'mine'},
metadata={'a': '0,1'},
settings={'aaa': 'bb'},
error_mitigation={'debias': True},
)
response = client.create_job(
serialized_program=program, repetitions=200, target='qpu', name='bacon'
Expand All @@ -109,9 +112,10 @@ def test_ionq_client_create_job(mock_post):
'lang': 'json',
'body': {'job': 'mine'},
'name': 'bacon',
'metadata': {'shots': '200', 'a': '0,1'},
'settings': {'aaa': 'bb'},
'shots': '200',
'error_mitigation': {'debias': True},
'metadata': {'shots': '200', 'a': '0,1'},
}
expected_headers = {
'Authorization': 'apiKey to_my_heart',
Expand All @@ -129,7 +133,7 @@ def test_ionq_client_create_job_extra_params(mock_post):
mock_post.return_value.json.return_value = {'foo': 'bar'}

client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
program = ionq.SerializedProgram(body={'job': 'mine'}, metadata={'a': '0,1'})
program = ionq.SerializedProgram(body={'job': 'mine'}, metadata={'a': '0,1'}, settings={})
response = client.create_job(
serialized_program=program,
repetitions=200,
Expand Down Expand Up @@ -166,7 +170,7 @@ def test_ionq_client_create_job_default_target(mock_post):
client = ionq.ionq_client._IonQClient(
remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
)
_ = client.create_job(ionq.SerializedProgram(body={'job': 'mine'}, metadata={}))
_ = client.create_job(ionq.SerializedProgram(body={'job': 'mine'}, metadata={}, settings={}))
assert mock_post.call_args[1]['json']['target'] == 'simulator'


Expand All @@ -179,7 +183,7 @@ def test_ionq_client_create_job_target_overrides_default_target(mock_post):
remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
)
_ = client.create_job(
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={}),
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={}, settings={}),
target='qpu',
repetitions=1,
)
Expand All @@ -190,7 +194,9 @@ def test_ionq_client_create_job_no_targets():
client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
with pytest.raises(AssertionError, match='neither were set'):
_ = client.create_job(
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
serialized_program=ionq.SerializedProgram(
body={'job': 'mine'}, metadata={}, settings={}
)
)


Expand All @@ -204,7 +210,9 @@ def test_ionq_client_create_job_unauthorized(mock_post):
)
with pytest.raises(ionq.IonQException, match='Not authorized'):
_ = client.create_job(
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
serialized_program=ionq.SerializedProgram(
body={'job': 'mine'}, metadata={}, settings={}
)
)


Expand All @@ -218,7 +226,9 @@ def test_ionq_client_create_job_not_found(mock_post):
)
with pytest.raises(ionq.IonQNotFoundException, match='not find'):
_ = client.create_job(
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
serialized_program=ionq.SerializedProgram(
body={'job': 'mine'}, metadata={}, settings={}
)
)


Expand All @@ -232,7 +242,9 @@ def test_ionq_client_create_job_not_retriable(mock_post):
)
with pytest.raises(ionq.IonQException, match='Status: 409'):
_ = client.create_job(
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
serialized_program=ionq.SerializedProgram(
body={'job': 'mine'}, metadata={}, settings={}
)
)


Expand All @@ -253,7 +265,9 @@ def test_ionq_client_create_job_retry(mock_post):
test_stdout = io.StringIO()
with contextlib.redirect_stdout(test_stdout):
_ = client.create_job(
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
serialized_program=ionq.SerializedProgram(
body={'job': 'mine'}, metadata={}, settings={}
)
)
assert test_stdout.getvalue().strip() == 'Waiting 0.1 seconds before retrying.'
assert mock_post.call_count == 2
Expand All @@ -268,7 +282,7 @@ def test_ionq_client_create_job_retry_request_error(mock_post):
remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
)
_ = client.create_job(
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={}, settings={})
)
assert mock_post.call_count == 2

Expand All @@ -286,7 +300,9 @@ def test_ionq_client_create_job_timeout(mock_post):
)
with pytest.raises(TimeoutError):
_ = client.create_job(
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
serialized_program=ionq.SerializedProgram(
body={'job': 'mine'}, metadata={}, settings={}
)
)


Expand Down
15 changes: 13 additions & 2 deletions cirq-ionq/cirq_ionq/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@ class SerializedProgram:
Attributes:
body: A dictionary which contains the number of qubits and the serialized circuit
minus the measurements.
settings: A dictionary of settings which can override behavior for this circuit when
run on IonQ hardware.
metadata: A dictionary whose keys store information about the measurements in the circuit.
"""

body: dict
settings: dict
metadata: dict
error_mitigation: Optional[dict] = None

Expand Down Expand Up @@ -77,7 +80,10 @@ def __init__(self, atol: float = 1e-8):
}

def serialize(
self, circuit: cirq.AbstractCircuit, error_mitigation: Optional[dict] = None
self,
circuit: cirq.AbstractCircuit,
job_settings: Optional[dict] = None,
error_mitigation: Optional[dict] = None,
) -> SerializedProgram:
"""Serialize the given circuit.
Expand All @@ -100,7 +106,12 @@ def serialize(
}
metadata = self._serialize_measurements(op for op in serialized_ops if op['gate'] == 'meas')

return SerializedProgram(body=body, metadata=metadata, error_mitigation=error_mitigation)
return SerializedProgram(
body=body,
metadata=metadata,
settings=(job_settings or {}),
error_mitigation=error_mitigation,
)

def _validate_circuit(self, circuit: cirq.AbstractCircuit):
if len(circuit) == 0:
Expand Down
28 changes: 28 additions & 0 deletions cirq-ionq/cirq_ionq/serializer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ def test_serialize_implicit_num_qubits():
assert result.body['qubits'] == 3


def test_serialize_settings():
q0 = cirq.LineQubit(2)
circuit = cirq.Circuit(cirq.X(q0))
serializer = ionq.Serializer()
result = serializer.serialize(circuit, job_settings={"foo": "bar", "key": "heart"})
assert result == ionq.SerializedProgram(
body={'gateset': 'qis', 'qubits': 3, 'circuit': [{'gate': 'x', 'targets': [2]}]},
metadata={},
settings={"foo": "bar", "key": "heart"},
)


def test_serialize_non_gate_op_invalid():
q0 = cirq.LineQubit(0)
circuit = cirq.Circuit(cirq.X(q0), cirq.CircuitOperation(cirq.FrozenCircuit()))
Expand Down Expand Up @@ -89,6 +101,7 @@ def test_serialize_pow_gates():
'circuit': [{'gate': name, 'targets': [0], 'rotation': exponent * np.pi}],
},
metadata={},
settings={},
)


Expand All @@ -101,6 +114,7 @@ def test_serialize_pauli_gates():
assert result == ionq.SerializedProgram(
body={'gateset': 'qis', 'qubits': 1, 'circuit': [{'gate': name, 'targets': [0]}]},
metadata={},
settings={},
)


Expand All @@ -112,12 +126,14 @@ def test_serialize_sqrt_x_gate():
assert result == ionq.SerializedProgram(
body={'gateset': 'qis', 'qubits': 1, 'circuit': [{'gate': 'v', 'targets': [0]}]},
metadata={},
settings={},
)
circuit = cirq.Circuit(cirq.X(q0) ** (-0.5))
result = serializer.serialize(circuit)
assert result == ionq.SerializedProgram(
body={'gateset': 'qis', 'qubits': 1, 'circuit': [{'gate': 'vi', 'targets': [0]}]},
metadata={},
settings={},
)


Expand All @@ -129,12 +145,14 @@ def test_serialize_s_gate():
assert result == ionq.SerializedProgram(
body={'gateset': 'qis', 'qubits': 1, 'circuit': [{'gate': 's', 'targets': [0]}]},
metadata={},
settings={},
)
circuit = cirq.Circuit(cirq.Z(q0) ** (-0.5))
result = serializer.serialize(circuit)
assert result == ionq.SerializedProgram(
body={'gateset': 'qis', 'qubits': 1, 'circuit': [{'gate': 'si', 'targets': [0]}]},
metadata={},
settings={},
)


Expand All @@ -146,6 +164,7 @@ def test_serialize_h_gate():
assert result == ionq.SerializedProgram(
body={'gateset': 'qis', 'qubits': 1, 'circuit': [{'gate': 'h', 'targets': [0]}]},
metadata={},
settings={},
)

with pytest.raises(ValueError, match=r'H\*\*0.5'):
Expand All @@ -161,12 +180,14 @@ def test_serialize_t_gate():
assert result == ionq.SerializedProgram(
body={'gateset': 'qis', 'qubits': 1, 'circuit': [{'gate': 't', 'targets': [0]}]},
metadata={},
settings={},
)
circuit = cirq.Circuit(cirq.Z(q0) ** (-0.25))
result = serializer.serialize(circuit)
assert result == ionq.SerializedProgram(
body={'gateset': 'qis', 'qubits': 1, 'circuit': [{'gate': 'ti', 'targets': [0]}]},
metadata={},
settings={},
)


Expand All @@ -184,6 +205,7 @@ def test_serialize_parity_pow_gate():
'circuit': [{'gate': name, 'targets': [0, 1], 'rotation': exponent * np.pi}],
},
metadata={},
settings={},
)


Expand All @@ -199,6 +221,7 @@ def test_serialize_cnot_gate():
'circuit': [{'gate': 'cnot', 'control': 0, 'target': 1}],
},
metadata={},
settings={},
)

with pytest.raises(ValueError, match=r'CNOT\*\*0.5'):
Expand All @@ -214,6 +237,7 @@ def test_serialize_swap_gate():
assert result == ionq.SerializedProgram(
body={'gateset': 'qis', 'qubits': 2, 'circuit': [{'gate': 'swap', 'targets': [0, 1]}]},
metadata={},
settings={},
)

with pytest.raises(ValueError, match=r'SWAP\*\*0.5'):
Expand All @@ -229,6 +253,7 @@ def test_serialize_measurement_gate():
assert result == ionq.SerializedProgram(
body={'gateset': 'native', 'qubits': 1, 'circuit': []},
metadata={'measurement0': f'tomyheart{chr(31)}0'},
settings={},
)


Expand All @@ -240,6 +265,7 @@ def test_serialize_measurement_gate_target_order():
assert result == ionq.SerializedProgram(
body={'gateset': 'native', 'qubits': 3, 'circuit': []},
metadata={'measurement0': f'tomyheart{chr(31)}2,0'},
settings={},
)


Expand Down Expand Up @@ -271,6 +297,7 @@ def test_serialize_native_gates():
],
},
metadata={},
settings={},
)


Expand All @@ -282,6 +309,7 @@ def test_serialize_measurement_gate_multiple_keys():
assert result == ionq.SerializedProgram(
body={'gateset': 'native', 'qubits': 2, 'circuit': []},
metadata={'measurement0': f'a{chr(31)}0{chr(30)}b{chr(31)}1'},
settings={},
)


Expand Down
10 changes: 9 additions & 1 deletion cirq-ionq/cirq_ionq/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __init__(
default_target: Optional[str] = None,
api_version='v0.3',
max_retry_seconds: int = 3600,
job_settings: Optional[dict] = None,
verbose=False,
):
"""Creates the Service to access IonQ's API.
Expand All @@ -55,6 +56,8 @@ def __init__(
'simulator'.
api_version: Version of the api. Defaults to 'v0.3'.
max_retry_seconds: The number of seconds to retry calls for. Defaults to one hour.
job_settings: A dictionary of settings which can override behavior for circuits when
run on IonQ hardware.
verbose: Whether to print to stdio and stderr on retriable errors.
Raises:
Expand All @@ -67,7 +70,10 @@ def __init__(
or os.getenv('IONQ_REMOTE_HOST')
or f'https://api.ionq.co/{api_version}'
)

self.job_settings = job_settings or {}
self.api_key = api_key or os.getenv('CIRQ_IONQ_API_KEY') or os.getenv('IONQ_API_KEY')

if not self.api_key:
raise EnvironmentError(
'Parameter api_key was not specified and the environment variable '
Expand Down Expand Up @@ -175,7 +181,9 @@ def create_job(
Raises:
IonQException: If there was an error accessing the API.
"""
serialized_program = serializer.Serializer().serialize(circuit, error_mitigation)
serialized_program = serializer.Serializer().serialize(
circuit, job_settings=self.job_settings, error_mitigation=error_mitigation
)
result = self._client.create_job(
serialized_program=serialized_program,
repetitions=repetitions,
Expand Down

0 comments on commit bef756e

Please sign in to comment.