Skip to content
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

QPU connections doesn't need qemist_client #207

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
10 changes: 6 additions & 4 deletions examples/overview_endtoend.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -701,19 +701,21 @@
"os.environ['QEMIST_AUTH_TOKEN'] = \"your_qemist_authentication_token\"\n",
"\n",
"# Estimate, submit and get the results of your job / quantum task through our wrappers\n",
"from tangelo.linq.qpu_connection import job_submit, job_status, job_cancel, job_result, job_estimate\n",
"from tangelo.linq.qpu_connection import QEMISTCloudConnection\n",
"\n",
"qcloud_connection = QEMISTCloudConnection()\n",
"\n",
"circuit_YY = quantum_circuit[((0, \"Y\"), (1, \"Y\"))]\n",
"\n",
"price_estimates = job_estimate(circuit_YY, n_shots=n_shots)\n",
"price_estimates = qcloud_connection.job_estimate(circuit_YY, n_shots=n_shots)\n",
"print(price_estimates)\n",
"\n",
"backend = 'arn:aws:braket:::device/qpu/ionq/ionQdevice'\n",
"\n",
"# This two commands would respecfully submit the job to the quantum device and make a blocking call\n",
"# to retrieve the results, through a job ID returned by QEMIST Cloud\n",
"# job_id = job_submit(circuit_YY, n_shots=n_shots, backend=backend)\n",
"# freqs, raw_data = job_result(job_id)\n",
"# job_id = qcloud_connection.job_submit(circuit_YY, n_shots=n_shots, backend=backend)\n",
"# freqs, raw_data = qcloud_connection.job_results(job_id)\n",
"```\n",
"\n",
"Output:\n",
Expand Down
19 changes: 10 additions & 9 deletions examples/qemist_cloud_hardware_experiments_braket.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@
"metadata": {},
"outputs": [],
"source": [
"from tangelo.linq.qpu_connection import job_submit, job_status, job_cancel, job_result, job_estimate"
"from tangelo.linq.qpu_connection import QEMISTCloudConnection\n",
"qcloud_connection = QEMISTCloudConnection()"
]
},
{
Expand Down Expand Up @@ -141,7 +142,7 @@
}
],
"source": [
"price_estimates = job_estimate(circuit, n_shots=1000)\n",
"price_estimates = qcloud_connection.job_estimate(circuit, n_shots=1000)\n",
"print(price_estimates)"
]
},
Expand Down Expand Up @@ -190,7 +191,7 @@
}
],
"source": [
"job_id = job_submit(circuit, n_shots=100, backend=backend)\n",
"job_id = qcloud_connection.job_submit(circuit, n_shots=100, backend=backend)\n",
"print(f\"Job submitted with job id :: {job_id}\")"
]
},
Expand Down Expand Up @@ -219,7 +220,7 @@
}
],
"source": [
"print(job_status(job_id))"
"print(qcloud_connection.job_status(job_id))"
]
},
{
Expand All @@ -237,7 +238,7 @@
"metadata": {},
"outputs": [],
"source": [
"# print(job_cancel(job_id))"
"# print(qcloud_connection.job_cancel(job_id))"
]
},
{
Expand Down Expand Up @@ -272,7 +273,7 @@
}
],
"source": [
"freqs, raw_data = job_result(job_id)\n",
"freqs, raw_data = qcloud_connection.job_results(job_id)\n",
"print(f\"Frequencies :: {freqs}\")"
]
},
Expand Down Expand Up @@ -305,9 +306,9 @@
],
"metadata": {
"kernelspec": {
"display_name": "tangelo_docs_aesthetics",
"display_name": "qemist",
"language": "python",
"name": "tangelo_docs_aesthetics"
"name": "qemist"
},
"language_info": {
"codemirror_mode": {
Expand All @@ -319,7 +320,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.10"
"version": "3.9.9"
}
},
"nbformat": 4,
Expand Down
2 changes: 1 addition & 1 deletion tangelo/linq/qpu_connection/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from .qemist_cloud_connection import job_submit, job_status, job_cancel, job_result, job_estimate
from .qemist_cloud_connection import QEMISTCloudConnection
from .ionq_connection import IonQConnection
281 changes: 142 additions & 139 deletions tangelo/linq/qpu_connection/qemist_cloud_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,142 +19,145 @@
QEMIST_PROJECT_ID with values retrieved from their QEMIST Cloud dashboard.
"""

try:
import qemist_client.util as qclient_util
except ModuleNotFoundError:
print("qemist_client python package not found (optional dependency for hardware experiment submission)")


def job_submit(circuit, n_shots, backend):
"""Job submission to run a circuit on quantum hardware.

Args:
circuit: a quantum circuit in the abstract format.
n_shots (int): the number of shots.
backend (str): the identifier string for the desired backend.

Returns:
int: A problem handle / job ID that can be used to retrieve the result
or cancel the problem.
"""

# Serialize circuit data
circuit_data = circuit.serialize()

# Build option dictionary
job_options = {'shots': n_shots, 'backend': backend}

# Submit the problem
qemist_cloud_job_id = qclient_util.solve_quantum_circuits_async(serialized_fragment=circuit_data,
serialized_solver=job_options)[0]
return qemist_cloud_job_id


def job_status(qemist_cloud_job_id):
"""Returns the current status of the problem, as a string. Possible values:
ready, in_progress, complete, cancelled.

Args:
qemist_cloud_job_id (int): problem handle / job identifier.

Returns:
str: current status of the problem, as a string.
"""
res = qclient_util.get_problem_statuses(qemist_cloud_job_id)

return res


def job_cancel(qemist_cloud_job_id):
"""Cancels the job matching the input job id, if done in time before it
starts.

Args:
qemist_cloud_job_id (int): problem handle / job identifier.

Returns:
dict: cancelled problems / subproblems.
"""
res = qclient_util.cancel_problems(qemist_cloud_job_id)
# TODO: If res is coming out as an error code, we should raise an error

return res


def job_result(qemist_cloud_job_id):
"""Blocks until the job results are available. Returns a tuple containing
the histogram of frequencies, and also the more in-depth raw data from the
cloud services provider as a nested dictionary

Args:
qemist_cloud_job_id (int): problem handle / job identifier.

Returns:
dict: Histogram of measurement frequencies.
dict: The cloud provider raw data.
"""

try:
qclient_util.monitor_problem_status(problem_handles=[qemist_cloud_job_id], verbose=False)

except KeyboardInterrupt:
print(f"\nYour problem is still running with id {qemist_cloud_job_id}.\n")
command = input("Type 'cancel' and return to cancel your problem."
"Type anything else to disconnect but keep the problem running.\n")
if command.lower() == "cancel":
ret = job_cancel(qemist_cloud_job_id)
print("Problem cancelled.", ret)
else:
print(f"Reconnect and block until the problem is complete with "
f"qemist_client.util.monitor_problem_status({qemist_cloud_job_id}).\n\n")
raise

except Exception:
print(f"\n\nYour problem is still running with handle {qemist_cloud_job_id}.\n"
f"Cancel the problem with qemist_client.util.cancel_problems({qemist_cloud_job_id}).\n"
f"Reconnect and block until the problem is complete with qemist_client.util.monitor_problem_status({qemist_cloud_job_id}).\n\n")
raise

# Once a result is available, retrieve it.
# If the util module is not found earlier, an error has been raised.
output = qclient_util.get_results(qemist_cloud_job_id)

# Amazon Braket: parsing of output
freqs = output['results']['measurement_probabilities']
raw_data = output

return freqs, raw_data


def job_estimate(circuit, n_shots, backend=None):
"""Returns an estimate of the cost of running an experiment, for a specified backend
or all backends available. Some service providers care about the
complexity / structure of the input quantum circuit, some do not.

The backend identifier strings that a user can provide as argument can be obtained
by calling this function without specifying a backend. They appear as keys in
the returned dictionary. These strings may change with time, as we adjust to the
growing cloud quantum offer (services and devices).

Args:
circuit (Circuit): the abstract circuit to be run on the target device.
n_shots (int): number of shots in the expriment.
backend (str): the identifier string for the desired backend.

Returns:
dict: Returns dict of prices in USD. If backend is not None, dictionary
contains the cost for running the desired job. If backend is None,
returns dictionary of prices for all supported backends.
"""

# Serialize circuit data
circuit_data = circuit.serialize()

# Build option dictionary
job_options = {'shots': n_shots}
if backend:
job_options['backend'] = backend

price_estimate = qclient_util.check_qpu_cost(circuit_data, job_options)

return price_estimate
from tangelo.linq.qpu_connection.qpu_connection import QpuConnection


class QEMISTCloudConnection(QpuConnection):
""" Wrapper about the QEMIST Cloud connection to QPUs. """

def __init__(self):
try:
import qemist_client.util as qclient_util
except ModuleNotFoundError:
raise ModuleNotFoundError("qemist_client python package not found (optional dependency for hardware experiment submission)")
self.qclient_util = qclient_util

def job_submit(self, circuit, n_shots, backend):
"""Job submission to run a circuit on quantum hardware.

Args:
circuit: a quantum circuit in the abstract format.
n_shots (int): the number of shots.
backend (str): the identifier string for the desired backend.

Returns:
int: A problem handle / job ID that can be used to retrieve the result
or cancel the problem.
"""

# Serialize circuit data
circuit_data = circuit.serialize()

# Build option dictionary
job_options = {'shots': n_shots, 'backend': backend}

# Submit the problem
qemist_cloud_job_id = self.qclient_util.solve_quantum_circuits_async(serialized_fragment=circuit_data,
serialized_solver=job_options)[0]
return qemist_cloud_job_id

def job_status(self, qemist_cloud_job_id):
"""Returns the current status of the problem, as a string. Possible values:
ready, in_progress, complete, cancelled.

Args:
qemist_cloud_job_id (int): problem handle / job identifier.

Returns:
str: current status of the problem, as a string.
"""
res = self.qclient_util.get_problem_statuses(qemist_cloud_job_id)

return res

def job_cancel(self, qemist_cloud_job_id):
"""Cancels the job matching the input job id, if done in time before it
starts.

Args:
qemist_cloud_job_id (int): problem handle / job identifier.

Returns:
dict: cancelled problems / subproblems.
"""
res = self.qclient_util.cancel_problems(qemist_cloud_job_id)
# TODO: If res is coming out as an error code, we should raise an error

return res

def job_results(self, qemist_cloud_job_id):
"""Blocks until the job results are available. Returns a tuple containing
the histogram of frequencies, and also the more in-depth raw data from the
cloud services provider as a nested dictionary

Args:
qemist_cloud_job_id (int): problem handle / job identifier.

Returns:
dict: Histogram of measurement frequencies.
dict: The cloud provider raw data.
"""

try:
self.qclient_util.monitor_problem_status(problem_handles=[qemist_cloud_job_id], verbose=False)

except KeyboardInterrupt:
print(f"\nYour problem is still running with id {qemist_cloud_job_id}.\n")
command = input("Type 'cancel' and return to cancel your problem."
"Type anything else to disconnect but keep the problem running.\n")
if command.lower() == "cancel":
ret = self.job_cancel(qemist_cloud_job_id)
print("Problem cancelled.", ret)
else:
print(f"Reconnect and block until the problem is complete with "
f"qemist_client.util.monitor_problem_status({qemist_cloud_job_id}).\n\n")
raise

except Exception:
print(f"\n\nYour problem is still running with handle {qemist_cloud_job_id}.\n"
f"Cancel the problem with qemist_client.util.cancel_problems({qemist_cloud_job_id}).\n"
f"Reconnect and block until the problem is complete with qemist_client.util.monitor_problem_status({qemist_cloud_job_id}).\n\n")
raise

# Once a result is available, retrieve it.
# If the util module is not found earlier, an error has been raised.
output = self.qclient_util.get_results(qemist_cloud_job_id)

# Amazon Braket: parsing of output
freqs = output['results']['measurement_probabilities']
raw_data = output

return freqs, raw_data

def job_estimate(self, circuit, n_shots, backend=None):
"""Returns an estimate of the cost of running an experiment, for a specified backend
or all backends available. Some service providers care about the
complexity / structure of the input quantum circuit, some do not.

The backend identifier strings that a user can provide as argument can be obtained
by calling this function without specifying a backend. They appear as keys in
the returned dictionary. These strings may change with time, as we adjust to the
growing cloud quantum offer (services and devices).

Args:
circuit (Circuit): the abstract circuit to be run on the target device.
n_shots (int): number of shots in the expriment.
backend (str): the identifier string for the desired backend.

Returns:
dict: Returns dict of prices in USD. If backend is not None, dictionary
contains the cost for running the desired job. If backend is None,
returns dictionary of prices for all supported backends.
"""

# Serialize circuit data
circuit_data = circuit.serialize()

# Build option dictionary
job_options = {'shots': n_shots}
if backend:
job_options['backend'] = backend

price_estimate = self.qclient_util.check_qpu_cost(circuit_data, job_options)

return price_estimate