Skip to content

Commit

Permalink
support EstimationResult and dictionary items (#529)
Browse files Browse the repository at this point in the history
* support EstimationResult and dictionary items

* feedback

* unit tests
  • Loading branch information
ivanbasov authored Nov 22, 2023
1 parent 4908b19 commit 448cc43
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 32 deletions.
14 changes: 9 additions & 5 deletions azure-quantum/azure/quantum/target/microsoft/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
##
__all__ = ['MicrosoftEstimatorResult']

from typing import Any, Dict, List, Optional, Union

import json
Expand All @@ -18,7 +20,6 @@ def __init__(self, content: str):
def _repr_html_(self):
return self.content


class MicrosoftEstimatorResult(dict):
"""
Microsoft Resource Estimator result.
Expand All @@ -38,7 +39,7 @@ def __init__(self, data: Union[Dict, List]):
super().__init__(data)

self._is_simple = True
if self._is_succeeded():
if MicrosoftEstimatorResult._is_succeeded(self):
self._repr = self._item_result_table()
self.summary = HTMLWrapper(self._item_result_summary_table())
self.diagram = EstimatorResultDiagram(self.data().copy())
Expand Down Expand Up @@ -279,7 +280,7 @@ def _summary_data_frame(self, **kwargs):
labels = labels[:len(self)]

def get_row(result):
if self._is_succeeded():
if MicrosoftEstimatorResult._is_succeeded(result):
formatted = result["physicalCountsFormatted"]

return (
Expand Down Expand Up @@ -409,7 +410,7 @@ def _item_result_summary_table(self):
return html

def _batch_result_table(self, indices):
succeeded_item_indices = [i for i in indices if self[i]._is_succeeded()]
succeeded_item_indices = [i for i in indices if MicrosoftEstimatorResult._is_succeeded(self[i])]
if len(succeeded_item_indices) == 0:
print("None of the jobs succeeded")
return ""
Expand Down Expand Up @@ -471,7 +472,10 @@ def _batch_result_table(self, indices):

return html


@staticmethod
def _is_succeeded(obj):
return 'status' in obj and obj['status'] == "success"

class EstimatorResultDiagram:
def __init__(self, data):
data.pop("reportData")
Expand Down
142 changes: 115 additions & 27 deletions azure-quantum/tests/unit/test_microsoft_qc.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def _ccnot_bitcode(self) -> bytes:
bitcode_filename = path.join(path.dirname(__file__), "qir", "ccnot.bc")
with open(bitcode_filename, "rb") as f:
return f.read()

def _mock_result_data(self) -> dict:
"""
A small result data for tests.
Expand All @@ -43,6 +43,34 @@ def _mock_result_data(self) -> dict:
"reportData": {"groups": [], "assumptions": []}
}

def _mock_result_data_full(self, status) -> dict:
formatted = {
"algorithmicLogicalQubits": 10,
"logicalDepth": 100,
"numTstates": 15,
"numTfactories": 1000,
"physicalQubitsForTfactoriesPercentage": 10,
"physicalQubits": 30000,
"rqops": 45678,
"runtime": 23456789
}

result = {
"physicalCounts": {
"physicalQubits": 655321,
"runtime": 1729,
"rqops": 314
},
"logicalQubit": {
"codeDistance": 11
},
"reportData": {"groups": [], "assumptions": []}
}

result["physicalCountsFormatted"] = formatted
result["status"] = status
return result

@pytest.mark.microsoft_qc
@pytest.mark.live_test
def test_estimator_non_batching_job(self):
Expand Down Expand Up @@ -88,7 +116,7 @@ def test_estimator_batching_job(self):
params.items[0].qubit_params.t_gate_time = "10 ns"
params.items[0].qubit_params.idle_error_rate = 0.00002
params.items[0].qubit_params.two_qubit_joint_measurement_error_rate = \
MeasurementErrorRate(process = 0.00005, readout = 0.00007)
MeasurementErrorRate(process=0.00005, readout=0.00007)

specification1 = DistillationUnitSpecification()
specification1.display_name = "S"
Expand All @@ -110,10 +138,11 @@ def test_estimator_batching_job(self):
specification2 = DistillationUnitSpecification()
specification2.name = "15-1 RM"

specification3= DistillationUnitSpecification()
specification3 = DistillationUnitSpecification()
specification3.name = "15-1 space-efficient"

params.items[0].distillation_unit_specifications = [specification1, specification2, specification3]
params.items[0].distillation_unit_specifications = [
specification1, specification2, specification3]

params.items[1].error_budget = 0.002
params.items[1].constraints.max_duration = "20s"
Expand Down Expand Up @@ -318,21 +347,20 @@ def test_estimator_params_validation_measurement_error_rates_valid(self):
params.qubit_params.idle_error_rate = 0.02
params.qubit_params.one_qubit_measurement_error_rate = 0.01
params.qubit_params.two_qubit_joint_measurement_error_rate = \
MeasurementErrorRate(process = 0.02, readout = 0.03)
MeasurementErrorRate(process=0.02, readout=0.03)

