#
# Copyright © 2022-2024 University of Strasbourg. All Rights Reserved.
# Copyright © 2032-2024 QPerfect. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from mimiqcircuits.proto.qcsrproto import toproto_qcsr, fromproto_qcsr
from mimiqcircuits.bitstrings import bitvec_to_int
from statistics import mean, median, stdev
[docs]
class QCSResults:
"""
Represents the results of quantum computations obtained from quantum cloud services (QCS).
Args:
simulator (str): The name of the quantum simulator.
version (str): The version of the quantum simulator.
fidelities (list): List of fidelity estimates from different executions.
avggateerrors (list): List of average multi-qubit gate errors from different executions.
cstates (list): List of classical states obtained from executions.
zstates (list): Not used in the current implementation.
amplitudes (dict): Dictionary of statevector amplitudes for different quantum states.
timings (dict): Dictionary of timing information for different phases of the computation.
"""
def __init__(
self,
simulator=None,
version=None,
fidelities=None,
avggateerrors=None,
cstates=None,
zstates=None,
amplitudes=None,
timings=None,
):
self.simulator = simulator
self.version = version
self.fidelities = fidelities if fidelities is not None else []
self.avggateerrors = avggateerrors if avggateerrors is not None else []
self.cstates = cstates if cstates is not None else []
self.zstates = zstates if zstates is not None else []
self.amplitudes = amplitudes if amplitudes is not None else {}
self.timings = timings if timings is not None else {}
def __repr__(self):
result_str = "QCSResults:\n"
if self.simulator is not None:
result_str += f"├── simulator: {self.simulator} {self.version}\n"
# filter out the ones < 1e-7
timings = {k: v for k, v in self.timings.items() if v > 1e-7}
result_str += "├── timings:\n"
for key, value in list(timings.items())[:-1]:
result_str += f"│ ├── {key} time: {value}s\n"
key, value = list(timings.items())[-1]
result_str += f"│ └── {key} time: {value}s\n"
if len(self.fidelities) == 1:
result_str += f"├── fidelity estimate: {self.fidelities[0]:.3g}\n"
result_str += f"├── average multi-qubit gate error estimate: {self.avggateerrors[0]:.3g}\n"
elif len(self.fidelities) != 0:
result_str += "├── fidelity estimate:\n"
result_str += f"│ ├── min, max: {min(self.fidelities):.3g}, {max(self.fidelities):.3g}\n"
result_str += f"│ ├── mean: {mean(self.fidelities):.3g}\n"
result_str += f"│ ├── median: {median(self.fidelities):.3g}\n"
result_str += f"│ └── std: {stdev(self.fidelities):.3g}\n"
result_str += "├── average multi-qubit gate error estimate:\n"
result_str += f"│ ├── min, max: {min(self.avggateerrors):.3g}, {max(self.avggateerrors):.3g}\n"
result_str += f"│ ├── mean: {mean(self.avggateerrors):.3g}\n"
result_str += f"│ ├── median: {median(self.avggateerrors):.3g}\n"
result_str += f"│ └── std: {stdev(self.avggateerrors):.3g}\n"
if len(self.cstates) != 0:
hist = self.histogram()
outcomes = sorted(hist, key=hist.get, reverse=True)[0:5]
result_str += "├── most sampled:\n"
for bs in outcomes[:-1]:
result_str += f'│ ├── bs"{bs.to01()}" => {hist[bs]}\n'
bs = outcomes[-1]
result_str += f'│ └── bs"{bs.to01()}" => {hist[bs]}\n'
if len(self.zstates) > 0 and any(len(zstate) > 0 for zstate in self.zstates):
result_str += "├── zreg (most sampled):\n"
z_hist = self.histzvars()
for zstate, count in list(z_hist.items())[:-1]:
result_str += f"│ ├── [{', '.join(map(str, zstate))}] => {count}\n"
zstate, count = list(z_hist.items())[-1]
result_str += f"│ └── [{', '.join(map(str, zstate))}] => {count}\n"
result_str += f"├── {len(self.fidelities)} executions\n"
result_str += f"├── {len(self.amplitudes)} amplitudes\n"
result_str += f"└── {len(self.cstates)} samples"
return result_str
def _repr_html_(self):
html_output = "<table><tbody>"
# QCSResults Header
html_output += '<tr><td colspan=2 style="text-align:center;"><strong>QCSResults</strong></td></tr>'
html_output += "<tr><td colspan=2><hr></td></tr>"
# Simulator Information
html_output += '<tr><td colspan=2 style="text-align:center;"><strong>Simulator</strong></td></tr>'
html_output += f'<tr><td colspan=2 style="text-align:center;">{self.simulator} {self.version}</td></tr>'
html_output += "<tr><td colspan=2><hr></td></tr>"
# Timings
html_output += '<tr><td colspan=2 style="text-align:center;"><strong>Timings</strong></td></tr>'
for key, value in self.timings.items():
html_output += f'<tr><td style="text-align:left;">{key} time</td><td>{value}s</td></tr>'
html_output += "<tr><td colspan=2><hr></td></tr>"
# Fidelity Estimates
if self.fidelities:
html_output += "<tr><td colspan=2></td></tr>"
html_output += '<tr><td colspan=2 style="text-align:center;"><strong>Fidelity estimate</strong></td></tr>'
if len(self.fidelities) == 1:
html_output += f'<tr><td style="text-align:left;">Single run value</td><td>{round(self.fidelities[0], 3)}</td></tr>'
else:
html_output += f'<tr><td style="text-align:left;">Mean</td><td>{round(mean(self.fidelities), 3)}</td></tr>'
html_output += f'<tr><td style="text-align:left;">Median</td><td>{round(median(self.fidelities), 3)}</td></tr>'
html_output += f'<tr><td style="text-align:left;">Standard Deviation</td><td>{round(stdev(self.fidelities), 3)}</td></tr>'
html_output += "<tr><td colspan=2><hr></td></tr>"
# Average Multiqubit Error Estimates
if self.avggateerrors:
html_output += "<tr><td colspan=2></td></tr>"
html_output += '<tr><td colspan=2 style="text-align:center;"><strong>Average multiqubit error estimate</strong></td></tr>'
if len(self.avggateerrors) == 1:
html_output += f'<tr><td style="text-align:left;">Single run value</td><td>{round(self.avggateerrors[0], 3)}</td></tr>'
else:
html_output += f'<tr><td style="text-align:left;">Mean</td><td>{round(mean(self.avggateerrors), 3)}</td></tr>'
html_output += f'<tr><td style="text-align:left;">Median</td><td>{round(median(self.avggateerrors), 3)}</td></tr>'
html_output += f'<tr><td style="text-align:left;">Standard Deviation</td><td>{round(stdev(self.avggateerrors), 3)}</td></tr>'
html_output += "<tr><td colspan=2><hr></td></tr>"
# Statistics
html_output += "<tr><td colspan=2></td></tr>"
html_output += '<tr><td colspan=2 style="text-align:center;"><strong>Statistics</strong></td></tr>'
html_output += f'<tr><td style="text-align:left;">Number of executions</td><td>{len(self.fidelities)}</td></tr>'
html_output += f'<tr><td style="text-align:left;">Number of samples</td><td>{len(self.cstates)}</td></tr>'
html_output += f'<tr><td style="text-align:left;">Number of amplitudes</td><td>{len(self.amplitudes)}</td></tr>'
html_output += "<tr><td colspan=2><hr></td></tr>"
# Sampled Classical States
if self.cstates:
html_output += "<tr><td colspan=2></td></tr>"
html_output += '<tr><td colspan=2 style="text-align:center;"><strong>Samples</strong></td></tr>'
hist = self.histogram()
outcomes = sorted(hist, key=hist.get, reverse=True)[0:10]
for bs in outcomes:
html_output += f'<tr><td style="text-align:left;font-family: monospace;">{hex(bitvec_to_int(bs))}</td><td style="text-align:left;font-family: monospace;">{bs.to01()}</td><td style="font-family: monospace;">{hist[bs]}</td></tr>'
html_output += "<tr><td colspan=2><hr></td></tr>"
# Statevector Amplitudes
if self.amplitudes:
html_output += "<tr><td colspan=2></td></tr>"
html_output += '<tr><td colspan=2 style="text-align:center;"><strong>Statevector Amplitudes</strong></td></tr>'
for bs, amp in self.amplitudes.items():
html_output += f'<tr><td style="text-align:left;">{hex(bitvec_to_int(bs.bits))}</td><td style="text-align:left;">{bs.to01()}</td><td>{amp:.3f}</td></tr>'
html_output += "<tr><td colspan=2><hr></td></tr>"
if len(self.zstates) > 0 and any(len(zstate) > 0 for zstate in self.zstates):
html_output += "<tr><td colspan=2></td></tr>"
html_output += '<tr><td colspan=2 style="text-align:center;"><strong>Z States</strong></td></tr>'
z_hist = self.histzvars()
for zstate, count in z_hist.items():
html_output += f'<tr><td style="text-align:left;font-family: monospace;">[{", ".join(map(str, zstate))}]</td><td style="text-align:left;font-family: monospace;">{count}</td></tr>'
html_output += "<tr><td colspan=2><hr></td></tr>"
html_output += "</tbody></table>"
return html_output
[docs]
def histogram(self):
"""Histogram of the obtained classical states' occurrences.
Returns:
A dictionary of classical states (bitarray) and their occurrences
(float).
Raises:
TypeError: If a non QCSResults object is passed.
"""
hist = {}
for cstate in self.cstates:
if cstate in hist:
hist[cstate] += 1
else:
hist[cstate] = 1
return hist
[docs]
def histzvars(self):
"""
Histogram of the obtained zstates' occurrences.
"""
hist = {}
for zstate in self.zstates:
i = tuple(zstate) # Make it hashable
if i in hist:
hist[i] += 1
else:
hist[i] = 1
return hist
[docs]
def saveproto(self, file):
"""Save QCSResults object to a Protocol Buffers file.
Examples:
>>> from mimiqcircuits import *
>>> import os
>>> from symengine import *
>>> import tempfile
>>> x, y = symbols("x y")
>>> c = Circuit()
>>> c.push(GateH(), 0)
1-qubit circuit with 1 instructions:
└── H @ q[0]
<BLANKLINE>
>>> conn = MimiqConnection(os.getenv("MIMIQCLOUD2"))
>>> conn.connect(os.getenv("MIMIQUSER"), os.getenv("MIMIQPASS"))
Connection:
├── url: https://mimiqfast.qperfect.io/api
├── Computing time: 597/10000 minutes
├── Executions: 451/10000
├── Max time limit per request: 180 minutes
└── status: open
<BLANKLINE>
>>> job = conn.execute(c)
>>> res = conn.get_result(job)
>>> res
QCSResults:
├── simulator: MIMIQ-StateVector 0.18.0
├── timings:
│ ├── parse time: 5.9552e-05s
│ ├── apply time: 1.6682e-05s
│ ├── total time: 0.00019081399999999998s
│ ├── compression time: 4.575e-06s
│ └── sample time: 5.299e-05s
├── fidelity estimate: 1
├── average multi-qubit gate error estimate: 0
├── most sampled:
│ ├── bs"1" => 513
│ └── bs"0" => 487
├── 1 executions
├── 0 amplitudes
└── 1000 samples
>>> tmpfile = tempfile.NamedTemporaryFile(suffix=".pb", delete=True)
>>> res.saveproto(tmpfile.name)
7169
>>> res.loadproto(tmpfile.name)
QCSResults:
├── simulator: MIMIQ-StateVector 0.18.0
├── timings:
│ ├── parse time: 5.9552e-05s
│ ├── apply time: 1.6682e-05s
│ ├── total time: 0.00019081399999999998s
│ ├── compression time: 4.575e-06s
│ └── sample time: 5.299e-05s
├── fidelity estimate: 1
├── average multi-qubit gate error estimate: 0
├── most sampled:
│ ├── bs"1" => 513
│ └── bs"0" => 487
├── 1 executions
├── 0 amplitudes
└── 1000 samples
Note:
This example uses a temporary file to demonstrate the save and load functionality.
You can save your file with any name at any location using:
.. code-block:: python
res.saveproto("example.pb")
res.loadproto("example.pb")
"""
if isinstance(file, str):
with open(file, "wb") as f:
return f.write(toproto_qcsr(self).SerializeToString())
elif hasattr(file, "write"):
return file.write(toproto_qcsr(self).SerializeToString())
[docs]
@staticmethod
def loadproto(file):
"""Load QCSResults object from a Protocol Buffers file.
The :func:`loadproto` method is a static method and should be called on the class,
not on an instance of the class.
Note:
Look for example in :func:`QCSResults.saveproto`
"""
from mimiqcircuits.proto import qcsresults_pb2
qcs_results_proto = qcsresults_pb2.QCSResults()
if isinstance(file, str):
with open(file, "rb") as f:
qcs_results_proto.ParseFromString(f.read())
elif hasattr(file, "read"):
qcs_results_proto.ParseFromString(file.read())
return fromproto_qcsr(qcs_results_proto)
__all__ = ["QCSResults"]