Skip to content

Commit

Permalink
Add implicit cast of argument types (#1770)
Browse files Browse the repository at this point in the history
* 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 #1754.

* does not warning in cast of numpy classes if they are compatible with expected type
* fix lint issue
* simplify class checking
  • Loading branch information
hhorii authored May 17, 2023
1 parent 7ac677e commit e3a968f
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 0 deletions.
93 changes: 93 additions & 0 deletions qiskit_aer/backends/aer_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
43 changes: 43 additions & 0 deletions test/terra/backends/aer_simulator/test_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

0 comments on commit e3a968f

Please sign in to comment.