Skip to content

Commit bef756e

Browse files
authored
Allow specifying settings field from Cirq-ionq (#5817)
1 parent fd18da5 commit bef756e

File tree

5 files changed

+80
-15
lines changed

5 files changed

+80
-15
lines changed

cirq-ionq/cirq_ionq/ionq_client.py

+2
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ def create_job(
142142
json['name'] = name
143143
# We have to pass measurement keys through the metadata.
144144
json['metadata'] = serialized_program.metadata
145+
if serialized_program.settings:
146+
json['settings'] = serialized_program.settings
145147

146148
# Shots are ignored by simulator, but pass them anyway.
147149
json['shots'] = str(repetitions)

cirq-ionq/cirq_ionq/ionq_client_test.py

+28-12
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ def test_ionq_client_create_job(mock_post):
9797

9898
client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
9999
program = ionq.SerializedProgram(
100-
body={'job': 'mine'}, metadata={'a': '0,1'}, error_mitigation={'debias': True}
100+
body={'job': 'mine'},
101+
metadata={'a': '0,1'},
102+
settings={'aaa': 'bb'},
103+
error_mitigation={'debias': True},
101104
)
102105
response = client.create_job(
103106
serialized_program=program, repetitions=200, target='qpu', name='bacon'
@@ -109,9 +112,10 @@ def test_ionq_client_create_job(mock_post):
109112
'lang': 'json',
110113
'body': {'job': 'mine'},
111114
'name': 'bacon',
115+
'metadata': {'shots': '200', 'a': '0,1'},
116+
'settings': {'aaa': 'bb'},
112117
'shots': '200',
113118
'error_mitigation': {'debias': True},
114-
'metadata': {'shots': '200', 'a': '0,1'},
115119
}
116120
expected_headers = {
117121
'Authorization': 'apiKey to_my_heart',
@@ -129,7 +133,7 @@ def test_ionq_client_create_job_extra_params(mock_post):
129133
mock_post.return_value.json.return_value = {'foo': 'bar'}
130134

131135
client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
132-
program = ionq.SerializedProgram(body={'job': 'mine'}, metadata={'a': '0,1'})
136+
program = ionq.SerializedProgram(body={'job': 'mine'}, metadata={'a': '0,1'}, settings={})
133137
response = client.create_job(
134138
serialized_program=program,
135139
repetitions=200,
@@ -166,7 +170,7 @@ def test_ionq_client_create_job_default_target(mock_post):
166170
client = ionq.ionq_client._IonQClient(
167171
remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
168172
)
169-
_ = client.create_job(ionq.SerializedProgram(body={'job': 'mine'}, metadata={}))
173+
_ = client.create_job(ionq.SerializedProgram(body={'job': 'mine'}, metadata={}, settings={}))
170174
assert mock_post.call_args[1]['json']['target'] == 'simulator'
171175

172176

@@ -179,7 +183,7 @@ def test_ionq_client_create_job_target_overrides_default_target(mock_post):
179183
remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
180184
)
181185
_ = client.create_job(
182-
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={}),
186+
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={}, settings={}),
183187
target='qpu',
184188
repetitions=1,
185189
)
@@ -190,7 +194,9 @@ def test_ionq_client_create_job_no_targets():
190194
client = ionq.ionq_client._IonQClient(remote_host='http://example.com', api_key='to_my_heart')
191195
with pytest.raises(AssertionError, match='neither were set'):
192196
_ = client.create_job(
193-
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
197+
serialized_program=ionq.SerializedProgram(
198+
body={'job': 'mine'}, metadata={}, settings={}
199+
)
194200
)
195201

196202

@@ -204,7 +210,9 @@ def test_ionq_client_create_job_unauthorized(mock_post):
204210
)
205211
with pytest.raises(ionq.IonQException, match='Not authorized'):
206212
_ = client.create_job(
207-
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
213+
serialized_program=ionq.SerializedProgram(
214+
body={'job': 'mine'}, metadata={}, settings={}
215+
)
208216
)
209217

210218

@@ -218,7 +226,9 @@ def test_ionq_client_create_job_not_found(mock_post):
218226
)
219227
with pytest.raises(ionq.IonQNotFoundException, match='not find'):
220228
_ = client.create_job(
221-
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
229+
serialized_program=ionq.SerializedProgram(
230+
body={'job': 'mine'}, metadata={}, settings={}
231+
)
222232
)
223233

224234

