From e3a968f0a73d2a19daf1f41e8f2575832aea3aed Mon Sep 17 00:00:00 2001 From: Hiroshi Horii Date: Wed, 17 May 2023 19:01:11 +0900 Subject: [PATCH] Add implicit cast of argument types (#1770) * 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. * does not warning in cast of numpy classes if they are compatible with expected type * fix lint issue * simplify class checking --- qiskit_aer/backends/aer_compiler.py | 93 +++++++++++++++++++ ...t_cast_for_arguments-a3c671db2fff6f17.yaml | 9 ++ .../backends/aer_simulator/test_circuit.py | 43 +++++++++ 3 files changed, 145 insertions(+) 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 8875169406..b5281288f3 100644 --- a/qiskit_aer/backends/aer_compiler.py +++ b/qiskit_aer/backends/aer_compiler.py @@ -17,6 +17,9 @@ import itertools 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 @@ -34,6 +37,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 @@ -428,6 +432,93 @@ def compile_circuit(circuits, basis_gates=None, optypes=None): return AerCompiler().compile(circuits, basis_gates, optypes) +BACKEND_RUN_ARG_TYPES = { + "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), +} + + +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 + + expected_type = BACKEND_RUN_ARG_TYPES[k][0] + + if expected_type in (int, float, bool, str): + try: + ret = expected_type(v) + 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." + "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}, " + f"type={v.__class__.__name__}, expected={BACKEND_RUN_ARG_TYPES[k][0].__name__}" + ) + + def generate_aer_config( circuits: List[QuantumCircuit], backend_options: Options, **run_options ) -> AerConfig: @@ -449,9 +540,11 @@ def generate_aer_config( 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 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. 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)