From fe83b304e4cc9881992549dce45206b7f295e6b1 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 22 Oct 2024 15:13:28 -0400 Subject: [PATCH 01/10] Simplify runtime usage --- mthree/_helpers.py | 17 ++++++++-------- mthree/mitigation.py | 46 +++++++++++++++++--------------------------- requirements.txt | 1 + 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/mthree/_helpers.py b/mthree/_helpers.py index e57060df..6699c711 100644 --- a/mthree/_helpers.py +++ b/mthree/_helpers.py @@ -32,15 +32,16 @@ def system_info(backend): config = backend.configuration() info_dict["name"] = backend.name info_dict["num_qubits"] = config.num_qubits - _max_shots = backend.configuration().max_shots + _max_shots = config.max_shots info_dict["max_shots"] = _max_shots if _max_shots else int(1e6) - - if isinstance(backend, IBMBackend): - info_dict["simulator"] = False - elif isinstance(backend, BackendV2): - info_dict["simulator"] = True - else: - raise M3Error("Invalid backend passed.") + info_dict["simulator"] = config.simulator + # On IBM systems it is max_experiments, on other stuff it might be max_circuits + max_circuits = getattr(config, "max_experiments", None) + if max_circuits is None: + max_circuits = getattr(config, "max_circuits", 1) + if config.simulator: + max_circuits = 1024 + info_dict["max_circuits"] = max_circuits # Look for faulty qubits. Renaming to 'inoperable' here if hasattr(backend, "properties"): if hasattr(backend.properties(), "faulty_qubits"): diff --git a/mthree/mitigation.py b/mthree/mitigation.py index ba674918..4a239b43 100644 --- a/mthree/mitigation.py +++ b/mthree/mitigation.py @@ -22,8 +22,7 @@ import psutil import numpy as np import orjson -from qiskit.providers import BackendV2 -from qiskit_ibm_runtime import SamplerV2 +import runningman as rm from mthree.circuits import ( @@ -61,7 +60,8 @@ def __init__(self, system=None, iter_threshold=4096): cal_timestamp (str): Time at which cals were taken single_qubit_cals (list): 1Q calibration matrices """ - self.executor = None + if system.__class__.__name__ == 'IBMBackend' and not isinstance(system, rm.RunningManBackend): + system = rm.RunningManBackend(system) self.system = system self.system_info = system_info(system) if system else {} self.single_qubit_cals = None @@ -138,8 +138,8 @@ def cals_from_system( initial_reset=False, rep_delay=None, cals_file=None, - async_cal=False, - mode=None, + async_cal=True, + runtime_mode=None, ): """Grab calibration data from system. @@ -151,8 +151,8 @@ def cals_from_system( initial_reset (bool): Use resets at beginning of calibration circuits, default=False. rep_delay (float): Delay between circuits on IBM Quantum backends. cals_file (str): Output path to write JSON calibration data to. - async_cal (bool): Do calibration async in a separate thread, default is False. - mode (Batch or Session): Mode to run jobs in, default=None + async_cal (bool): Do calibration async in a separate thread, default is True. + runtime_mode (Batch or Session): Mode to run jobs in if using IBM system, default=None Returns: list: List of jobs submitted. @@ -160,17 +160,14 @@ def cals_from_system( Raises: M3Error: Called while a calibration currently in progress. """ - if mode is not None: - executor = SamplerV2(mode=mode) - if mode.backend() != self.system.name: - raise M3Error( - f"Mode backend {mode.backend()} != M3 backend {self.system.name}" - ) - else: - executor = SamplerV2(mode=self.system) - self.executor = executor if self._thread: raise M3Error("Calibration currently in progress.") + + if isinstance(self.system, rm.RunningManBackend): + if runtime_mode: + self.system.set_mode(runtime_mode, overwrite=True) + elif not self.system.get_mode(): + self.system.set_mode('batch') if qubits is None: qubits = range(self.num_qubits) # Remove faulty qubits if any @@ -323,8 +320,6 @@ def _grab_additional_cals( """ if self.system is None: raise M3Error("System is not set. Use 'cals_from_file'.") - if self.executor is None: - self.executor = SamplerV2(mode=self.system) if self.single_qubit_cals is None: self.single_qubit_cals = [None] * self.num_qubits if self.cal_shots is None: @@ -397,13 +392,7 @@ def _grab_additional_cals( ) num_circs = len(trans_qcs) - if isinstance(self.system, BackendV2): - max_circuits = getattr(self.system.configuration(), "max_circuits", 300) - # Needed for https://github.com/Qiskit/qiskit-terra/issues/9947 - if max_circuits is None: - max_circuits = 300 - else: - raise M3Error("Unknown backend type") + max_circuits = self.system_info['max_circuits'] # Determine the number of jobs required num_jobs = ceil(num_circs / max_circuits) logger.info( @@ -422,9 +411,10 @@ def _grab_additional_cals( jobs = [] if self.rep_delay: self.executor.options.execution.rep_delay = self.rep_delay - self.executor.options.environment.job_tags = ["M3 calibration"] for circs in circs_list: - _job = self.executor.run(circs, shots=shots) + _job = self.system.run(circs, shots=shots, + rep_delay=self.rep_delay, + job_tags=["M3 calibration"]) jobs.append(_job) # Execute job and cal building in new thread. @@ -763,7 +753,7 @@ def _job_thread(jobs, mit, qubits, num_cal_qubits, cal_strings): mit._job_error = error return else: - _counts = [rr.data.c.get_counts() for rr in res] + _counts = res.get_counts() # _counts can be a list or a dict (if only one circuit was executed within the job) if isinstance(_counts, list): counts.extend(_counts) diff --git a/requirements.txt b/requirements.txt index 6351b842..e2348360 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ qiskit>=1.0 qiskit_ibm_runtime>=0.22 psutil orjson>=3.0.0 +runningman \ No newline at end of file From 363e8156f345a011b630830676c6dcd1cbb772b3 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 22 Oct 2024 15:14:58 -0400 Subject: [PATCH 02/10] cleanup requirements --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e2348360..4aa2dd32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ numpy>=1.23 scipy>=1.11.1 cython>=3.0.10 qiskit>=1.0 -qiskit_ibm_runtime>=0.22 psutil orjson>=3.0.0 runningman \ No newline at end of file From 622e103761d64e9d3d415c0682011b5cbaadbe3d Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 22 Oct 2024 15:19:16 -0400 Subject: [PATCH 03/10] just check black --- .github/workflows/python-package-conda.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package-conda.yml b/.github/workflows/python-package-conda.yml index 2e471ff9..7132b93f 100644 --- a/.github/workflows/python-package-conda.yml +++ b/.github/workflows/python-package-conda.yml @@ -37,7 +37,7 @@ jobs: else python setup.py build_ext --inplace fi - black mthree + black --check mthree - name: Run tests with pytest run: | pip install pytest From 187152a38239691201e690c162723de5b9a7d845 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 22 Oct 2024 15:23:11 -0400 Subject: [PATCH 04/10] black --- mthree/mitigation.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/mthree/mitigation.py b/mthree/mitigation.py index 4a239b43..3897fd8f 100644 --- a/mthree/mitigation.py +++ b/mthree/mitigation.py @@ -60,7 +60,9 @@ def __init__(self, system=None, iter_threshold=4096): cal_timestamp (str): Time at which cals were taken single_qubit_cals (list): 1Q calibration matrices """ - if system.__class__.__name__ == 'IBMBackend' and not isinstance(system, rm.RunningManBackend): + if system.__class__.__name__ == "IBMBackend" and not isinstance( + system, rm.RunningManBackend + ): system = rm.RunningManBackend(system) self.system = system self.system_info = system_info(system) if system else {} @@ -167,7 +169,7 @@ def cals_from_system( if runtime_mode: self.system.set_mode(runtime_mode, overwrite=True) elif not self.system.get_mode(): - self.system.set_mode('batch') + self.system.set_mode("batch") if qubits is None: qubits = range(self.num_qubits) # Remove faulty qubits if any @@ -392,7 +394,7 @@ def _grab_additional_cals( ) num_circs = len(trans_qcs) - max_circuits = self.system_info['max_circuits'] + max_circuits = self.system_info["max_circuits"] # Determine the number of jobs required num_jobs = ceil(num_circs / max_circuits) logger.info( @@ -412,9 +414,12 @@ def _grab_additional_cals( if self.rep_delay: self.executor.options.execution.rep_delay = self.rep_delay for circs in circs_list: - _job = self.system.run(circs, shots=shots, - rep_delay=self.rep_delay, - job_tags=["M3 calibration"]) + _job = self.system.run( + circs, + shots=shots, + rep_delay=self.rep_delay, + job_tags=["M3 calibration"], + ) jobs.append(_job) # Execute job and cal building in new thread. From a816c897d2700a4f36e078d044fd76fb2d256a6a Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 22 Oct 2024 15:35:01 -0400 Subject: [PATCH 05/10] fix simulators --- mthree/_helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mthree/_helpers.py b/mthree/_helpers.py index 6699c711..1cb6c535 100644 --- a/mthree/_helpers.py +++ b/mthree/_helpers.py @@ -35,6 +35,8 @@ def system_info(backend): _max_shots = config.max_shots info_dict["max_shots"] = _max_shots if _max_shots else int(1e6) info_dict["simulator"] = config.simulator + if "fake" in backend.name: + info_dict["simulator"] = True # On IBM systems it is max_experiments, on other stuff it might be max_circuits max_circuits = getattr(config, "max_experiments", None) if max_circuits is None: From d1ab2b6d1d603a9b8b1b5ef94a649e18a4fe6161 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 22 Oct 2024 15:49:25 -0400 Subject: [PATCH 06/10] fix tests for async=True --- mthree/test/test_extra_cals.py | 2 ++ mthree/test/test_file_io.py | 1 + 2 files changed, 3 insertions(+) diff --git a/mthree/test/test_extra_cals.py b/mthree/test/test_extra_cals.py index 95cd897a..eebcfe4c 100644 --- a/mthree/test/test_extra_cals.py +++ b/mthree/test/test_extra_cals.py @@ -71,6 +71,7 @@ def test_save_cals(tmp_path): cal_file = tmp_path / "cal.json" mit = mthree.M3Mitigation(backend) mit.cals_from_system(cals_file=cal_file) + _ = mit.single_qubit_cals #Force waiting since async=True by default with open(cal_file, "r", encoding="utf-8") as fd: cals = np.array(orjson.loads(fd.read())["cals"], dtype=np.float32) assert np.array_equal(mit.single_qubit_cals, cals) @@ -82,6 +83,7 @@ def test_load_cals(tmp_path): backend = FakeAthens() mit = mthree.M3Mitigation(backend) mit.cals_from_system(cals_file=cal_file) + _ = mit.single_qubit_cals #Force waiting since async=True by default new_mit = mthree.M3Mitigation(backend) new_mit.cals_from_file(cal_file) assert np.array_equal(mit.single_qubit_cals, new_mit.single_qubit_cals) diff --git a/mthree/test/test_file_io.py b/mthree/test/test_file_io.py index ab757e59..49917d69 100644 --- a/mthree/test/test_file_io.py +++ b/mthree/test/test_file_io.py @@ -34,6 +34,7 @@ def test_load_cals_from_file(): raw_counts = backend.run(qc).result().get_counts() mit = mthree.M3Mitigation(backend) mit.cals_from_system(cals_file="cals.json") + _ = mit.single_qubit_cals mit2 = mthree.M3Mitigation() mit2.cals_from_file(cals_file="cals.json") From 2ebb5bd61278937dc30a0f32972c26262dcd5177 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 22 Oct 2024 15:56:05 -0400 Subject: [PATCH 07/10] cleanup backend helper --- mthree/_helpers.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mthree/_helpers.py b/mthree/_helpers.py index 1cb6c535..01e3ae5f 100644 --- a/mthree/_helpers.py +++ b/mthree/_helpers.py @@ -13,9 +13,6 @@ """ Helper functions """ -from qiskit.providers import BackendV2 -from qiskit_ibm_runtime import IBMBackend -from mthree.exceptions import M3Error def system_info(backend): @@ -37,10 +34,7 @@ def system_info(backend): info_dict["simulator"] = config.simulator if "fake" in backend.name: info_dict["simulator"] = True - # On IBM systems it is max_experiments, on other stuff it might be max_circuits - max_circuits = getattr(config, "max_experiments", None) - if max_circuits is None: - max_circuits = getattr(config, "max_circuits", 1) + max_circuits = getattr(config, "max_experiments", 1) if config.simulator: max_circuits = 1024 info_dict["max_circuits"] = max_circuits From 1ed2372f33f1dd53fe22d3dadce3228e68478cef Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 22 Oct 2024 15:56:18 -0400 Subject: [PATCH 08/10] black --- mthree/test/test_extra_cals.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mthree/test/test_extra_cals.py b/mthree/test/test_extra_cals.py index eebcfe4c..d8da5577 100644 --- a/mthree/test/test_extra_cals.py +++ b/mthree/test/test_extra_cals.py @@ -71,7 +71,7 @@ def test_save_cals(tmp_path): cal_file = tmp_path / "cal.json" mit = mthree.M3Mitigation(backend) mit.cals_from_system(cals_file=cal_file) - _ = mit.single_qubit_cals #Force waiting since async=True by default + _ = mit.single_qubit_cals # Force waiting since async=True by default with open(cal_file, "r", encoding="utf-8") as fd: cals = np.array(orjson.loads(fd.read())["cals"], dtype=np.float32) assert np.array_equal(mit.single_qubit_cals, cals) @@ -83,7 +83,7 @@ def test_load_cals(tmp_path): backend = FakeAthens() mit = mthree.M3Mitigation(backend) mit.cals_from_system(cals_file=cal_file) - _ = mit.single_qubit_cals #Force waiting since async=True by default + _ = mit.single_qubit_cals # Force waiting since async=True by default new_mit = mthree.M3Mitigation(backend) new_mit.cals_from_file(cal_file) assert np.array_equal(mit.single_qubit_cals, new_mit.single_qubit_cals) From 6b0440587ab3f88ed83439cc8bc445a688179150 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 22 Oct 2024 16:03:10 -0400 Subject: [PATCH 09/10] fix --- mthree/mitigation.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mthree/mitigation.py b/mthree/mitigation.py index 3897fd8f..afec55ee 100644 --- a/mthree/mitigation.py +++ b/mthree/mitigation.py @@ -411,8 +411,6 @@ def _grab_additional_cals( ] + [trans_qcs[(num_jobs - 1) * circ_slice :]] # Do job submission here jobs = [] - if self.rep_delay: - self.executor.options.execution.rep_delay = self.rep_delay for circs in circs_list: _job = self.system.run( circs, From 88d1502919db684e24b8b71327ea3f419ee0044e Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Wed, 23 Oct 2024 10:25:39 -0400 Subject: [PATCH 10/10] updates --- docs/runtime.rst | 6 ++++-- mthree/_helpers.py | 5 ++++- mthree/mitigation.py | 6 ++---- requirements.txt | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/runtime.rst b/docs/runtime.rst index 01dd45a3..9c74fbc0 100644 --- a/docs/runtime.rst +++ b/docs/runtime.rst @@ -5,7 +5,7 @@ Using Runtime execution modes ############################# The Qiskit Runtime has two execution modes, `Batch` and `Session`, that allow for grouping -multiple jobs. You can include M3 calibration jobs in these modes using the `mode` argument +multiple jobs. You can include M3 calibration jobs in these modes using the `runtime_mode` argument in `mthree.M3Mitigation.cals_from_system`. For example: .. jupyter-execute:: @@ -18,5 +18,7 @@ in `mthree.M3Mitigation.cals_from_system`. For example: batch = Batch(backend=backend) mit = mthree.M3Mitigation(backend) - mit.cals_from_system(mode=batch); # This is where the Batch or Session goes + mit.cals_from_system(runtime_mode=batch); # This is where the Batch or Session goes + +Note that if no `runtime_mode` is set, and the passed system is an IBM backend, then jobs are submitted in `Batch` mode automatically. This mode is NOT closed by default, allowing users to include additional jobs. This mode can be accessed via `mode = mit.system.get_mode()` diff --git a/mthree/_helpers.py b/mthree/_helpers.py index 01e3ae5f..650a2e94 100644 --- a/mthree/_helpers.py +++ b/mthree/_helpers.py @@ -34,8 +34,11 @@ def system_info(backend): info_dict["simulator"] = config.simulator if "fake" in backend.name: info_dict["simulator"] = True + # max_circuits can be set a couple of ways max_circuits = getattr(config, "max_experiments", 1) - if config.simulator: + if max_circuits == 1: + max_circuits = getattr(config, "max_circuits", 1) + if max_circuits == 1 and config.simulator: max_circuits = 1024 info_dict["max_circuits"] = max_circuits # Look for faulty qubits. Renaming to 'inoperable' here diff --git a/mthree/mitigation.py b/mthree/mitigation.py index afec55ee..15a2a034 100644 --- a/mthree/mitigation.py +++ b/mthree/mitigation.py @@ -23,7 +23,7 @@ import numpy as np import orjson import runningman as rm - +from runningman.utils import is_ibm_backend from mthree.circuits import ( _tensor_meas_states, @@ -60,9 +60,7 @@ def __init__(self, system=None, iter_threshold=4096): cal_timestamp (str): Time at which cals were taken single_qubit_cals (list): 1Q calibration matrices """ - if system.__class__.__name__ == "IBMBackend" and not isinstance( - system, rm.RunningManBackend - ): + if is_ibm_backend(system): system = rm.RunningManBackend(system) self.system = system self.system_info = system_info(system) if system else {} diff --git a/requirements.txt b/requirements.txt index 4aa2dd32..5d3cd8c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ cython>=3.0.10 qiskit>=1.0 psutil orjson>=3.0.0 -runningman \ No newline at end of file +runningman>=2.1 \ No newline at end of file