assert params.as_dict() == {
"errorBudget": 0.1,
"qubitParams": {"name": "qubit_gate_ns_e3",
"instructionSet": "gate_based",
"tGateErrorRate": 0.03,
"tGateTime": "10 ns",
"idleErrorRate": 0.02,
"oneQubitMeasurementErrorRate": 0.01,
"twoQubitJointMeasurementErrorRate":
"errorBudget": 0.1,
"qubitParams": {"name": "qubit_gate_ns_e3",
"instructionSet": "gate_based",
"tGateErrorRate": 0.03,
"tGateTime": "10 ns",
"idleErrorRate": 0.02,
"oneQubitMeasurementErrorRate": 0.01,
"twoQubitJointMeasurementErrorRate":
{"process": 0.02, "readout": 0.03}}
}


def test_estimator_error_budget_float(self):
params = MicrosoftEstimatorParams()
params.error_budget = 0.001
Expand Down Expand Up @@ -398,9 +426,9 @@ def test_estimator_custom_distillation_units_name_and_custom_not_allowed_togethe
params.distillation_unit_specifications.append(unit)

with raises(LookupError, match="If predefined name is provided, "
"custom specification is not allowed. "
"Either remove name or remove all other "
"specification of the distillation unit"):
"custom specification is not allowed. "
"Either remove name or remove all other "
"specification of the distillation unit"):
params.as_dict()

def test_estimator_custom_distillation_units_by_specification_short(self):
Expand All @@ -414,9 +442,9 @@ def test_estimator_custom_distillation_units_by_specification_short(self):
params.distillation_unit_specifications.append(unit)

assert params.as_dict() == {
"distillationUnitSpecifications":
"distillationUnitSpecifications":
[{"displayName": "T", "failureProbabilityFormula": "c",
"outputErrorRateFormula": "r", "numInputTs": 1, "numOutputTs": 2 }]
"outputErrorRateFormula": "r", "numInputTs": 1, "numOutputTs": 2}]
}

def test_estimator_custom_distillation_units_by_specification_full(self):
Expand Down Expand Up @@ -449,13 +477,13 @@ def test_estimator_custom_distillation_units_by_specification_full(self):

print(params.as_dict())
assert params.as_dict() == {
"distillationUnitSpecifications":
[{"displayName": "T", "numInputTs": 1, "numOutputTs": 2,
"failureProbabilityFormula": "c", "outputErrorRateFormula": "r",
"physicalQubitSpecification": {"numUnitQubits": 1, "durationInQubitCycleTime": 2},
"logicalQubitSpecification": {"numUnitQubits":3, "durationInQubitCycleTime":4},
"logicalQubitSpecificationFirstRoundOverride":
{"numUnitQubits":5, "durationInQubitCycleTime":6}}]
"distillationUnitSpecifications":
[{"displayName": "T", "numInputTs": 1, "numOutputTs": 2,
"failureProbabilityFormula": "c", "outputErrorRateFormula": "r",
"physicalQubitSpecification": {"numUnitQubits": 1, "durationInQubitCycleTime": 2},
"logicalQubitSpecification": {"numUnitQubits": 3, "durationInQubitCycleTime": 4},
"logicalQubitSpecificationFirstRoundOverride":
{"numUnitQubits": 5, "durationInQubitCycleTime": 6}}]
}

def test_estimator_protocol_specific_distillation_unit_specification_empty_not_allowed(self):
Expand Down Expand Up @@ -496,7 +524,67 @@ def test_batch_result_as_json(self):

import json
assert json.loads(result.json) == data


def test_list_status_all_failed(self):
data = [self._mock_result_data_full(
"error"), self._mock_result_data_full("failure")]
result = MicrosoftEstimatorResult(data)

import json
assert json.loads(result.json) == data

data_frame = result.summary_data_frame()
assert data_frame.values.real[0][0] == "No solution found"
assert data_frame.values.real[1][5] == "No solution found"

assert not hasattr(result[0], "summary")
assert not hasattr(result[1], "summary")

assert not hasattr(result[0], "diagram")
assert not hasattr(result[1], "diagram")

def test_list_status_partial_success(self):
data = [self._mock_result_data_full(
"success"), self._mock_result_data_full("error")]
result = MicrosoftEstimatorResult(data)

import json
assert json.loads(result.json) == data

data_frame = result.summary_data_frame()
assert data_frame.values.real[0][0] == 10
assert data_frame.values.real[1][5] == "No solution found"

assert hasattr(result[0], "summary")
assert not hasattr(result[1], "summary")

assert hasattr(result[0], "diagram")
assert not hasattr(result[1], "diagram")

def test_dict_status_failed(self):
data = self._mock_result_data_full("error")
result = MicrosoftEstimatorResult(data)

import json
assert json.loads(result.json) == data

assert not hasattr(result, "summary_data_frame")

assert not hasattr(result, "summary")
assert not hasattr(result, "diagram")

def test_dict_status_success(self):
data = self._mock_result_data_full("success")
result = MicrosoftEstimatorResult(data)

import json
assert json.loads(result.json) == data

assert not hasattr(result, "summary_data_frame")

assert hasattr(result, "summary")
assert hasattr(result, "diagram")

def test_duration_and_physical_qubits_constraints_not_allowed_together(self):
constraints = MicrosoftEstimatorConstraints()
constraints.max_physical_qubits = 100
Expand Down

0 comments on commit 448cc43

Please sign in to comment.