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
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