diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 40387e359a..fd8cdbc1c9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,13 +12,13 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ["macOS-latest", "ubuntu-latest", "windows-2019"] + os: ["macos-13", "ubuntu-latest", "windows-2019"] steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python '3.10' + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: '3.10' - name: Install deps run: pip install "conan<2.0.0" - name: Install openblas @@ -73,11 +73,11 @@ jobs: matrix: os: ["ubuntu-latest"] steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python '3.10' + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: '3.10' - name: Install deps run: pip install "conan<2.0.0" - name: Install openblas and mpi @@ -121,13 +121,13 @@ jobs: needs: ["standalone"] strategy: matrix: - os: ["macOS-latest", "ubuntu-latest", "windows-2019"] + os: ["macos-13", "ubuntu-latest", "windows-2019"] steps: - - uses: actions/checkout@v2 - - name: Set up Python Python 3.8 - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python Python '3.10' + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: '3.10' - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v1.0.2 if: runner.os == 'Windows' @@ -144,18 +144,20 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ["macOS-latest"] + os: ["macos-latest"] steps: - - uses: actions/checkout@v2 - - name: Set up Python Python 3.8 - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python Python 3.10 + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: '3.10' + architecture: arm64 - name: Install deps run: python -m pip install -U cibuildwheel==2.16.2 - name: Build Wheels env: CIBW_ARCHS_MACOS: arm64 + CIBW_SKIP: "pp* cp38* cp39*" run: cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v2 with: @@ -176,7 +178,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.8' + python-version: '3.10' - name: Install cibuildwheel run: | python -m pip install cibuildwheel==2.16.2 @@ -208,7 +210,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.8' + python-version: '3.10' - name: Install cibuildwheel run: | python -m pip install cibuildwheel==2.16.2 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 941591e9cc..36d214ab24 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -8,7 +8,7 @@ jobs: name: Build qiskit-aer wheels strategy: matrix: - os: ["macOS-latest", "ubuntu-latest", "windows-2019"] + os: ["macos-13", "ubuntu-latest", "windows-2019"] runs-on: ${{ matrix.os }} environment: release steps: @@ -16,13 +16,14 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.8' + python-version: '3.10' - name: Install cibuildwheel run: | python -m pip install cibuildwheel==2.16.2 - name: Build wheels env: AER_CMAKE_OPENMP_BUILD: 1 + CIBW_SKIP: "pp* cp38-macosx_arm64 cp39-macosx_arm64" run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v3 with: @@ -43,7 +44,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.8' + python-version: '3.10' - uses: actions-rs/toolchain@v1 with: toolchain: stable @@ -73,19 +74,21 @@ jobs: strategy: fail-fast: false matrix: - os: ["macOS-latest"] + os: ["macos-latest"] environment: release steps: - - uses: actions/checkout@v3 - - name: Set up Python Python 3.8 - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - name: Set up Python Python '3.10' + uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: '3.10' + architecture: arm64 - name: Install deps run: python -m pip install -U cibuildwheel==2.16.2 - name: Build Wheels env: CIBW_ARCHS_MACOS: arm64 + CIBW_SKIP: "pp* cp38* cp39*" run: cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v3 with: @@ -103,7 +106,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.8' + python-version: '3.10' - name: Install Deps run: pip install -U scikit-build wheel - name: Build Artifacts @@ -140,7 +143,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.8' + python-version: '3.10' - name: Install cibuildwheel run: | python -m pip install cibuildwheel==2.16.2 @@ -183,7 +186,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.8' + python-version: '3.10' - name: Install cibuildwheel run: | python -m pip install cibuildwheel==2.16.2 @@ -218,7 +221,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.8' + python-version: '3.10' - uses: actions-rs/toolchain@v1 with: toolchain: stable @@ -254,7 +257,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.8' + python-version: '3.10' - uses: actions-rs/toolchain@v1 with: toolchain: stable diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 192e7bcd07..97f5661afd 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -13,7 +13,7 @@ jobs: timeout-minutes: 60 strategy: matrix: - python-version: [3.8] + python-version: ['3.8'] steps: - uses: actions/checkout@v2 with: @@ -50,7 +50,7 @@ jobs: needs: [docs] strategy: matrix: - python-version: [3.8] + python-version: ['3.8'] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2bd6e3020c..2728be31f5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8] + python-version: ['3.10'] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -147,7 +147,7 @@ jobs: timeout-minutes: 60 strategy: matrix: - python-version: [3.9] + python-version: ['3.10'] os: ["ubuntu-latest"] env: AER_THRUST_BACKEND: OMP @@ -192,7 +192,7 @@ jobs: stestr run --slowest shell: bash tests_macos: - runs-on: macOS-latest + runs-on: macos-13 name: macOS Python ${{ matrix.python-version }} needs: [sdist, lint] timeout-minutes: 60 diff --git a/README.md b/README.md index 591a56f17b..cfd4cd36f7 100755 --- a/README.md +++ b/README.md @@ -46,45 +46,101 @@ the [contributing guide](CONTRIBUTING.md#building-with-gpu-support) for instructions on doing this. ## Simulating your first Qiskit circuit with Aer -Now that you have Aer installed, you can start simulating quantum circuits with noise. Here is a basic example: +Now that you have Aer installed, you can start simulating quantum circuits using primitives and noise models. Here is a basic example: ``` $ python ``` ```python -import qiskit +from qiskit import transpile +from qiskit.circuit.library import RealAmplitudes +from qiskit.quantum_info import SparsePauliOp from qiskit_aer import AerSimulator -from qiskit_ibm_runtime import QiskitRuntimeService -# Generate 3-qubit GHZ state -circ = qiskit.QuantumCircuit(3) -circ.h(0) -circ.cx(0, 1) -circ.cx(1, 2) -circ.measure_all() - -# Construct an ideal simulator -aersim = AerSimulator() - -# Perform an ideal simulation -result_ideal = aersim.run(circ).result() -counts_ideal = result_ideal.get_counts(0) -print('Counts(ideal):', counts_ideal) -# Counts(ideal): {'000': 493, '111': 531} - -# Construct a simulator using a noise model -# from a real backend. -provider = QiskitRuntimeService() +sim = AerSimulator() +# -------------------------- +# Simulating using estimator +#--------------------------- +from qiskit_aer.primitives import EstimatorV2 + +psi1 = transpile(RealAmplitudes(num_qubits=2, reps=2), sim, optimization_level=0) +psi2 = transpile(RealAmplitudes(num_qubits=2, reps=3), sim, optimization_level=0) + +H1 = SparsePauliOp.from_list([("II", 1), ("IZ", 2), ("XI", 3)]) +H2 = SparsePauliOp.from_list([("IZ", 1)]) +H3 = SparsePauliOp.from_list([("ZI", 1), ("ZZ", 1)]) + +theta1 = [0, 1, 1, 2, 3, 5] +theta2 = [0, 1, 1, 2, 3, 5, 8, 13] +theta3 = [1, 2, 3, 4, 5, 6] + +estimator = EstimatorV2() + +# calculate [ [, +# ], +# [] ] +job = estimator.run( + [ + (psi1, [H1, H3], [theta1, theta3]), + (psi2, H2, theta2) + ], + precision=0.01 +) +result = job.result() +print(f"expectation values : psi1 = {result[0].data.evs}, psi2 = {result[1].data.evs}") + +# -------------------------- +# Simulating using sampler +# -------------------------- +from qiskit_aer.primitives import SamplerV2 +from qiskit import QuantumCircuit + +# create a Bell circuit +bell = QuantumCircuit(2) +bell.h(0) +bell.cx(0, 1) +bell.measure_all() + +# create two parameterized circuits +pqc = RealAmplitudes(num_qubits=2, reps=2) +pqc.measure_all() +pqc = transpile(pqc, sim, optimization_level=0) +pqc2 = RealAmplitudes(num_qubits=2, reps=3) +pqc2.measure_all() +pqc2 = transpile(pqc2, sim, optimization_level=0) + +theta1 = [0, 1, 1, 2, 3, 5] +theta2 = [0, 1, 2, 3, 4, 5, 6, 7] + +# initialization of the sampler +sampler = SamplerV2() + +# collect 128 shots from the Bell circuit +job = sampler.run([bell], shots=128) +job_result = job.result() +print(f"counts for Bell circuit : {job_result[0].data.meas.get_counts()}") + +# run a sampler job on the parameterized circuits +job2 = sampler.run([(pqc, theta1), (pqc2, theta2)]) +job_result = job2.result() +print(f"counts for parameterized circuit : {job_result[0].data.meas.get_counts()}") + +# -------------------------------------------------- +# Simulating with noise model from actual hardware +# -------------------------------------------------- +from qiskit_ibm_runtime import QiskitRuntimeService +provider = QiskitRuntimeService(channel='ibm_quantum', token="set your own token here") backend = provider.get_backend("ibm_kyoto") -aersim_backend = AerSimulator.from_backend(backend) -# Perform noisy simulation -result_noise = aersim_backend.run(circ).result() -counts_noise = result_noise.get_counts(0) +# create sampler from the actual backend +sampler.from_backend(backend) + +# run a sampler job on the parameterized circuits with noise model of the actual hardware +job3 = sampler.run([(pqc, theta1), (pqc2, theta2)]) +job_result = job3.result() +print(f"Parameterized for Bell circuit w/noise: {job_result[0].data.meas.get_counts()}") -print('Counts(noise):', counts_noise) -# Counts(noise): {'101': 16, '110': 48, '100': 7, '001': 31, '010': 7, '000': 464, '011': 15, '111': 436} ``` ## Contribution Guidelines diff --git a/docs/conf.py b/docs/conf.py index a8f10072b4..b0bf2c26d5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,8 +31,12 @@ Sphinx documentation builder """ -import os import datetime +import importlib +import inspect +import os +import re +from pathlib import Path # Set env flag so that we can doc functions that may otherwise not be loaded # see for example interactive visualizations in qiskit.visualization. os.environ['QISKIT_DOCS'] = 'TRUE' @@ -43,9 +47,9 @@ author = 'Qiskit Development Team' # The short X.Y version -version = '0.14.0' +version = '0.14.1' # The full version, including alpha/beta/rc tags -release = '0.14.0.1' +release = '0.14.1' templates_path = ['_templates'] @@ -64,7 +68,7 @@ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', + "sphinx.ext.linkcode", 'sphinx.ext.extlinks', 'jupyter_sphinx', 'reno.sphinxext', @@ -146,3 +150,82 @@ "matplotlib": ("https://matplotlib.org/stable/", None), "qiskit": ("https://docs.quantum.ibm.com/api/qiskit/", None), } + + +# ---------------------------------------------------------------------------------- +# Source code links +# ---------------------------------------------------------------------------------- + +def determine_github_branch() -> str: + """Determine the GitHub branch name to use for source code links. + + We need to decide whether to use `stable/` vs. `main` for dev builds. + Refer to https://docs.github.com/en/actions/learn-github-actions/variables + for how we determine this with GitHub Actions. + """ + # If CI env vars not set, default to `main`. This is relevant for local builds. + if "GITHUB_REF_NAME" not in os.environ: + return "main" + + # PR workflows set the branch they're merging into. + if base_ref := os.environ.get("GITHUB_BASE_REF"): + return base_ref + + ref_name = os.environ["GITHUB_REF_NAME"] + + # Check if the ref_name is a tag like `1.0.0` or `1.0.0rc1`. If so, we need + # to transform it to a Git branch like `stable/1.0`. + version_without_patch = re.match(r"(\d+\.\d+)", ref_name) + return ( + f"stable/{version_without_patch.group()}" + if version_without_patch + else ref_name + ) + + +REPO_ROOT = Path(__file__).resolve().parents[1] +GITHUB_BRANCH = determine_github_branch() + + +def linkcode_resolve(domain, info): + if domain != "py": + return None + + module_name = info["module"] + if "qiskit_aer" not in module_name: + return None + + try: + module = importlib.import_module(module_name) + except ModuleNotFoundError: + return None + + obj = module + for part in info["fullname"].split("."): + try: + obj = getattr(obj, part) + except AttributeError: + return None + + try: + full_file_name = inspect.getsourcefile(obj) + except TypeError: + return None + if full_file_name is None: + return None + try: + relative_file_name = Path(full_file_name).resolve().relative_to(REPO_ROOT) + file_name = re.sub(r"\.tox\/.+\/site-packages\/", "", str(relative_file_name)) + except ValueError: + return None + + try: + source, lineno = inspect.getsourcelines(obj) + except (OSError, TypeError): + linespec = "" + else: + ending_lineno = lineno + len(source) - 1 + linespec = f"#L{lineno}-L{ending_lineno}" + + repo_name = "qiskit" if "qiskit/" in str(file_name) else "qiskit-aer" + return f"https://github.com/Qiskit/{repo_name}/tree/{GITHUB_BRANCH}/{file_name}{linespec}" diff --git a/pyproject.toml b/pyproject.toml index 4d08ae9241..b3c0cf01e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta" [tool.cibuildwheel] manylinux-x86_64-image = "manylinux2014" manylinux-i686-image = "manylinux2014" -skip = "pp* cp36* cp37* cp38* *musllinux*" +skip = "pp* cp36* cp37* *musllinux* cp38-macosx_arm64" test-skip = "cp3*-win32 cp3*-manylinux_i686" test-command = "python {project}/tools/verify_wheels.py" # We need to use pre-built versions of Numpy and Scipy in the tests; they have a diff --git a/qiskit_aer/VERSION.txt b/qiskit_aer/VERSION.txt index e54d09d4ee..930e3000bd 100644 --- a/qiskit_aer/VERSION.txt +++ b/qiskit_aer/VERSION.txt @@ -1 +1 @@ -0.14.0.1 +0.14.1 diff --git a/qiskit_aer/backends/aer_compiler.py b/qiskit_aer/backends/aer_compiler.py index bbb2e18a25..12758b4573 100644 --- a/qiskit_aer/backends/aer_compiler.py +++ b/qiskit_aer/backends/aer_compiler.py @@ -36,7 +36,10 @@ SwitchCaseOp, CASE_DEFAULT, ) -from qiskit.compiler import transpile +from qiskit.transpiler import PassManager +from qiskit.transpiler.passes import Decompose + + from qiskit.qobj import QobjExperimentHeader from qiskit_aer.aererror import AerError from qiskit_aer.noise import NoiseModel @@ -67,13 +70,11 @@ class AerCompiler: def __init__(self): self._last_flow_id = -1 - def compile(self, circuits, basis_gates=None, optypes=None): + def compile(self, circuits, optypes=None): """compile a circuit that have control-flow instructions. Args: circuits (QuantumCircuit or list): The QuantumCircuits to be compiled - basis_gates (list): basis gates to decompose sub-circuits - (default: None). optypes (list): list of instruction type sets for each circuit (default: None). @@ -92,17 +93,14 @@ def compile(self, circuits, basis_gates=None, optypes=None): # Make a shallow copy incase we modify it compiled_optypes = list(optypes) if isinstance(circuits, list): - basis_gates = basis_gates + ["mark", "jump"] compiled_circuits = [] for idx, circuit in enumerate(circuits): # Resolve initialize circuit = self._inline_initialize(circuit, compiled_optypes[idx]) if self._is_dynamic(circuit, compiled_optypes[idx]): - compiled_circ = transpile( - self._inline_circuit(circuit, None, None), - basis_gates=basis_gates, - optimization_level=0, - ) + pm = PassManager([Decompose(["mark", "jump"])]) + compiled_circ = pm.run(self._inline_circuit(circuit, None, None)) + compiled_circuits.append(compiled_circ) # Recompute optype for compiled circuit compiled_optypes[idx] = circuit_optypes(compiled_circ) @@ -214,7 +212,6 @@ def _inline_circuit(self, circ, continue_label, break_label, bit_map=None): ) else: ret._append(instruction) - return ret def _convert_jump_conditional(self, cond_tuple, bit_map): @@ -272,7 +269,9 @@ def _inline_for_loop_op(self, instruction, parent, bit_map): inlined_body = self._inline_circuit(body, continue_label, break_label, inner_bit_map) if loop_parameter is not None: inlined_body = inlined_body.assign_parameters({loop_parameter: index}) - parent.append(inlined_body, qargs, cargs) + # parent.append(inlined_body, qargs, cargs) + for inst in inlined_body: + parent.append(inst, qargs, cargs) parent.append(AerMark(continue_label, len(qargs), len(cargs)), qargs, cargs) if inlined_body is not None: @@ -323,7 +322,8 @@ def _inline_while_loop_op(self, instruction, parent, bit_map): ) parent.append(AerJump(break_label, len(qargs), len(mark_cargs)), qargs, mark_cargs) parent.append(AerMark(loop_start_label, len(qargs), len(mark_cargs)), qargs, mark_cargs) - parent.append(inlined_body, qargs, cargs) + for inst in inlined_body: + parent.append(inst, qargs, cargs) parent.append(AerJump(continue_label, len(qargs), len(mark_cargs)), qargs, mark_cargs) parent.append(AerMark(break_label, len(qargs), len(mark_cargs)), qargs, mark_cargs) @@ -371,9 +371,9 @@ def _inline_if_else_op(self, instruction, continue_label, break_label, parent, b ) parent.append(AerJump(if_else_label, len(qargs), len(mark_cargs)), qargs, mark_cargs) parent.append(AerMark(if_true_label, len(qargs), len(mark_cargs)), qargs, mark_cargs) - parent.append( - self._inline_circuit(true_body, continue_label, break_label, true_bit_map), qargs, cargs - ) + child = self._inline_circuit(true_body, continue_label, break_label, true_bit_map) + for inst in child.data: + parent.append(inst, qargs, cargs) if false_body: false_bit_map = { @@ -385,11 +385,9 @@ def _inline_if_else_op(self, instruction, continue_label, break_label, parent, b } parent.append(AerJump(if_end_label, len(qargs), len(mark_cargs)), qargs, mark_cargs) parent.append(AerMark(if_else_label, len(qargs), len(mark_cargs)), qargs, mark_cargs) - parent.append( - self._inline_circuit(false_body, continue_label, break_label, false_bit_map), - qargs, - cargs, - ) + child = self._inline_circuit(false_body, continue_label, break_label, false_bit_map) + for inst in child.data: + parent.append(inst, qargs, cargs) parent.append(AerMark(if_end_label, len(qargs), len(mark_cargs)), qargs, mark_cargs) @@ -468,23 +466,21 @@ def _inline_switch_case_op(self, instruction, continue_label, break_label, paren for case_data in case_data_list: parent.append(AerMark(case_data.label, len(qargs), len(mark_cargs)), qargs, mark_cargs) - parent.append( - self._inline_circuit( - case_data.body, continue_label, break_label, case_data.bit_map - ), - qargs, - cargs, + child = self._inline_circuit( + case_data.body, continue_label, break_label, case_data.bit_map ) + for inst in child.data: + parent.append(inst, qargs, cargs) parent.append(AerJump(switch_end_label, len(qargs), len(mark_cargs)), qargs, mark_cargs) parent.append(AerMark(switch_end_label, len(qargs), len(mark_cargs)), qargs, mark_cargs) -def compile_circuit(circuits, basis_gates=None, optypes=None): +def compile_circuit(circuits, optypes=None): """ compile a circuit that have control-flow instructions """ - return AerCompiler().compile(circuits, basis_gates, optypes) + return AerCompiler().compile(circuits, optypes) BACKEND_RUN_ARG_TYPES = { @@ -609,7 +605,7 @@ def generate_aer_config( return config -def assemble_circuit(circuit: QuantumCircuit): +def assemble_circuit(circuit: QuantumCircuit, basis_gates=None): """assemble circuit object mapped to AER::Circuit""" num_qubits = circuit.num_qubits @@ -691,6 +687,7 @@ def assemble_circuit(circuit: QuantumCircuit): is_conditional, conditional_reg, conditional_expr, + basis_gates, ) index_map.append(num_of_aer_ops - 1) @@ -796,6 +793,7 @@ def _assemble_op( is_conditional, conditional_reg, conditional_expr, + basis_gates, ): operation = inst.operation qubits = [qubit_indices[qubit] for qubit in inst.qubits] @@ -816,7 +814,7 @@ def _assemble_op( num_of_aer_ops = 1 # fmt: off - if name in { + if basis_gates is None and name in { "ccx", "ccz", "cp", "cswap", "csx", "cx", "cy", "cz", "delay", "ecr", "h", "id", "mcp", "mcphase", "mcr", "mcrx", "mcry", "mcrz", "mcswap", "mcsx", "mcu", "mcu1", "mcu2", "mcu3", "mcx", "mcx_gray", "mcy", "mcz", "p", "r", @@ -916,6 +914,9 @@ def _assemble_op( aer_circ.mark(qubits, params) elif name == "qerror_loc": aer_circ.set_qerror_loc(qubits, label if label else name, conditional_reg, aer_cond_expr) + elif basis_gates is not None and name in basis_gates: + aer_circ.gate(name, qubits, params, [], conditional_reg, aer_cond_expr, + label if label else name) elif name in ("for_loop", "while_loop", "if_else"): raise AerError( "control-flow instructions must be converted " f"to jump and mark instructions: {name}" @@ -927,11 +928,12 @@ def _assemble_op( return num_of_aer_ops -def assemble_circuits(circuits: List[QuantumCircuit]) -> List[AerCircuit]: +def assemble_circuits(circuits: List[QuantumCircuit], basis_gates: list = None) -> List[AerCircuit]: """converts a list of Qiskit circuits into circuits mapped AER::Circuit Args: circuits: circuit(s) to be converted + basis_gates (list): supported gates to be converted Returns: a list of circuits to be run on the Aer backends and @@ -951,5 +953,11 @@ def assemble_circuits(circuits: List[QuantumCircuit]) -> List[AerCircuit]: # Generate AerCircuit from the input circuit aer_qc_list, idx_maps = assemble_circuits(circuits=[qc]) """ - aer_circuits, idx_maps = zip(*[assemble_circuit(circuit) for circuit in circuits]) + if basis_gates is not None: + basis_gates_set = set(basis_gates) + aer_circuits, idx_maps = zip( + *[assemble_circuit(circuit, basis_gates_set) for circuit in circuits] + ) + else: + aer_circuits, idx_maps = zip(*[assemble_circuit(circuit) for circuit in circuits]) return list(aer_circuits), list(idx_maps) diff --git a/qiskit_aer/backends/aer_simulator.py b/qiskit_aer/backends/aer_simulator.py index 083fc5f9d2..958a10ba89 100644 --- a/qiskit_aer/backends/aer_simulator.py +++ b/qiskit_aer/backends/aer_simulator.py @@ -15,6 +15,7 @@ import copy import logging +from qiskit.providers import convert_to_target from qiskit.providers.options import Options from qiskit.providers.models import QasmBackendConfiguration from qiskit.providers.backend import BackendV2, BackendV1 @@ -33,6 +34,7 @@ # pylint: disable=import-error, no-name-in-module, abstract-method from .controller_wrappers import aer_controller_execute +from .name_mapping import NAME_MAPPING logger = logging.getLogger(__name__) @@ -856,7 +858,7 @@ def from_backend(cls, backend, **options): name = configuration.backend_name configuration.backend_name = f"aer_simulator_from({name})" - target = None + target = convert_to_target(configuration, properties, None, NAME_MAPPING) else: raise TypeError( "The backend argument requires a BackendV2 or BackendV1 object, " diff --git a/qiskit_aer/backends/aerbackend.py b/qiskit_aer/backends/aerbackend.py index bc95a45de6..ed8059a57b 100644 --- a/qiskit_aer/backends/aerbackend.py +++ b/qiskit_aer/backends/aerbackend.py @@ -350,6 +350,11 @@ def target(self): tgt._coupling_graph = self._coupling_map.graph.copy() return tgt + def set_max_qubits(self, max_qubits): + """Set maximun number of qubits to be used for this backend.""" + if self._target is not None: + self._configuration.n_qubits = max_qubits + def clear_options(self): """Reset the simulator options to default values.""" self._options = self._default_options() @@ -445,7 +450,10 @@ def _execute_circuits_job( # Compile circuits circuits, noise_model = self._compile(circuits, **run_options) - aer_circuits, idx_maps = assemble_circuits(circuits) + if self._target is not None: + aer_circuits, idx_maps = assemble_circuits(circuits, self.configuration().basis_gates) + else: + aer_circuits, idx_maps = assemble_circuits(circuits) if parameter_binds: run_options["parameterizations"] = self._convert_binds( circuits, parameter_binds, idx_maps @@ -517,9 +525,7 @@ def _compile(self, circuits, **run_options): optypes = [circuit_optypes(circ) for circ in circuits] # Compile Qasm3 instructions - circuits, optypes = compile_circuit( - circuits, basis_gates=self.configuration().basis_gates, optypes=optypes - ) + circuits, optypes = compile_circuit(circuits, optypes=optypes) # run option noise model circuits, noise_model, run_options = self._assemble_noise_model( diff --git a/qiskit_aer/primitives/__init__.py b/qiskit_aer/primitives/__init__.py index 7ed7414f0c..84a2d064d7 100644 --- a/qiskit_aer/primitives/__init__.py +++ b/qiskit_aer/primitives/__init__.py @@ -31,7 +31,11 @@ Estimator """ +import qiskit + from .estimator import Estimator -from .estimator_v2 import EstimatorV2 from .sampler import Sampler -from .sampler_v2 import SamplerV2 + +if not qiskit.__version__.startswith("0."): + from .estimator_v2 import EstimatorV2 + from .sampler_v2 import SamplerV2 diff --git a/qiskit_aer/primitives/estimator.py b/qiskit_aer/primitives/estimator.py index d4f9f65790..3c0ba42473 100644 --- a/qiskit_aer/primitives/estimator.py +++ b/qiskit_aer/primitives/estimator.py @@ -532,6 +532,7 @@ def _transpile_circuits(self, circuits): circuit = self._circuits[i].copy() circuit.measure_all() num_qubits = circuit.num_qubits + self._backend.set_max_qubits(num_qubits) circuit = self._transpile(circuit) bit_map = {bit: index for index, bit in enumerate(circuit.qubits)} layout = [bit_map[qr[0]] for _, qr, _ in circuit[-num_qubits:]] diff --git a/qiskit_aer/primitives/sampler.py b/qiskit_aer/primitives/sampler.py index 7f06124289..17350f0707 100644 --- a/qiskit_aer/primitives/sampler.py +++ b/qiskit_aer/primitives/sampler.py @@ -182,6 +182,15 @@ def _preprocess_circuit(circuit: QuantumCircuit): circuit.save_probabilities_dict(qargs) return circuit + def _transpile_circuit(self, circuit): + self._backend.set_max_qubits(circuit.num_qubits) + transpiled = transpile( + circuit, + self._backend, + **self._transpile_options, + ) + return transpiled + def _transpile(self, circuit_indices: Sequence[int], is_shots_none: bool): to_handle = [ i for i in set(circuit_indices) if (i, is_shots_none) not in self._transpiled_circuits @@ -191,11 +200,7 @@ def _transpile(self, circuit_indices: Sequence[int], is_shots_none: bool): if is_shots_none: circuits = (self._preprocess_circuit(circ) for circ in circuits) if not self._skip_transpilation: - circuits = transpile( - list(circuits), - self._backend, - **self._transpile_options, - ) + circuits = (self._transpile_circuit(circ) for circ in circuits) for i, circuit in zip(to_handle, circuits): self._transpiled_circuits[(i, is_shots_none)] = circuit diff --git a/releasenotes/notes/fix_noise_dynamic_circuits-59c6cf0061e956a8.yaml b/releasenotes/notes/fix_noise_dynamic_circuits-59c6cf0061e956a8.yaml new file mode 100644 index 0000000000..c302bcaff3 --- /dev/null +++ b/releasenotes/notes/fix_noise_dynamic_circuits-59c6cf0061e956a8.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + When the circuit is a dynamic circuit, the input circuit was not correctly + transpiled in AerCompiler that causes wrong noise simulation result, + because some gates noise to be applied were transpiled to other gates. + + This fix uses custom pass manager to decompose only jump and mark ops + Aer uses internally. diff --git a/releasenotes/notes/fixes_dependency_issues_by0.14-da7f11cb29710f86.yaml b/releasenotes/notes/fixes_dependency_issues_by0.14-da7f11cb29710f86.yaml new file mode 100644 index 0000000000..6fdb0e8595 --- /dev/null +++ b/releasenotes/notes/fixes_dependency_issues_by0.14-da7f11cb29710f86.yaml @@ -0,0 +1,11 @@ +--- +fixes: + - | + Fixes for dependency issues caused by release 0.14. + + Fix for issue in samplingVector.allocate() when > 63 qubits + + Use basis_gates in AerCompiler when AerBackend is made by from_backend + + Setting number of qubits before transpile for Primitives V1 (issue #2084) + diff --git a/releasenotes/notes/release_0.14.1-b206e7031adb1af4.yaml b/releasenotes/notes/release_0.14.1-b206e7031adb1af4.yaml new file mode 100644 index 0000000000..193e3d5c5c --- /dev/null +++ b/releasenotes/notes/release_0.14.1-b206e7031adb1af4.yaml @@ -0,0 +1,9 @@ +--- +prelude: > + Qiskit Aer release 0.14.1 is a bugfix release caused by release 0.14.0.1 + Major fixes are: + - Fix sampling measure for > 64 qubits simulation + - Fix for noise simulation with dynamic circuits + - Fix for shot-branching optimizaiton + - Fix for runtime noise sampling on shot-branching + diff --git a/src/framework/operations.hpp b/src/framework/operations.hpp index ea8b68da1a..484dd3660a 100644 --- a/src/framework/operations.hpp +++ b/src/framework/operations.hpp @@ -383,7 +383,6 @@ enum class OpType { superop, roerror, noise_switch, - sample_noise, // Save instructions save_state, save_expval, @@ -532,9 +531,6 @@ inline std::ostream &operator<<(std::ostream &stream, const OpType &type) { case OpType::qerror_loc: stream << "qerror_loc"; break; - case OpType::sample_noise: - stream << "sample_noise"; - break; case OpType::noise_switch: stream << "noise_switch"; break; @@ -614,11 +610,14 @@ struct Op { string_params; // used for label, control-flow, and boolean functions // Conditional Operations - bool conditional = false; // is gate conditional gate - uint_t conditional_reg; // (opt) the (single) register location to look up for - // conditional - BinaryOp binary_op; // (opt) boolean function relation - std::shared_ptr expr; // (opt) classical expression + // is gate conditional gate + bool conditional = false; + // (opt) the (single) register location to look up for conditional + uint_t conditional_reg = 0; + // (opt) boolean function relation + BinaryOp binary_op; + // (opt) classical expression + std::shared_ptr expr = nullptr; // Measurement reg_t memory; // (opt) register operation it acts on (measure) @@ -640,6 +639,9 @@ struct Op { // Save DataSubType save_type = DataSubType::single; + // runtime noise sampling + bool sample_noise = false; + // runtime parameter bind bool has_bind_params = false; }; @@ -1319,8 +1321,14 @@ inline Op bind_parameter(const Op &src, const uint_t iparam, op.type = src.type; op.name = src.name; op.qubits = src.qubits; + op.regs = src.regs; + op.int_params = src.int_params; + op.string_params = src.string_params; op.conditional = src.conditional; op.conditional_reg = src.conditional_reg; + op.binary_op = src.binary_op; + op.expr = src.expr; + op.has_bind_params = false; if (src.params.size() > 0) { uint_t stride = src.params.size() / num_params; diff --git a/src/noise/noise_model.hpp b/src/noise/noise_model.hpp index 23dbccc9bd..4063aaa58a 100644 --- a/src/noise/noise_model.hpp +++ b/src/noise/noise_model.hpp @@ -65,7 +65,13 @@ class NoiseModel { const Method method = Method::circuit, bool sample_at_runtime = false) const; - NoiseOps sample_noise_loc(const Operations::Op &op, RngEngine &rng) const; + NoiseOps sample_noise_at_runtime(const Operations::Op &op, + RngEngine &rng) const; + + void sample_noise_at_runtime(const Operations::Op &op, + NoiseModel::NoiseOps &noise_before, + NoiseModel::NoiseOps &noise_after, + RngEngine &rng) const; // Enable superop sampling method // This will cause all QuantumErrors stored in the noise model @@ -151,14 +157,12 @@ class NoiseModel { void sample_local_quantum_noise(const Operations::Op &op, NoiseOps &noise_before, NoiseOps &noise_after, RngEngine &rng, const Method method, - const reg_t &mapping, - bool sample_at_runtime) const; + const reg_t &mapping) const; void sample_nonlocal_quantum_noise(const Operations::Op &op, NoiseOps &noise_ops, NoiseOps &noise_after, RngEngine &rng, const Method method, - const reg_t &mapping, - bool sample_at_runtime) const; + const reg_t &mapping) const; // Sample noise for the current operation NoiseOps sample_noise_helper(const Operations::Op &op, RngEngine &rng, @@ -176,9 +180,6 @@ class NoiseModel { const std::vector &op_qubits, const std::vector &noise_qubits); - // create loc noise - NoiseOps create_noise_loc(const Operations::Op &op) const; - // Flags which say whether the local or nonlocal error tables are used bool local_quantum_errors_ = false; bool nonlocal_quantum_errors_ = false; @@ -245,7 +246,8 @@ class NoiseModel { std::unordered_set enabled_methods_ = std::unordered_set({Method::circuit}); - // saved qubit mapping for runtime loc + // saved qubit mapping and method for runtime noise sampling + mutable Method circ_method_; mutable reg_t circ_mapping_; }; @@ -281,21 +283,57 @@ Circuit NoiseModel::sample_noise(const Circuit &circ, RngEngine &rng, return sample_noise_circuit(circ, rng, method, sample_at_runtime); } -NoiseModel::NoiseOps NoiseModel::sample_noise_loc(const Operations::Op &op, - RngEngine &rng) const { - auto noise_ops = - sample_noise_op(op, rng, Method::circuit, circ_mapping_, false); +NoiseModel::NoiseOps +NoiseModel::sample_noise_at_runtime(const Operations::Op &op, + RngEngine &rng) const { + // Return operator set + NoiseOps noise_before; + NoiseOps noise_after; + + sample_noise_at_runtime(op, noise_before, noise_after, rng); + + // Combine errors + auto &noise_ops = noise_before; + noise_ops.reserve(noise_before.size() + noise_after.size() + 1); + Operations::Op op_sampled = op; + op_sampled.sample_noise = false; + noise_ops.push_back(op_sampled); + noise_ops.insert(noise_ops.end(), + std::make_move_iterator(noise_after.begin()), + std::make_move_iterator(noise_after.end())); + + return noise_ops; +} + +void NoiseModel::sample_noise_at_runtime(const Operations::Op &op, + NoiseModel::NoiseOps &noise_before, + NoiseModel::NoiseOps &noise_after, + RngEngine &rng) const { + // Apply local errors first + sample_local_quantum_noise(op, noise_before, noise_after, rng, circ_method_, + circ_mapping_); + // Apply nonlocal errors second + sample_nonlocal_quantum_noise(op, noise_before, noise_after, rng, + circ_method_, circ_mapping_); + // Apply readout error to measure ops + if (op.type == Operations::OpType::measure) { + sample_readout_noise(op, noise_after, rng, circ_mapping_); + } // If original op is conditional, make all the noise operations also // conditional if (op.conditional) { - for (auto &noise_op : noise_ops) { + for (auto &noise_op : noise_before) { + noise_op.conditional = op.conditional; + noise_op.conditional_reg = op.conditional_reg; + noise_op.binary_op = op.binary_op; + } + for (auto &noise_op : noise_after) { noise_op.conditional = op.conditional; noise_op.conditional_reg = op.conditional_reg; noise_op.binary_op = op.binary_op; } } - return noise_ops; } Circuit NoiseModel::sample_noise_circuit(const Circuit &circ, RngEngine &rng, @@ -355,6 +393,7 @@ Circuit NoiseModel::sample_noise_circuit(const Circuit &circ, RngEngine &rng, noisy_circ.set_params(); if (sample_at_runtime) { noisy_circ.can_sample = false; + circ_method_ = method; circ_mapping_ = mapping; } return noisy_circ; @@ -518,21 +557,27 @@ NoiseModel::sample_noise_helper(const Operations::Op &op, RngEngine &rng, NoiseOps noise_after; // Apply local errors first sample_local_quantum_noise(op, noise_before, noise_after, rng, method, - mapping, sample_at_runtime); + mapping); // Apply nonlocal errors second sample_nonlocal_quantum_noise(op, noise_before, noise_after, rng, method, - mapping, sample_at_runtime); + mapping); // Apply readout error to measure ops if (op.type == Operations::OpType::measure) { sample_readout_noise(op, noise_after, rng, mapping); } + if (sample_at_runtime && + (noise_before.size() > 0 || noise_after.size() > 0)) { + NoiseOps ret(1); + ret[0] = op; + ret[0].sample_noise = true; + return ret; + } + // Combine errors auto &noise_ops = noise_before; noise_ops.reserve(noise_before.size() + noise_after.size() + 1); - if (op.type != Operations::OpType::sample_noise) { - noise_ops.push_back(op); - } + noise_ops.push_back(op); noise_ops.insert(noise_ops.end(), std::make_move_iterator(noise_after.begin()), std::make_move_iterator(noise_after.end())); @@ -655,8 +700,7 @@ void NoiseModel::sample_local_quantum_noise(const Operations::Op &op, NoiseOps &noise_before, NoiseOps &noise_after, RngEngine &rng, const Method method, - const reg_t &mapping, - bool sample_at_runtime) const { + const reg_t &mapping) const { // If no errors are defined pass if (local_quantum_errors_ == false) return; @@ -714,11 +758,7 @@ void NoiseModel::sample_local_quantum_noise(const Operations::Op &op, : iter_default->second; for (auto &pos : error_positions) { NoiseOps noise_ops; - if (sample_at_runtime) - noise_ops = create_noise_loc(op); - else - noise_ops = - quantum_errors_[pos].sample_noise(op_qubits, rng, method); + noise_ops = quantum_errors_[pos].sample_noise(op_qubits, rng, method); // Duplicate same sampled error operations if (quantum_errors_[pos].errors_after()) noise_after.insert(noise_after.end(), noise_ops.begin(), @@ -734,8 +774,7 @@ void NoiseModel::sample_local_quantum_noise(const Operations::Op &op, void NoiseModel::sample_nonlocal_quantum_noise( const Operations::Op &op, NoiseOps &noise_before, NoiseOps &noise_after, - RngEngine &rng, const Method method, const reg_t &mapping, - bool sample_at_runtime) const { + RngEngine &rng, const Method method, const reg_t &mapping) const { // If no errors are defined pass if (nonlocal_quantum_errors_ == false) return; @@ -783,12 +822,8 @@ void NoiseModel::sample_nonlocal_quantum_noise( auto &error_positions = target_pair.second; for (auto &pos : error_positions) { NoiseOps ops; - if (sample_at_runtime) - ops = create_noise_loc(op); - else { - ops = quantum_errors_[pos].sample_noise(string2reg(target_qubits), - rng, method); - } + ops = quantum_errors_[pos].sample_noise(string2reg(target_qubits), + rng, method); if (quantum_errors_[pos].errors_after()) noise_after.insert(noise_after.end(), ops.begin(), ops.end()); else @@ -800,14 +835,6 @@ void NoiseModel::sample_nonlocal_quantum_noise( } } -NoiseModel::NoiseOps -NoiseModel::create_noise_loc(const Operations::Op &op) const { - NoiseOps ops(1); - ops[0] = op; - ops[0].type = Operations::OpType::sample_noise; - return ops; -} - cmatrix_t NoiseModel::op2superop(const Operations::Op &op) const { switch (op.type) { case Operations::OpType::superop: diff --git a/src/simulators/batch_shots_executor.hpp b/src/simulators/batch_shots_executor.hpp index 612e5ed289..8e829f753f 100644 --- a/src/simulators/batch_shots_executor.hpp +++ b/src/simulators/batch_shots_executor.hpp @@ -99,6 +99,9 @@ class BatchShotsExecutor : public virtual MultiStateExecutor { InputIterator last_meas, uint_t shots, uint_t i_group, ResultItr result, std::vector &rng); + + // check if ops contains pauli ops + bool check_pauli_only(std::vector &ops); }; template @@ -506,13 +509,13 @@ void BatchShotsExecutor::apply_ops_batched_shots_for_group( #endif for (auto op = first; op != last; ++op) { - if (op->type == Operations::OpType::sample_noise) { + if (op->sample_noise) { if (op->expr) { for (uint_t j = Base::top_state_of_group_[i_group]; j < Base::top_state_of_group_[i_group + 1]; j++) { Base::states_[j].qreg().enable_batch(false); Base::states_[j].qreg().read_measured_data(Base::states_[j].creg()); - std::vector nops = noise.sample_noise_loc( + std::vector nops = noise.sample_noise_at_runtime( *op, rng[j - Base::top_state_of_group_[i_group]]); for (uint_t k = 0; k < nops.size(); k++) { Base::states_[j].apply_op( @@ -526,55 +529,87 @@ void BatchShotsExecutor::apply_ops_batched_shots_for_group( // sample error here uint_t count = Base::num_states_in_group_[i_group]; - std::vector> noise_ops(count); + std::vector> noise_ops_before(count); + std::vector> noise_ops_after(count); - uint_t count_ops = 0; - uint_t non_pauli_gate_count = 0; + uint_t count_ops_before = 0; + uint_t count_ops_after = 0; + bool pauli_only_before = true; + bool pauli_only_after = true; if (num_inner_threads > 1) { -#pragma omp parallel for reduction(+: count_ops,non_pauli_gate_count) num_threads(num_inner_threads) +#pragma omp parallel for reduction(+: count_ops_before, count_ops_after) reduction(&: pauli_only_before, pauli_only_after) num_threads(num_inner_threads) for (int_t j = 0; j < (int_t)count; j++) { - noise_ops[j] = noise.sample_noise_loc(*op, rng[j]); - - if (!(noise_ops[j].size() == 0 || - (noise_ops[j].size() == 1 && noise_ops[j][0].name == "id"))) { - count_ops++; - for (uint_t k = 0; k < noise_ops[j].size(); k++) { - if (noise_ops[j][k].name != "id" && noise_ops[j][k].name != "x" && - noise_ops[j][k].name != "y" && noise_ops[j][k].name != "z" && - noise_ops[j][k].name != "pauli") { - non_pauli_gate_count++; - break; - } - } + noise.sample_noise_at_runtime(*op, noise_ops_before[j], + noise_ops_after[j], rng[j]); + + pauli_only_before &= check_pauli_only(noise_ops_before[j]); + pauli_only_after &= check_pauli_only(noise_ops_after[j]); + + if (!(noise_ops_before[j].size() == 0 || + (noise_ops_before[j].size() == 1 && + noise_ops_before[j][0].name == "id"))) { + count_ops_before++; + } + if (!(noise_ops_after[j].size() == 0 || + (noise_ops_after[j].size() == 1 && + noise_ops_after[j][0].name == "id"))) { + count_ops_after++; } } } else { for (uint_t j = 0; j < count; j++) { - noise_ops[j] = noise.sample_noise_loc(*op, rng[j]); - - if (!(noise_ops[j].size() == 0 || - (noise_ops[j].size() == 1 && noise_ops[j][0].name == "id"))) { - count_ops++; - for (uint_t k = 0; k < noise_ops[j].size(); k++) { - if (noise_ops[j][k].name != "id" && noise_ops[j][k].name != "x" && - noise_ops[j][k].name != "y" && noise_ops[j][k].name != "z" && - noise_ops[j][k].name != "pauli") { - non_pauli_gate_count++; - break; - } - } + noise.sample_noise_at_runtime(*op, noise_ops_before[j], + noise_ops_after[j], rng[j]); + + pauli_only_before &= check_pauli_only(noise_ops_before[j]); + pauli_only_after &= check_pauli_only(noise_ops_after[j]); + + if (!(noise_ops_before[j].size() == 0 || + (noise_ops_before[j].size() == 1 && + noise_ops_before[j][0].name == "id"))) { + count_ops_before++; + } + if (!(noise_ops_after[j].size() == 0 || + (noise_ops_after[j].size() == 1 && + noise_ops_after[j][0].name == "id"))) { + count_ops_after++; } } } - if (count_ops == 0) { - continue; // do nothing + // noise before op + if (count_ops_before > 0) { + if (pauli_only_before) { // optimization for Pauli error + Base::states_[istate].qreg().apply_batched_pauli_ops( + noise_ops_before); + } else { + // otherwise execute each circuit + apply_batched_noise_ops(i_group, noise_ops_before, result_it, rng); + } } - if (non_pauli_gate_count == 0) { // optimization for Pauli error - Base::states_[istate].qreg().apply_batched_pauli_ops(noise_ops); - } else { - // otherwise execute each circuit - apply_batched_noise_ops(i_group, noise_ops, result_it, rng); + // apply original op + if (op->expr || !apply_batched_op(istate, *op, result_it, rng, + final_ops && (op + 1 == last))) { + // call apply_op for each state + for (uint_t j = 0; j < Base::num_states_in_group_[i_group]; j++) { + uint_t is = Base::top_state_of_group_[i_group] + j; + uint_t ip = (Base::global_state_index_ + is) / + Base::num_shots_per_bind_param_; + Base::states_[is].qreg().enable_batch(false); + Base::states_[is].qreg().read_measured_data(Base::states_[is].creg()); + Base::states_[is].apply_op(*op, *(result_it + ip), rng[j], + final_ops && (op + 1 == last)); + Base::states_[is].qreg().enable_batch(true); + } + } + // noise after op + if (count_ops_after > 0) { + if (pauli_only_after) { // optimization for Pauli error + Base::states_[istate].qreg().apply_batched_pauli_ops(noise_ops_after); + } else { + // otherwise execute each circuit + apply_batched_noise_ops(i_group, noise_ops_after, result_it, rng); + } } } else { if (!op->expr && apply_batched_op(istate, *op, result_it, rng, @@ -596,6 +631,18 @@ void BatchShotsExecutor::apply_ops_batched_shots_for_group( } } +template +bool BatchShotsExecutor::check_pauli_only( + std::vector &ops) { + for (uint_t k = 0; k < ops.size(); k++) { + if (ops[k].name != "id" && ops[k].name != "x" && ops[k].name != "y" && + ops[k].name != "z" && ops[k].name != "pauli") { + return false; + } + } + return true; +} + template void BatchShotsExecutor::apply_batched_noise_ops( const int_t i_group, const std::vector> &ops, diff --git a/src/simulators/matrix_product_state/matrix_product_state.hpp b/src/simulators/matrix_product_state/matrix_product_state.hpp index 60cba8195f..51df5dd27e 100644 --- a/src/simulators/matrix_product_state/matrix_product_state.hpp +++ b/src/simulators/matrix_product_state/matrix_product_state.hpp @@ -797,7 +797,7 @@ State::sample_measure_using_apply_measure(const reg_t &qubits, uint_t shots, for (int_t i = 0; i < static_cast(shots); i++) { temp.initialize(qreg_); auto single_result = temp.apply_measure_internal(qubits, rnds_list[i]); - all_samples[i] = single_result; + all_samples[i].from_vector(single_result); } } return all_samples; @@ -811,7 +811,7 @@ std::vector State::sample_measure_all(uint_t shots, #pragma omp parallel for if (getenv("PRL_PROB_MEAS")) for (int_t i = 0; i < static_cast(shots); i++) { auto single_result = qreg_.sample_measure(shots, rng); - all_samples[i] = single_result; + all_samples[i].from_vector(single_result); } return all_samples; } diff --git a/src/simulators/multi_state_executor.hpp b/src/simulators/multi_state_executor.hpp index 3f180bea1b..7f80ae2645 100644 --- a/src/simulators/multi_state_executor.hpp +++ b/src/simulators/multi_state_executor.hpp @@ -468,7 +468,7 @@ void MultiStateExecutor::run_circuit_with_shot_branching( // initial state waiting_branches.push_back(std::make_shared()); waiting_branches[0]->set_shots(shots_storage); - waiting_branches[0]->op_iterator() = first; + waiting_branches[0]->set_iterator(first); if (Base::num_bind_params_ > 1) { waiting_branches[0]->set_param_index(global_state_index_ + ishot, Base::num_shots_per_bind_param_); @@ -494,7 +494,7 @@ void MultiStateExecutor::run_circuit_with_shot_branching( break; uint_t sid = top_state + i; waiting_branches[i]->state_index() = sid; - waiting_branches[i]->op_iterator() = first; + waiting_branches[i]->set_iterator(first); branches.push_back(waiting_branches[i]); // initialize state @@ -514,7 +514,7 @@ void MultiStateExecutor::run_circuit_with_shot_branching( while (num_active_states > 0) { // loop until all branches execute all ops // functor for ops execution auto apply_ops_func = [this, &branches, &noise, &par_results, measure_seq, - last, par_shots, num_active_states](int_t i) { + last, par_shots](int_t i) { uint_t istate, state_end; istate = branches.size() * i / par_shots; state_end = branches.size() * (i + 1) / par_shots; @@ -524,56 +524,9 @@ void MultiStateExecutor::run_circuit_with_shot_branching( for (; istate < state_end; istate++) { state_t &state = states_[branches[istate]->state_index()]; - while (branches[istate]->op_iterator() != measure_seq || - branches[istate]->additional_ops().size() > 0) { - // execute additional ops first if avaiable - if (branches[istate]->additional_ops().size() > 0) { - int_t iadd = 0; - int_t num_add = branches[istate]->additional_ops().size(); - while (iadd < num_add) { - if (apply_branching_op(*branches[istate], - branches[istate]->additional_ops()[iadd], - par_results[i].begin(), false)) { - // check if there are new branches - if (branches[istate]->num_branches() > 0) { - // if there are additional ops remaining, queue them on new - // branches - for (uint_t k = iadd + 1; - k < branches[istate]->additional_ops().size(); k++) { - for (uint_t l = 0; l < branches[istate]->num_branches(); - l++) - branches[istate]->branches()[l]->add_op_after_branch( - branches[istate]->additional_ops()[k]); - } - branches[istate]->remove_empty_branches(); - state.creg() = branches[istate]->creg(); - - // if there are some branches still remaining - if (branches[istate]->num_branches() > 0) { - nbranch += branches[istate]->num_branches(); - break; - } - iadd = 0; - num_add = branches[istate]->additional_ops().size(); - } - } else { - state.apply_op(branches[istate]->additional_ops()[iadd], - par_results[i][0], dummy_rng, false); - } - iadd++; - } - branches[istate]->clear_additional_ops(); - // if there are some branches still remaining - if (branches[istate]->num_branches() > 0) { - nbranch += branches[istate]->num_branches(); - break; - } - } + while (branches[istate]->op_iterator() != measure_seq) { OpItr op = branches[istate]->op_iterator(); - if (op == measure_seq) - break; - // then execute ops if (!state.creg().check_conditional(*op)) { branches[istate]->advance_iterator(); continue; @@ -581,13 +534,13 @@ void MultiStateExecutor::run_circuit_with_shot_branching( if (branches[istate]->apply_control_flow(state.creg(), measure_seq)) continue; - // runtime noise sampling - if (op->type == Operations::OpType::sample_noise) { + branches[istate]->advance_iterator(); + if (op->sample_noise) { + // runtime noise sampling branches[istate]->apply_runtime_noise_sampling(state.creg(), *op, noise); - } - // runtime parameterizaion - else if (op->has_bind_params) { + } else if (op->has_bind_params) { + // runtime parameterizaion apply_runtime_parameterization(*branches[istate], *op); } else { if (!apply_branching_op(*branches[istate], *op, @@ -598,7 +551,6 @@ void MultiStateExecutor::run_circuit_with_shot_branching( } } - branches[istate]->advance_iterator(); if (branches[istate]->num_branches() > 0) { branches[istate]->remove_empty_branches(); state.creg() = branches[istate]->creg(); @@ -672,8 +624,7 @@ void MultiStateExecutor::run_circuit_with_shot_branching( // check if there are remaining ops num_active_states = 0; for (uint_t i = 0; i < branches.size(); i++) { - if (branches[i]->op_iterator() != measure_seq || - branches[i]->additional_ops().size() > 0) + if (branches[i]->op_iterator() != measure_seq) num_active_states++; } } diff --git a/src/simulators/sample_vector.hpp b/src/simulators/sample_vector.hpp index 36717bfc4f..afc4658302 100644 --- a/src/simulators/sample_vector.hpp +++ b/src/simulators/sample_vector.hpp @@ -123,9 +123,7 @@ void SampleVector::allocate(uint_t n, uint_t base) { elem_mask_ = (1ull << (elem_shift_bits_ + 1)) - 1; vec_mask_ = (1ull << vec_shift_bits_) - 1; - uint_t size = n >> vec_shift_bits_; - if (size == 0) - size = 1; + uint_t size = (n + (REG_SIZE >> elem_shift_bits_) - 1) >> vec_shift_bits_; bits_.resize(size, 0ull); size_ = n; } @@ -185,7 +183,6 @@ void SampleVector::from_vector_with_map(const reg_t &src, const reg_t &map, uint_t pos = 0; uint_t n = REG_SIZE >> elem_shift_bits_; for (uint_t i = 0; i < bits_.size(); i++) { - uint_t n = REG_SIZE; uint_t val = 0; if (n > size_ - pos) n = size_ - pos; diff --git a/src/simulators/shot_branching.hpp b/src/simulators/shot_branching.hpp index 0d81f707a4..ab6805ec25 100644 --- a/src/simulators/shot_branching.hpp +++ b/src/simulators/shot_branching.hpp @@ -39,6 +39,7 @@ class Branch { // additional operations applied after shot branching std::vector additional_ops_; + uint_t additional_op_pos_; // mark for control flow std::unordered_map flow_marks_; @@ -49,8 +50,11 @@ class Branch { // branches from this std::vector> branches_; + // this flag is used for initialize op + bool initialize_after_reset_ = false; + public: - Branch(void) {} + Branch(void) { additional_op_pos_ = 0; } ~Branch() { shots_.clear(); additional_ops_.clear(); @@ -61,22 +65,28 @@ class Branch { creg_ = src.creg_; iter_ = src.iter_; flow_marks_ = src.flow_marks_; + additional_ops_ = src.additional_ops_; + additional_op_pos_ = src.additional_op_pos_; } uint_t &state_index(void) { return state_index_; } uint_t &root_state_index(void) { return root_state_index_; } ClassicalRegister &creg(void) { return creg_; } std::vector &rng_shots(void) { return shots_; } - OpItr &op_iterator(void) { return iter_; } std::unordered_map &marks(void) { return flow_marks_; } uint_t num_branches(void) { return branches_.size(); } std::vector> &branches(void) { return branches_; } + bool &initialize_after_reset(void) { return initialize_after_reset_; } + + void set_iterator(OpItr &iter) { iter_ = iter; } + OpItr op_iterator(void); uint_t num_shots(void) { return shots_.size(); } void clear(void) { shots_.clear(); additional_ops_.clear(); branches_.clear(); + additional_op_pos_ = 0; } void clear_branch(void) { branches_.clear(); } @@ -91,16 +101,16 @@ class Branch { void add_op_after_branch(Operations::Op &op) { additional_ops_.push_back(op); } - void copy_ops_after_branch(std::vector &ops) { - additional_ops_ = ops; + void add_ops_after_branch(std::vector &ops) { + additional_ops_.insert(additional_ops_.end(), ops.begin(), ops.end()); } - void clear_additional_ops(void) { additional_ops_.clear(); } - - std::vector &additional_ops(void) { return additional_ops_; } void branch_shots(reg_t &shots, int_t nbranch); bool apply_control_flow(ClassicalRegister &creg, OpItr last) { + if (additional_ops_.size() > additional_op_pos_) + return false; + if (iter_->type == Operations::OpType::mark) { flow_marks_[iter_->string_params[0]] = iter_; iter_++; @@ -236,26 +246,35 @@ void Branch::branch_shots_by_params(void) { } } -void Branch::advance_iterator(void) { - iter_++; - for (uint_t i = 0; i < branches_.size(); i++) { - branches_[i]->iter_++; +OpItr Branch::op_iterator(void) { + if (additional_ops_.size() > additional_op_pos_) { + OpItr it = additional_ops_.cbegin(); + it += additional_op_pos_; + return it; } + return iter_; +} + +void Branch::advance_iterator(void) { + if (additional_ops_.size() > additional_op_pos_) + additional_op_pos_++; + else + iter_++; } bool Branch::apply_runtime_noise_sampling(const ClassicalRegister &creg, const Operations::Op &op, const Noise::NoiseModel &noise) { - if (op.type != Operations::OpType::sample_noise) - return false; - uint_t nshots = num_shots(); reg_t shot_map(nshots); std::vector> noises; + if (!op.sample_noise) + return false; + for (uint_t i = 0; i < nshots; i++) { std::vector noise_ops = - noise.sample_noise_loc(op, shots_[i]); + noise.sample_noise_at_runtime(op, shots_[i]); // search same noise ops int_t pos = -1; @@ -264,6 +283,11 @@ bool Branch::apply_runtime_noise_sampling(const ClassicalRegister &creg, continue; bool same = true; for (uint_t k = 0; k < noise_ops.size(); k++) { + if (noise_ops[k].sample_noise) { + noise_ops[k].sample_noise = false; + continue; // skip original op + } + if (noise_ops[k].type != noises[j][k].type || noise_ops[k].name != noises[j][k].name) same = false; @@ -323,6 +347,14 @@ bool Branch::apply_runtime_noise_sampling(const ClassicalRegister &creg, } } + if (noises.size() == 0) { + for (uint_t k = 0; k < noise_ops.size(); k++) { + if (noise_ops[k].sample_noise) { + noise_ops[k].sample_noise = false; + } + } + } + if (pos < 0) { // if not found, add noise ops to the list shot_map[i] = noises.size(); noises.push_back(noise_ops); @@ -334,37 +366,51 @@ bool Branch::apply_runtime_noise_sampling(const ClassicalRegister &creg, creg_ = creg; branch_shots(shot_map, noises.size()); for (uint_t i = 0; i < noises.size(); i++) { - branches_[i]->copy_ops_after_branch(noises[i]); + branches_[i]->add_ops_after_branch(noises[i]); } return true; } void Branch::remove_empty_branches(void) { - int_t istart = 0; + // find first branch that has at least one shot + int_t iroot = -1; for (uint_t j = 0; j < branches_.size(); j++) { if (branches_[j]->num_shots() > 0) { - // copy shots to the root - shots_ = branches_[j]->rng_shots(); - param_index_ = branches_[j]->param_index_; - param_shots_ = branches_[j]->param_shots_; - additional_ops_ = branches_[j]->additional_ops(); - creg_ = branches_[j]->creg(); - branches_[j].reset(); - istart = j + 1; + iroot = j; break; } branches_[j].reset(); } - std::vector> new_branches; + // copy shots to the root + shots_ = branches_[iroot]->rng_shots(); + param_index_ = branches_[iroot]->param_index_; + param_shots_ = branches_[iroot]->param_shots_; + creg_ = branches_[iroot]->creg(); + initialize_after_reset_ = branches_[iroot]->initialize_after_reset_; - for (uint_t j = istart; j < branches_.size(); j++) { - if (branches_[j]->num_shots() > 0) - new_branches.push_back(branches_[j]); - else + std::vector> new_branches; + for (uint_t j = iroot; j < branches_.size(); j++) { + if (branches_[j]->num_shots() > 0) { + // update additional ops if there are remaining additional ops + if (additional_ops_.size() > additional_op_pos_) { + branches_[j]->additional_ops_.insert( + branches_[j]->additional_ops_.end(), + additional_ops_.begin() + additional_op_pos_, + additional_ops_.end()); + } + if (j != iroot) + new_branches.push_back(branches_[j]); + } else branches_[j].reset(); } + + additional_ops_ = branches_[iroot]->additional_ops_; + additional_op_pos_ = 0; + branches_[iroot].reset(); + branches_.clear(); + branches_ = new_branches; } diff --git a/src/simulators/stabilizer/stabilizer_state.hpp b/src/simulators/stabilizer/stabilizer_state.hpp index e695411b61..cd0e18f678 100644 --- a/src/simulators/stabilizer/stabilizer_state.hpp +++ b/src/simulators/stabilizer/stabilizer_state.hpp @@ -519,7 +519,7 @@ std::vector State::sample_measure(const reg_t &qubits, auto qreg_cache = BaseState::qreg_; std::vector samples(shots); for (int_t ishot = 0; ishot < shots; ishot++) { - samples[ishot] = apply_measure_and_update(qubits, rng); + samples[ishot].from_vector(apply_measure_and_update(qubits, rng)); BaseState::qreg_ = qreg_cache; // restore pre-measurement data from cache } return samples; diff --git a/src/simulators/statevector/statevector_executor.hpp b/src/simulators/statevector/statevector_executor.hpp index 826642fc6a..26b00e801e 100644 --- a/src/simulators/statevector/statevector_executor.hpp +++ b/src/simulators/statevector/statevector_executor.hpp @@ -1647,19 +1647,22 @@ void Executor::apply_initialize(CircuitExecutor::Branch &root, } } - if (root.additional_ops().size() == 0) { + if (!root.initialize_after_reset()) { apply_reset(root, qubits); - Operations::Op op; - op.type = OpType::initialize; - op.name = "initialize"; - op.qubits = qubits; - op.params = params; - for (uint_t i = 0; i < root.num_branches(); i++) { - root.branches()[i]->add_op_after_branch(op); + if (root.num_branches() > 0) { + Operations::Op op; + op.type = OpType::initialize; + op.name = "initialize"; + op.qubits = qubits; + op.params = params; + for (uint_t i = 0; i < root.num_branches(); i++) { + root.branches()[i]->add_op_after_branch(op); + root.branches()[i]->initialize_after_reset() = true; + } + return; // initialization will be done in next call because of shot + // branching in reset } - return; // initialization will be done in next call because of shot - // branching in reset } Base::states_[root.state_index()].qreg().initialize_component(qubits, params); diff --git a/src/simulators/tensor_network/tensor_net_executor.hpp b/src/simulators/tensor_network/tensor_net_executor.hpp index 5bcc47532f..cd147e0797 100644 --- a/src/simulators/tensor_network/tensor_net_executor.hpp +++ b/src/simulators/tensor_network/tensor_net_executor.hpp @@ -276,19 +276,22 @@ void Executor::apply_initialize(CircuitExecutor::Branch &root, } } - if (root.additional_ops().size() == 0) { + if (!root.initialize_after_reset()) { apply_reset(root, qubits); - Operations::Op op; - op.type = OpType::initialize; - op.name = "initialize"; - op.qubits = qubits; - op.params = params; - for (uint_t i = 0; i < root.num_branches(); i++) { - root.branches()[i]->add_op_after_branch(op); + if (root.num_branches() > 0) { + Operations::Op op; + op.type = OpType::initialize; + op.name = "initialize"; + op.qubits = qubits; + op.params = params; + for (uint_t i = 0; i < root.num_branches(); i++) { + root.branches()[i]->add_op_after_branch(op); + root.branches()[i]->initialize_after_reset() = true; + } + return; // initialization will be done in next call because of shot + // branching in reset } - return; // initialization will be done in next call because of shot - // branching in reset } Base::states_[root.state_index()].qreg().initialize_component(qubits, params); diff --git a/src/transpile/fusion.hpp b/src/transpile/fusion.hpp index c65160fed9..9948c640dc 100644 --- a/src/transpile/fusion.hpp +++ b/src/transpile/fusion.hpp @@ -174,7 +174,7 @@ class UnitaryFusion : public FusionMethod { }; virtual bool can_apply(const op_t &op, uint_t max_fused_qubits) const { - if (op.conditional) + if (op.conditional || op.sample_noise) return false; switch (op.type) { case optype_t::matrix: @@ -221,7 +221,7 @@ class SuperOpFusion : public UnitaryFusion { }; virtual bool can_apply(const op_t &op, uint_t max_fused_qubits) const { - if (op.conditional) + if (op.conditional || op.sample_noise) return false; switch (op.type) { case optype_t::kraus: @@ -271,7 +271,7 @@ class KrausFusion : public UnitaryFusion { }; virtual bool can_apply(const op_t &op, uint_t max_fused_qubits) const { - if (op.conditional) + if (op.conditional || op.sample_noise) return false; switch (op.type) { case optype_t::kraus: diff --git a/test/terra/backends/aer_simulator/test_measure.py b/test/terra/backends/aer_simulator/test_measure.py index eda06dbf7c..72356e5f89 100644 --- a/test/terra/backends/aer_simulator/test_measure.py +++ b/test/terra/backends/aer_simulator/test_measure.py @@ -17,6 +17,7 @@ from test.terra.reference import ref_measure from qiskit import QuantumCircuit from qiskit import transpile +import qiskit.quantum_info as qi from qiskit_aer import AerSimulator from qiskit_aer.noise import NoiseModel from qiskit_aer.noise.errors import ReadoutError, depolarizing_error @@ -265,6 +266,24 @@ def test_measure_stablizer_64bit(self, method, device): self.assertDictAlmostEqual(output, targets, delta=delta * shots) + @supported_methods(["stabilizer"], [65, 127, 433]) + def test_measure_sampling_large_ghz_stabilizer(self, method, device, num_qubits): + """Test sampling measure for large stabilizer circuit""" + shots = 1000 + delta = 0.05 + qc = QuantumCircuit(num_qubits) + qc.h(0) + for q in range(1, num_qubits): + qc.cx(q - 1, q) + qc.measure_all() + backend = self.backend(method=method) + result = backend.run(qc, shots=shots).result() + counts = result.get_counts() + targets = {} + targets["0" * num_qubits] = shots / 2 + targets["1" * num_qubits] = shots / 2 + self.assertDictAlmostEqual(counts, targets, delta=delta * shots) + # --------------------------------------------------------------------- # Test MPS algorithms for measure # --------------------------------------------------------------------- diff --git a/tox.ini b/tox.ini index d537174f70..3ab790f5d1 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,9 @@ setenv = LANGUAGE=en_US LC_ALL=en_US.utf-8 SETUPTOOLS_ENABLE_FEATURES=legacy-editable +passenv= + GITHUB_REF_NAME + GITHUB_BASE_REF whitelist_externals = sh deps = -r requirements-dev.txt