Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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 @@ -140,6 +140,8 @@ def create_job(
json['name'] = name
# We have to pass measurement keys through the metadata.
json['metadata'] = serialized_program.metadata
if len(serialized_program.settings) > 0:
json['settings'] = serialized_program.settings
Comment thread
Cynocracy marked this conversation as resolved.
Outdated

# Shots are ignored by simulator, but pass them anyway.
json['shots'] = str(repetitions)
Expand Down
37 changes: 26 additions & 11 deletions cirq-ionq/cirq_ionq/ionq_client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ def test_ionq_client_create_job(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={'aaa': 'bb'}
)
response = client.create_job(
serialized_program=program, repetitions=200, target='qpu', name='bacon'
)
Expand All @@ -106,8 +108,9 @@ def test_ionq_client_create_job(mock_post):
'lang': 'json',
'body': {'job': 'mine'},
'name': 'bacon',
'metadata': {'a': '0,1', 'shots': '200'},
'settings': {'aaa': 'bb'},
'shots': '200',
'metadata': {'shots': '200', 'a': '0,1'},
}
expected_headers = {
'Authorization': 'apiKey to_my_heart',
Expand All @@ -127,7 +130,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 @@ -140,7 +143,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 @@ -151,7 +154,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 @@ -165,7 +170,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 @@ -179,7 +186,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 @@ -193,7 +202,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 @@ -214,7 +225,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 @@ -229,7 +242,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 @@ -247,7 +260,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
9 changes: 7 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


Expand Down Expand Up @@ -75,7 +78,9 @@ def __init__(self, atol: float = 1e-8):
MSGate: self._serialize_ms_gate,
}

def serialize(self, circuit: cirq.AbstractCircuit) -> SerializedProgram:
def serialize(
self, circuit: cirq.AbstractCircuit, job_settings: Optional[dict] = None
) -> SerializedProgram:
"""Serialize the given circuit.

Raises:
Expand All @@ -97,7 +102,7 @@ def serialize(self, circuit: cirq.AbstractCircuit) -> SerializedProgram:
}
metadata = self._serialize_measurements(op for op in serialized_ops if op['gate'] == 'meas')

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

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
8 changes: 7 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.1',
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.1'.
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 @@ -64,6 +67,7 @@ def __init__(
self.remote_host = (
remote_host 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('IONQ_API_KEY')
if not self.api_key:
raise EnvironmentError(
Expand Down Expand Up @@ -148,7 +152,9 @@ def create_job(
Raises:
IonQException: If there was an error accessing the API.
"""
serialized_program = serializer.Serializer().serialize(circuit)
serialized_program = serializer.Serializer().serialize(
circuit, job_settings=self.job_settings
)
result = self._client.create_job(
serialized_program=serialized_program, repetitions=repetitions, target=target, name=name
)
Expand Down