@@ -232,7 +242,9 @@ def test_ionq_client_create_job_not_retriable(mock_post):
232242
)
233243
with pytest.raises(ionq.IonQException, match='Status: 409'):
234244
_ = client.create_job(
235-
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
245+
serialized_program=ionq.SerializedProgram(
246+
body={'job': 'mine'}, metadata={}, settings={}
247+
)
236248
)
237249

238250

@@ -253,7 +265,9 @@ def test_ionq_client_create_job_retry(mock_post):
253265
test_stdout = io.StringIO()
254266
with contextlib.redirect_stdout(test_stdout):
255267
_ = client.create_job(
256-
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
268+
serialized_program=ionq.SerializedProgram(
269+
body={'job': 'mine'}, metadata={}, settings={}
270+
)
257271
)
258272
assert test_stdout.getvalue().strip() == 'Waiting 0.1 seconds before retrying.'
259273
assert mock_post.call_count == 2
@@ -268,7 +282,7 @@ def test_ionq_client_create_job_retry_request_error(mock_post):
268282
remote_host='http://example.com', api_key='to_my_heart', default_target='simulator'
269283
)
270284
_ = client.create_job(
271-
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
285+
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={}, settings={})
272286
)
273287
assert mock_post.call_count == 2
274288

@@ -286,7 +300,9 @@ def test_ionq_client_create_job_timeout(mock_post):
286300
)
287301
with pytest.raises(TimeoutError):
288302
_ = client.create_job(
289-
serialized_program=ionq.SerializedProgram(body={'job': 'mine'}, metadata={})
303+
serialized_program=ionq.SerializedProgram(
304+
body={'job': 'mine'}, metadata={}, settings={}
305+
)
290306
)
291307

292308

cirq-ionq/cirq_ionq/serializer.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,13 @@ class SerializedProgram:
3535
Attributes:
3636
body: A dictionary which contains the number of qubits and the serialized circuit
3737
minus the measurements.
38+
settings: A dictionary of settings which can override behavior for this circuit when
39+
run on IonQ hardware.
3840
metadata: A dictionary whose keys store information about the measurements in the circuit.
3941
"""
4042

4143
body: dict
44+
settings: dict
4245
metadata: dict
4346
error_mitigation: Optional[dict] = None
4447

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

7982
def serialize(
80-
self, circuit: cirq.AbstractCircuit, error_mitigation: Optional[dict] = None
83+
self,
84+
circuit: cirq.AbstractCircuit,
85+
job_settings: Optional[dict] = None,
86+
error_mitigation: Optional[dict] = None,
8187
) -> SerializedProgram:
8288
"""Serialize the given circuit.
8389
@@ -100,7 +106,12 @@ def serialize(
100106
}
101107
metadata = self._serialize_measurements(op for op in serialized_ops if op['gate'] == 'meas')
102108

103-
return SerializedProgram(body=body, metadata=metadata, error_mitigation=error_mitigation)
109+
return SerializedProgram(
110+
body=body,
111+
metadata=metadata,
112+
settings=(job_settings or {}),
113+
error_mitigation=error_mitigation,
114+
)
104115

105116
def _validate_circuit(self, circuit: cirq.AbstractCircuit):
106117
if len(circuit) == 0:

cirq-ionq/cirq_ionq/serializer_test.py

+28
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ def test_serialize_implicit_num_qubits():
5959
assert result.body['qubits'] == 3
6060

6161

62+
def test_serialize_settings():
63+
q0 = cirq.LineQubit(2)
64+
circuit = cirq.Circuit(cirq.X(q0))
65+
serializer = ionq.Serializer()
66+
result = serializer.serialize(circuit, job_settings={"foo": "bar", "key": "heart"})
67+
assert result == ionq.SerializedProgram(
68+
body={'gateset': 'qis', 'qubits': 3, 'circuit': [{'gate': 'x', 'targets': [2]}]},
69+
metadata={},
70+
settings={"foo": "bar", "key": "heart"},
71+
)
72+
73+
6274
def test_serialize_non_gate_op_invalid():
6375
q0 = cirq.LineQubit(0)
6476
circuit = cirq.Circuit(cirq.X(q0), cirq.CircuitOperation(cirq.FrozenCircuit()))
@@ -89,6 +101,7 @@ def test_serialize_pow_gates():
89101
'circuit': [{'gate': name, 'targets': [0], 'rotation': exponent * np.pi}],
90102
},
91103
metadata={},
104+
settings={},
92105
)
93106

94107

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

106120

