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)