From dc7ce900e323fd1240cc56c0e5e8ce0c7f202ebb Mon Sep 17 00:00:00 2001 From: Hiroshi Horii Date: Tue, 4 Apr 2023 21:55:27 +0900 Subject: [PATCH 1/4] Add implicit cast of argument types Since 0.12.0, AerConfig is used to configure simulation, which is directly bound to a AER::Config object through pybind. This change requires application to specify strictly correct types of configuration options. This commit allows implicit casting to arguments if application specifies them with wrong types. This commit resolves https://github.com/Qiskit/qiskit-aer/issues/1754. --- qiskit_aer/backends/aer_compiler.py | 114 ++++++++++++++++-- ...t_cast_for_arguments-a3c671db2fff6f17.yaml | 9 ++ 2 files changed, 113 insertions(+), 10 deletions(-) create mode 100644 releasenotes/notes/implicit_cast_for_arguments-a3c671db2fff6f17.yaml diff --git a/qiskit_aer/backends/aer_compiler.py b/qiskit_aer/backends/aer_compiler.py index fdb4fd88bd..6489b99f3a 100644 --- a/qiskit_aer/backends/aer_compiler.py +++ b/qiskit_aer/backends/aer_compiler.py @@ -16,6 +16,7 @@ import itertools from copy import copy from typing import List +from warnings import warn from qiskit.circuit import QuantumCircuit, Clbit, ParameterExpression from qiskit.extensions import Initialize @@ -336,6 +337,83 @@ def compile_circuit(circuits, basis_gates=None, optypes=None): return AerCompiler().compile(circuits, basis_gates, optypes) +BACKEND_RUN_ARG_TYPES = { + "shots": int, + "method": str, + "device": str, + "precision": str, + "max_job_size": int, + "max_shot_size": int, + "enable_truncation": bool, + # "executor": Executor, + "zero_threshold": float, + "validation_threshold": int, + "max_parallel_threads": int, + "max_parallel_experiments": int, + "max_parallel_shots": int, + "max_memory_mb": int, + "fusion_enable": bool, + "fusion_verbose": bool, + "fusion_max_qubit": int, + "fusion_threshold": int, + "accept_distributed_results": bool, + "memory": bool, + # "noise_model": NoiseModel, + "seed_simulator": int, + "cuStateVec_enable": int, + "blocking_qubits": int, + "blocking_enable": bool, + "chunk_swap_buffer_qubits": int, + "batched_shots_gpu": bool, + "batched_shots_gpu_max_qubits": int, + "num_threads_per_device": int, + "statevector_parallel_threshold": int, + "statevector_sample_measure_opt": int, + "stabilizer_max_snapshot_probabilities": int, + "extended_stabilizer_sampling_method": str, + "extended_stabilizer_metropolis_mixing_time": int, + "extended_stabilizer_approximation_error": float, + "extended_stabilizer_norm_estimation_samples": int, + "extended_stabilizer_norm_estimation_repetitions": int, + "extended_stabilizer_parallel_threshold": int, + "extended_stabilizer_probabilities_snapshot_samples": int, + "matrix_product_state_truncation_threshold": float, + "matrix_product_state_max_bond_dimension": int, + "mps_sample_measure_algorithm": str, + "mps_log_data": bool, + "mps_swap_direction": str, + "chop_threshold": float, + "mps_parallel_threshold": int, + "mps_omp_threads": int, + "tensor_network_num_sampling_qubits": int, + "use_cuTensorNet_autotuning": bool, +} + + +def _validate_option(k, v): + """validate backend.run arguments""" + if v is None: + return v + if k not in BACKEND_RUN_ARG_TYPES: + raise AerError(f"invalid argument: name={k}") + if isinstance(v, BACKEND_RUN_ARG_TYPES[k]): + return v + try: + ret = BACKEND_RUN_ARG_TYPES[k](v) + warn( + f'A type of an option "{k}" is {BACKEND_RUN_ARG_TYPES[k].__name__} ' + "but {v.__class__.__name__} was specified." + "Implicit cast for an argument has been deprecated as of qiskit-aer 0.12.1.", + DeprecationWarning, + stacklevel=5, + ) + return ret + except Exception: # pylint: disable=broad-except + raise AerError( + f"invalid option type: name={k}, type={v.__class__}, expected={BACKEND_RUN_ARG_TYPES[k](v)}" + ) + + def generate_aer_config( circuits: List[QuantumCircuit], backend_options: Options, **run_options ) -> AerConfig: @@ -352,16 +430,32 @@ def generate_aer_config( num_qubits = max(circuit.num_qubits for circuit in circuits) memory_slots = max(circuit.num_clbits for circuit in circuits) - config = AerConfig() - config.memory_slots = memory_slots - config.n_qubits = num_qubits - for key, value in backend_options.__dict__.items(): - if hasattr(config, key) and value is not None: - setattr(config, key, value) - for key, value in run_options.items(): - if hasattr(config, key) and value is not None: - setattr(config, key, value) - return config + try: + config = AerConfig() + config.memory_slots = memory_slots + config.n_qubits = num_qubits + for key, value in backend_options.__dict__.items(): + if hasattr(config, key) and value is not None: + setattr(config, key, value) + for key, value in run_options.items(): + if hasattr(config, key) and value is not None: + setattr(config, key, value) + return config + except Exception: # pylint: disable=broad-except + # generate AER::Config was failed. + # try again with type validation of arguments + config = AerConfig() + config.memory_slots = memory_slots + config.n_qubits = num_qubits + for key, value in backend_options.__dict__.items(): + if hasattr(config, key) and value is not None: + value = _validate_option(key, value) + setattr(config, key, value) + for key, value in run_options.items(): + if hasattr(config, key) and value is not None: + value = _validate_option(key, value) + setattr(config, key, value) + return config def assemble_circuit(circuit: QuantumCircuit): diff --git a/releasenotes/notes/implicit_cast_for_arguments-a3c671db2fff6f17.yaml b/releasenotes/notes/implicit_cast_for_arguments-a3c671db2fff6f17.yaml new file mode 100644 index 0000000000..69618c0b99 --- /dev/null +++ b/releasenotes/notes/implicit_cast_for_arguments-a3c671db2fff6f17.yaml @@ -0,0 +1,9 @@ +--- +deprecations: + - | + Options of meth:`~.AerSimulator.run` need to use correct types. +fixes: + - | + Since 0.12.0, :class:`AerConfig` is used for simulation configuration while + performing strict type checking for arguments of meth:`~.AerSimulator.run`. + This commit adds casting if argument types are not expected. From 63356fc4c68068c9ff0ea2f4b12fdd6eaba6e3e9 Mon Sep 17 00:00:00 2001 From: Hiroshi Horii Date: Wed, 19 Apr 2023 19:07:31 +0900 Subject: [PATCH 2/4] does not warning in cast of numpy classes if they are compatible with expected type --- qiskit_aer/backends/aer_compiler.py | 176 +++++++++--------- .../backends/aer_simulator/test_circuit.py | 43 +++++ 2 files changed, 130 insertions(+), 89 deletions(-) diff --git a/qiskit_aer/backends/aer_compiler.py b/qiskit_aer/backends/aer_compiler.py index fd1993ef0e..bd85c59e07 100644 --- a/qiskit_aer/backends/aer_compiler.py +++ b/qiskit_aer/backends/aer_compiler.py @@ -14,9 +14,11 @@ """ import itertools +import numpy as np from copy import copy from typing import List from warnings import warn +from concurrent.futures import Executor from qiskit.circuit import QuantumCircuit, Clbit, ParameterExpression from qiskit.extensions import Initialize @@ -26,6 +28,7 @@ from qiskit.compiler import transpile from qiskit.qobj import QobjExperimentHeader from qiskit_aer.aererror import AerError +from qiskit_aer.noise import NoiseModel # pylint: disable=import-error, no-name-in-module from qiskit_aer.backends.controller_wrappers import AerCircuit, AerConfig @@ -338,55 +341,57 @@ def compile_circuit(circuits, basis_gates=None, optypes=None): BACKEND_RUN_ARG_TYPES = { - "shots": int, - "method": str, - "device": str, - "precision": str, - "max_job_size": int, - "max_shot_size": int, - "enable_truncation": bool, - # "executor": Executor, - "zero_threshold": float, - "validation_threshold": int, - "max_parallel_threads": int, - "max_parallel_experiments": int, - "max_parallel_shots": int, - "max_memory_mb": int, - "fusion_enable": bool, - "fusion_verbose": bool, - "fusion_max_qubit": int, - "fusion_threshold": int, - "accept_distributed_results": bool, - "memory": bool, - # "noise_model": NoiseModel, - "seed_simulator": int, - "cuStateVec_enable": int, - "blocking_qubits": int, - "blocking_enable": bool, - "chunk_swap_buffer_qubits": int, - "batched_shots_gpu": bool, - "batched_shots_gpu_max_qubits": int, - "num_threads_per_device": int, - "statevector_parallel_threshold": int, - "statevector_sample_measure_opt": int, - "stabilizer_max_snapshot_probabilities": int, - "extended_stabilizer_sampling_method": str, - "extended_stabilizer_metropolis_mixing_time": int, - "extended_stabilizer_approximation_error": float, - "extended_stabilizer_norm_estimation_samples": int, - "extended_stabilizer_norm_estimation_repetitions": int, - "extended_stabilizer_parallel_threshold": int, - "extended_stabilizer_probabilities_snapshot_samples": int, - "matrix_product_state_truncation_threshold": float, - "matrix_product_state_max_bond_dimension": int, - "mps_sample_measure_algorithm": str, - "mps_log_data": bool, - "mps_swap_direction": str, - "chop_threshold": float, - "mps_parallel_threshold": int, - "mps_omp_threads": int, - "tensor_network_num_sampling_qubits": int, - "use_cuTensorNet_autotuning": bool, + "shots": (int, np.integer), + "method": (str), + "device": (str), + "precision": (str), + "max_job_size": (int, np.integer), + "max_shot_size": (int, np.integer), + "enable_truncation": (bool, np.bool_), + "executor": Executor, + "zero_threshold": (float, np.floating), + "validation_threshold": (int, np.integer), + "max_parallel_threads": (int, np.integer), + "max_parallel_experiments": (int, np.integer), + "max_parallel_shots": (int, np.integer), + "max_memory_mb": (int, np.integer), + "fusion_enable": (bool, np.bool_), + "fusion_verbose": (bool, np.bool_), + "fusion_max_qubit": (int, np.integer), + "fusion_threshold": (int, np.integer), + "accept_distributed_results": (bool, np.bool_), + "memory": (bool, np.bool_), + "noise_model": (NoiseModel), + "seed_simulator": (int, np.integer), + "cuStateVec_enable": (int, np.integer), + "blocking_qubits": (int, np.integer), + "blocking_enable": (bool, np.bool_), + "chunk_swap_buffer_qubits": (int, np.integer), + "batched_shots_gpu": (bool, np.bool_), + "batched_shots_gpu_max_qubits": (int, np.integer), + "num_threads_per_device": (int, np.integer), + "statevector_parallel_threshold": (int, np.integer), + "statevector_sample_measure_opt": (int, np.integer), + "stabilizer_max_snapshot_probabilities": (int, np.integer), + "extended_stabilizer_sampling_method": (str), + "extended_stabilizer_metropolis_mixing_time": (int, np.integer), + "extended_stabilizer_approximation_error": (float, np.floating), + "extended_stabilizer_norm_estimation_samples": (int, np.integer), + "extended_stabilizer_norm_estimation_repetitions": (int, np.integer), + "extended_stabilizer_parallel_threshold": (int, np.integer), + "extended_stabilizer_probabilities_snapshot_samples": (int, np.integer), + "matrix_product_state_truncation_threshold": (float, np.floating), + "matrix_product_state_max_bond_dimension": (int, np.integer), + "mps_sample_measure_algorithm": (str), + "mps_log_data": (bool, np.bool_), + "mps_swap_direction": (str), + "chop_threshold": (float, np.floating), + "mps_parallel_threshold": (int, np.integer), + "mps_omp_threads": (int, np.integer), + "tensor_network_num_sampling_qubits": (int, np.integer), + "use_cuTensorNet_autotuning": (bool, np.bool_), + "parameterizations": (list), + "fusion_parallelization_threshold": (int, np.integer), } @@ -398,20 +403,27 @@ def _validate_option(k, v): raise AerError(f"invalid argument: name={k}") if isinstance(v, BACKEND_RUN_ARG_TYPES[k]): return v - try: - ret = BACKEND_RUN_ARG_TYPES[k](v) - warn( - f'A type of an option "{k}" is {BACKEND_RUN_ARG_TYPES[k].__name__} ' - "but {v.__class__.__name__} was specified." - "Implicit cast for an argument has been deprecated as of qiskit-aer 0.12.1.", - DeprecationWarning, - stacklevel=5, - ) - return ret - except Exception: # pylint: disable=broad-except - raise AerError( - f"invalid option type: name={k}, type={v.__class__}, expected={BACKEND_RUN_ARG_TYPES[k](v)}" - ) + + argType = BACKEND_RUN_ARG_TYPES[k][0] + + if argType in (int, float, bool, str): + try: + ret = argType(v) + if v.__class__ not in BACKEND_RUN_ARG_TYPES[k]: + warn( + f'A type of an option "{k}" should be {argType.__name__} ' + "but {v.__class__.__name__} was specified." + "Implicit cast for an argument has been deprecated as of qiskit-aer 0.12.1.", + DeprecationWarning, + stacklevel=5, + ) + return ret + except Exception: # pylint: disable=broad-except + pass + + raise TypeError( + f"invalid option type: name={k}, type={v.__class__.__name__}, expected={BACKEND_RUN_ARG_TYPES[k][0].__name__}" + ) def generate_aer_config( @@ -430,32 +442,18 @@ def generate_aer_config( num_qubits = max(circuit.num_qubits for circuit in circuits) memory_slots = max(circuit.num_clbits for circuit in circuits) - try: - config = AerConfig() - config.memory_slots = memory_slots - config.n_qubits = num_qubits - for key, value in backend_options.__dict__.items(): - if hasattr(config, key) and value is not None: - setattr(config, key, value) - for key, value in run_options.items(): - if hasattr(config, key) and value is not None: - setattr(config, key, value) - return config - except Exception: # pylint: disable=broad-except - # generate AER::Config was failed. - # try again with type validation of arguments - config = AerConfig() - config.memory_slots = memory_slots - config.n_qubits = num_qubits - for key, value in backend_options.__dict__.items(): - if hasattr(config, key) and value is not None: - value = _validate_option(key, value) - setattr(config, key, value) - for key, value in run_options.items(): - if hasattr(config, key) and value is not None: - value = _validate_option(key, value) - setattr(config, key, value) - return config + config = AerConfig() + config.memory_slots = memory_slots + config.n_qubits = num_qubits + for key, value in backend_options.__dict__.items(): + if hasattr(config, key) and value is not None: + value = _validate_option(key, value) + setattr(config, key, value) + for key, value in run_options.items(): + if hasattr(config, key) and value is not None: + value = _validate_option(key, value) + setattr(config, key, value) + return config def assemble_circuit(circuit: QuantumCircuit): diff --git a/test/terra/backends/aer_simulator/test_circuit.py b/test/terra/backends/aer_simulator/test_circuit.py index f387e27a16..91a8f4c97a 100644 --- a/test/terra/backends/aer_simulator/test_circuit.py +++ b/test/terra/backends/aer_simulator/test_circuit.py @@ -14,6 +14,7 @@ """ from math import sqrt from ddt import ddt +import numpy as np from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit.circuit import CircuitInstruction from test.terra.reference import ref_algorithms @@ -171,3 +172,45 @@ def test_partial_result_a_single_invalid_circuit(self): self.assertEqual(result.status, "PARTIAL COMPLETED") self.assertTrue(hasattr(result.results[1].data, "counts")) self.assertFalse(hasattr(result.results[0].data, "counts")) + + def test_numpy_integer_shots(self): + """Test implicit cast of shot option from np.int_ to int.""" + + backend = self.backend() + + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + qc.measure_all() + shots = 333 + + for np_type in { + np.int_, + np.uint, + np.short, + np.ushort, + np.intc, + np.uintc, + np.longlong, + np.ulonglong, + }: + result = backend.run(qc, shots=np_type(shots), method="statevector").result() + self.assertSuccess(result) + self.assertEqual(sum([result.get_counts()[key] for key in result.get_counts()]), shots) + + def test_floating_shots(self): + """Test implicit cast of shot option from float to int.""" + + backend = self.backend() + + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + qc.measure_all() + + for shots in {1e4, "300"}: + with self.assertWarns(DeprecationWarning): + result = backend.run(qc, shots=shots, method="statevector").result() + shots = int(shots) + self.assertSuccess(result) + self.assertEqual(sum([result.get_counts()[key] for key in result.get_counts()]), shots) From 528f8cbc7c52b880e36a2f8b24ce95f85528f529 Mon Sep 17 00:00:00 2001 From: Hiroshi Horii Date: Wed, 19 Apr 2023 19:21:53 +0900 Subject: [PATCH 3/4] fix lint issue --- qiskit_aer/backends/aer_compiler.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/qiskit_aer/backends/aer_compiler.py b/qiskit_aer/backends/aer_compiler.py index bd85c59e07..688754ae12 100644 --- a/qiskit_aer/backends/aer_compiler.py +++ b/qiskit_aer/backends/aer_compiler.py @@ -14,11 +14,11 @@ """ import itertools -import numpy as np from copy import copy from typing import List from warnings import warn from concurrent.futures import Executor +import numpy as np from qiskit.circuit import QuantumCircuit, Clbit, ParameterExpression from qiskit.extensions import Initialize @@ -404,14 +404,14 @@ def _validate_option(k, v): if isinstance(v, BACKEND_RUN_ARG_TYPES[k]): return v - argType = BACKEND_RUN_ARG_TYPES[k][0] + expected_type = BACKEND_RUN_ARG_TYPES[k][0] - if argType in (int, float, bool, str): + if expected_type in (int, float, bool, str): try: - ret = argType(v) + ret = expected_type(v) if v.__class__ not in BACKEND_RUN_ARG_TYPES[k]: warn( - f'A type of an option "{k}" should be {argType.__name__} ' + f'A type of an option "{k}" should be {expected_type.__name__} ' "but {v.__class__.__name__} was specified." "Implicit cast for an argument has been deprecated as of qiskit-aer 0.12.1.", DeprecationWarning, @@ -422,7 +422,8 @@ def _validate_option(k, v): pass raise TypeError( - f"invalid option type: name={k}, type={v.__class__.__name__}, expected={BACKEND_RUN_ARG_TYPES[k][0].__name__}" + f"invalid option type: name={k}, " + f"type={v.__class__.__name__}, expected={BACKEND_RUN_ARG_TYPES[k][0].__name__}" ) From d429260d87e7d4987b23669ea2639fefab763b0e Mon Sep 17 00:00:00 2001 From: Hiroshi Horii Date: Tue, 16 May 2023 22:26:57 +0900 Subject: [PATCH 4/4] simplify class checking --- qiskit_aer/backends/aer_compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_aer/backends/aer_compiler.py b/qiskit_aer/backends/aer_compiler.py index 688754ae12..85c02eaccc 100644 --- a/qiskit_aer/backends/aer_compiler.py +++ b/qiskit_aer/backends/aer_compiler.py @@ -409,7 +409,7 @@ def _validate_option(k, v): if expected_type in (int, float, bool, str): try: ret = expected_type(v) - if v.__class__ not in BACKEND_RUN_ARG_TYPES[k]: + if not isinstance(v, BACKEND_RUN_ARG_TYPES[k]): warn( f'A type of an option "{k}" should be {expected_type.__name__} ' "but {v.__class__.__name__} was specified."