From 448cc43d243239ba7f7eb432f2e2cf9abb8155b9 Mon Sep 17 00:00:00 2001 From: Ivan Basov Date: Tue, 21 Nov 2023 21:09:04 -0800 Subject: [PATCH] support EstimationResult and dictionary items (#529) * support EstimationResult and dictionary items * feedback * unit tests --- .../azure/quantum/target/microsoft/result.py | 14 +- azure-quantum/tests/unit/test_microsoft_qc.py | 142 ++++++++++++++---- 2 files changed, 124 insertions(+), 32 deletions(-) diff --git a/azure-quantum/azure/quantum/target/microsoft/result.py b/azure-quantum/azure/quantum/target/microsoft/result.py index f42ab40ab..fe54f2f50 100644 --- a/azure-quantum/azure/quantum/target/microsoft/result.py +++ b/azure-quantum/azure/quantum/target/microsoft/result.py @@ -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 @@ -18,7 +20,6 @@ def __init__(self, content: str): def _repr_html_(self): return self.content - class MicrosoftEstimatorResult(dict): """ Microsoft Resource Estimator result. @@ -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()) @@ -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 ( @@ -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 "" @@ -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") diff --git a/azure-quantum/tests/unit/test_microsoft_qc.py b/azure-quantum/tests/unit/test_microsoft_qc.py index 3139a386f..fec581b8e 100644 --- a/azure-quantum/tests/unit/test_microsoft_qc.py +++ b/azure-quantum/tests/unit/test_microsoft_qc.py @@ -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. @@ -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): @@ -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" @@ -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" @@ -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 @@ -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): @@ -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): @@ -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): @@ -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