@@ -112,12 +126,14 @@ def test_serialize_sqrt_x_gate():
112126
assert result == ionq.SerializedProgram(
113127
body={'gateset': 'qis', 'qubits': 1, 'circuit': [{'gate': 'v', 'targets': [0]}]},
114128
metadata={},
129+
settings={},
115130
)
116131
circuit = cirq.Circuit(cirq.X(q0) ** (-0.5))
117132
result = serializer.serialize(circuit)
118133
assert result == ionq.SerializedProgram(
119134
body={'gateset': 'qis', 'qubits': 1, 'circuit': [{'gate': 'vi', 'targets': [0]}]},
120135
metadata={},
136+
settings={},
121137
)
122138

123139

@@ -129,12 +145,14 @@ def test_serialize_s_gate():
129145
assert result == ionq.SerializedProgram(
130146
body={'gateset': 'qis', 'qubits': 1, 'circuit': [{'gate': 's', 'targets': [0]}]},
131147
metadata={},
148+
settings={},
132149
)
133150
circuit = cirq.Circuit(cirq.Z(q0) ** (-0.5))
134151
result = serializer.serialize(circuit)
135152
assert result == ionq.SerializedProgram(
136153
body={'gateset': 'qis', 'qubits': 1, 'circuit': [{'gate': 'si', 'targets': [0]}]},
137154
metadata={},
155+
settings={},
138156
)
139157

140158

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

151170
with pytest.raises(ValueError, match=r'H\*\*0.5'):
@@ -161,12 +180,14 @@ def test_serialize_t_gate():
161180
assert result == ionq.SerializedProgram(
162181
body={'gateset': 'qis', 'qubits': 1, 'circuit': [{'gate': 't', 'targets': [0]}]},
163182
metadata={},
183+
settings={},
164184
)
165185
circuit = cirq.Circuit(cirq.Z(q0) ** (-0.25))
166186
result = serializer.serialize(circuit)
167187
assert result == ionq.SerializedProgram(
168188
body={'gateset': 'qis', 'qubits': 1, 'circuit': [{'gate': 'ti', 'targets': [0]}]},
169189
metadata={},
190+
settings={},
170191
)
171192

172193

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

189211

@@ -199,6 +221,7 @@ def test_serialize_cnot_gate():
199221
'circuit': [{'gate': 'cnot', 'control': 0, 'target': 1}],
200222
},
201223
metadata={},
224+
settings={},
202225
)
203226

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

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

234259

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

245271

@@ -271,6 +297,7 @@ def test_serialize_native_gates():
271297
],
272298
},
273299
metadata={},
300+
settings={},
274301
)
275302

276303

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

287315

cirq-ionq/cirq_ionq/service.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def __init__(
3737
default_target: Optional[str] = None,
3838
api_version='v0.3',
3939
max_retry_seconds: int = 3600,
40+
job_settings: Optional[dict] = None,
4041
verbose=False,
4142
):
4243
"""Creates the Service to access IonQ's API.
@@ -55,6 +56,8 @@ def __init__(
5556
'simulator'.
5657
api_version: Version of the api. Defaults to 'v0.3'.
5758
max_retry_seconds: The number of seconds to retry calls for. Defaults to one hour.
59+
job_settings: A dictionary of settings which can override behavior for circuits when
60+
run on IonQ hardware.
5861
verbose: Whether to print to stdio and stderr on retriable errors.
5962
6063
Raises:
@@ -67,7 +70,10 @@ def __init__(
6770
or os.getenv('IONQ_REMOTE_HOST')
6871
or f'https://api.ionq.co/{api_version}'
6972
)
73+
74+
self.job_settings = job_settings or {}
7075
self.api_key = api_key or os.getenv('CIRQ_IONQ_API_KEY') or os.getenv('IONQ_API_KEY')
76+
7177
if not self.api_key:
7278
raise EnvironmentError(
7379
'Parameter api_key was not specified and the environment variable '
@@ -175,7 +181,9 @@ def create_job(
175181
Raises:
176182
IonQException: If there was an error accessing the API.
177183
"""
178-
serialized_program = serializer.Serializer().serialize(circuit, error_mitigation)
184+
serialized_program = serializer.Serializer().serialize(
185+
circuit, job_settings=self.job_settings, error_mitigation=error_mitigation
186+
)
179187
result = self._client.create_job(
180188
serialized_program=serialized_program,
181189
repetitions=repetitions,

0 commit comments

Comments
 (0)