diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9fbde6c804..a0a971dd58 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,8 +39,8 @@ jobs: matrix: os: [ubuntu-latest] steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 name: Install Python with: python-version: '3.10' @@ -48,19 +48,15 @@ jobs: with: toolchain: stable - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3 with: platforms: all - - name: Install cibuildwheel - run: | - python -m pip install cibuildwheel==2.17.0 - name: Build wheels - run: | - python -m cibuildwheel --output-dir wheelhouse + uses: pypa/cibuildwheel@v2.19.2 env: - CIBW_BEFORE_ALL_LINUX: "sed -i -e 's/^mirrorlist/#mirrorlist/' -e 's/^#baseurl/baseurl/' -e 's/mirror.centos.org/vault.centos.org/' /etc/yum.repos.d/*.repo && yum install -y https://dl.fedoraproject.org/pub/epel/7/aarch64/Packages/e/epel-release-7-12.noarch.rpm && yum install -y openblas-devel" + CIBW_BEFORE_ALL_LINUX: "sed -i -e 's/^mirrorlist/#mirrorlist/' -e 's/^#baseurl/baseurl/' -e 's/mirror.centos.org/vault.centos.org/' /etc/yum.repos.d/*.repo && yum install -y https://dl.fedoraproject.org/pub/archive/epel/7/aarch64/Packages/e/epel-release-7-12.noarch.rpm && yum install -y openblas-devel" CIBW_ARCHS_LINUX: aarch64 - CIBW_TEST_SKIP: "cp38* cp39* cp310* cp311*" + CIBW_TEST_SKIP: "cp*" - uses: actions/upload-artifact@v4 with: path: ./wheelhouse/*.whl @@ -226,15 +222,15 @@ jobs: with: toolchain: stable - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3 with: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.16.2 + uses: pypa/cibuildwheel@v2.19.2 env: CIBW_ARCHS_LINUX: s390x CIBW_TEST_SKIP: "cp*" - CIBW_BEFORE_ALL: "sed -i -e 's/^mirrorlist/#mirrorlist/' -e 's/^#baseurl/baseurl/' -e 's/mirror.centos.org/vault.centos.org/' /etc/yum.repos.d/*.repo && yum install -y epel-release && yum install -y openblas-devel" + CIBW_BEFORE_ALL: "sed -i -e 's/^mirrorlist/#mirrorlist/' -e 's/^#baseurl/baseurl/' -e 's/mirror.centos.org/vault.centos.org/' /etc/yum.repos.d/*.repo && yum install -y https://dl.fedoraproject.org/pub/epel/8/Everything/s390x/Packages/e/epel-release-8-21.el8.noarch.rpm && yum install -y openblas-devel" - uses: actions/upload-artifact@v4 with: path: ./wheelhouse/*.whl @@ -262,15 +258,15 @@ jobs: with: toolchain: stable - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3 with: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.16.2 + uses: pypa/cibuildwheel@v2.19.2 env: + CIBW_BEFORE_ALL: "sed -i -e 's/^mirrorlist/#mirrorlist/' -e 's/^#baseurl/baseurl/' -e 's/mirror.centos.org/vault.centos.org/' /etc/yum.repos.d/*.repo && yum install -y https://dl.fedoraproject.org/pub/archive/epel/7/ppc64le/Packages/e/epel-release-7-14.noarch.rpm && yum install -y openblas-devel" CIBW_ARCHS_LINUX: ppc64le CIBW_TEST_SKIP: "cp*" - CIBW_BEFORE_ALL: "sed -i -e 's/^mirrorlist/#mirrorlist/' -e 's/^#baseurl/baseurl/' -e 's/mirror.centos.org/vault.centos.org/' /etc/yum.repos.d/*.repo && yum install -y epel-release && yum install -y openblas-devel" - uses: actions/upload-artifact@v4 with: path: ./wheelhouse/*.whl diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 074add47a9..30154ce1ff 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.9'] steps: - uses: actions/checkout@v4 with: @@ -50,7 +50,7 @@ jobs: needs: [docs] strategy: matrix: - python-version: ['3.8'] + python-version: ['3.9'] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} @@ -74,7 +74,6 @@ jobs: pip install -U -r requirements-dev.txt -c constraints.txt pip install -c constraints.txt git+https://github.com/Qiskit/qiskit pip install -c constraints.txt . - pip install -U "qiskit-ibmq-provider" "z3-solver" "qiskit-ignis" "qiskit-aqua" "pyscf<1.7.4" "matplotlib>=3.3.0" jupyter pylatexenc nbsphinx cvxpy qiskit-sphinx-theme -c constraints.txt sudo apt install -y graphviz pandoc libopenblas-dev pip check shell: bash @@ -82,7 +81,7 @@ jobs: run: | set -e cd qiskit-tutorials - rm -rf tutorials/chemistry tutorials/circuits tutorials/circuits_advanced tutorials/finance tutorials/optimization tutorials/algorithms tutorials/operators tutorials/noise tutorials/machine_learning + rm -rf tutorials/circuits tutorials/circuits_advanced tutorials/algorithms tutorials/operators sphinx-build -b html . _build/html - uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 52b4a50ab6..db341fb5cf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -97,7 +97,7 @@ jobs: qiskit-extra: [""] include: - python-version: "3.10" - qiskit-extra: "'qiskit>=1.0.0rc1'" + qiskit-extra: "'qiskit>=1.3.0b1'" env: AER_THRUST_BACKEND: OMP diff --git a/qiskit_aer/VERSION.txt b/qiskit_aer/VERSION.txt index a551051694..04a373efe6 100644 --- a/qiskit_aer/VERSION.txt +++ b/qiskit_aer/VERSION.txt @@ -1 +1 @@ -0.15.0 +0.16.0 diff --git a/qiskit_aer/noise/noise_model.py b/qiskit_aer/noise/noise_model.py index 24933ec1d3..af5328de3b 100644 --- a/qiskit_aer/noise/noise_model.py +++ b/qiskit_aer/noise/noise_model.py @@ -20,7 +20,8 @@ import numpy as np -from qiskit.circuit import Instruction, Delay +from qiskit.circuit import QuantumCircuit, Instruction, Delay, Reset +from qiskit.circuit.library.generalized_gates import PauliGate, UnitaryGate from qiskit.providers import QubitProperties from qiskit.providers.exceptions import BackendPropertyError from qiskit.providers.models.backendproperties import BackendProperties @@ -959,6 +960,105 @@ def to_dict(self, serializable=False): return ret + @staticmethod + def from_dict(noise_dict): + """ + Load NoiseModel from a dictionary. + Args: + noise_dict (dict): A serialized noise model. + Returns: + NoiseModel: the noise model. + Raises: + NoiseError: if dict cannot be converted to NoiseModel. + """ + warn( + "from_dict has been deprecated as of qiskit-aer 0.15.0" + " and will be removed no earlier than 3 months from that release date.", + DeprecationWarning, + stacklevel=2, + ) + + def inst_dic_list_to_circuit(dic_list): + num_qubits = max(max(dic["qubits"]) for dic in dic_list) + 1 + circ = QuantumCircuit(num_qubits) + for dic in dic_list: + if dic["name"] == "reset": + circ.append(Reset(), qargs=dic["qubits"]) + elif dic["name"] == "kraus": + circ.append( + Instruction( + name="kraus", + num_qubits=len(dic["qubits"]), + num_clbits=0, + params=dic["params"], + ), + qargs=dic["qubits"], + ) + elif dic["name"] == "unitary": + circ.append(UnitaryGate(data=dic["params"][0]), qargs=dic["qubits"]) + elif dic["name"] == "pauli": + circ.append(PauliGate(dic["params"][0]), qargs=dic["qubits"]) + else: + with catch_warnings(): + filterwarnings( + "ignore", + category=DeprecationWarning, + module="qiskit_aer.noise.errors.errorutils", + ) + circ.append( + UnitaryGate( + label=dic["name"], data=_standard_gate_unitary(dic["name"]) + ), + qargs=dic["qubits"], + ) + return circ + + # Return noise model + noise_model = NoiseModel() + + # Get error terms + errors = noise_dict.get("errors", []) + + for error in errors: + error_type = error["type"] + + # Add QuantumError + if error_type == "qerror": + circuits = [inst_dic_list_to_circuit(dics) for dics in error["instructions"]] + noise_ops = tuple(zip(circuits, error["probabilities"])) + qerror = QuantumError(noise_ops) + qerror._id = error.get("id", None) or qerror.id + instruction_names = error["operations"] + all_gate_qubits = error.get("gate_qubits", None) + if all_gate_qubits is not None: + for gate_qubits in all_gate_qubits: + # Add local quantum error + noise_model.add_quantum_error( + qerror, instruction_names, gate_qubits, warnings=False + ) + else: + # Add all-qubit quantum error + noise_model.add_all_qubit_quantum_error( + qerror, instruction_names, warnings=False + ) + + # Add ReadoutError + elif error_type == "roerror": + probabilities = error["probabilities"] + all_gate_qubits = error.get("gate_qubits", None) + roerror = ReadoutError(probabilities) + # Add local readout error + if all_gate_qubits is not None: + for gate_qubits in all_gate_qubits: + noise_model.add_readout_error(roerror, gate_qubits, warnings=False) + # Add all-qubit readout error + else: + noise_model.add_all_qubit_readout_error(roerror, warnings=False) + # Invalid error type + else: + raise NoiseError("Invalid error type: {}".format(error_type)) + return noise_model + def _instruction_names_labels(self, instructions): """Return two lists of instruction name strings and label strings.""" if not isinstance(instructions, (list, tuple)): diff --git a/qiskit_aer/noise/passes/local_noise_pass.py b/qiskit_aer/noise/passes/local_noise_pass.py index 8713ed494d..f4ebf2fa7c 100644 --- a/qiskit_aer/noise/passes/local_noise_pass.py +++ b/qiskit_aer/noise/passes/local_noise_pass.py @@ -144,7 +144,9 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: if isinstance(new_op, QuantumCircuit): # If the new op is a quantum circuit, compose its DAG with the new dag # so that it is unrolled rather than added as an opaque instruction - new_dag.compose(circuit_to_dag(new_op), qubits=node.qargs) # never touch clbits + new_dag.compose( + circuit_to_dag(new_op), qubits=list(node.qargs) + ) # never touch clbits else: # Otherwise append the instruction returned by the function new_dag.apply_operation_back(new_op, qargs=node.qargs) # never touch cargs diff --git a/releasenotes/notes/fix_mps_size_estimator-0dcf387fd870f5e5.yaml b/releasenotes/notes/fix_mps_size_estimator-0dcf387fd870f5e5.yaml new file mode 100644 index 0000000000..8481135bac --- /dev/null +++ b/releasenotes/notes/fix_mps_size_estimator-0dcf387fd870f5e5.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + SIGSEGV raised when the circuits with unsupported gates is + passed to MPS simulator, because of `std::set.find()` in + size estimator for MPS. + This fix avoids SIGSEGV if unsupported gates is passed. diff --git a/releasenotes/notes/fix_tests_0.15-dccf0acb3cf10571.yaml b/releasenotes/notes/fix_tests_0.15-dccf0acb3cf10571.yaml new file mode 100644 index 0000000000..6fef6908c9 --- /dev/null +++ b/releasenotes/notes/fix_tests_0.15-dccf0acb3cf10571.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed tutorial build failure of Python version and unused tutorials. + Upgraded test for Qiskit 1.0.0rc1 to 1.3.0b1 this will need protection rules + for Aer repository. Also fixed noise/passes/local_noise_pass.py needed + conversion from tuple to list diff --git a/releasenotes/notes/revert_noise_from_dict-b3a85bf28582a9b1.yaml b/releasenotes/notes/revert_noise_from_dict-b3a85bf28582a9b1.yaml new file mode 100644 index 0000000000..0cd1bf54eb --- /dev/null +++ b/releasenotes/notes/revert_noise_from_dict-b3a85bf28582a9b1.yaml @@ -0,0 +1,5 @@ +--- +deprecations: + - | + Reverting NoiseModel.from_dict temporary to extend deprecation period + for qiskit-ibm-runtime, but we will remove again in the future release diff --git a/releasenotes/notes/truncate_expval-7fa814c732cca8db.yaml b/releasenotes/notes/truncate_expval-7fa814c732cca8db.yaml new file mode 100644 index 0000000000..1c7452328b --- /dev/null +++ b/releasenotes/notes/truncate_expval-7fa814c732cca8db.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + This fix truncates qubits of save_expval operation when EstimatorV2 + is made from existing backends using `from_backend`. + By transpiling on the existing backends, ancilla qubits are filled + to all the qubits that causes memory error on the simulator. + So Aer removes unused qubits for save_expval operation. diff --git a/src/framework/circuit.hpp b/src/framework/circuit.hpp index d5b5655256..eab35cd514 100644 --- a/src/framework/circuit.hpp +++ b/src/framework/circuit.hpp @@ -46,6 +46,7 @@ class Circuit { uint_t num_qubits = 0; // maximum number of qubits needed for ops uint_t num_memory = 0; // maximum number of memory clbits needed for ops uint_t num_registers = 0; // maximum number of registers clbits needed for ops + uint_t num_original_qubits = 0; // number of qubits without ancilla qubits // Measurement params bool has_conditional = false; // True if any ops are conditional @@ -419,7 +420,20 @@ void Circuit::reset_metadata() { void Circuit::add_op_metadata(const Op &op) { has_conditional |= op.conditional; opset_.insert(op); - qubitset_.insert(op.qubits.begin(), op.qubits.end()); + if (!qubitset_.empty() && + (op.type == OpType::save_expval || op.type == OpType::save_expval_var)) { + for (int_t j = 0; j < op.expval_params.size(); j++) { + const std::string &pauli = std::get<0>(op.expval_params[j]); + for (int_t i = 0; i < op.qubits.size(); i++) { + // add qubit with non-I operator + if (pauli[pauli.size() - 1 - i] != 'I') { + qubitset_.insert(op.qubits[i]); + } + } + } + } else { + qubitset_.insert(op.qubits.begin(), op.qubits.end()); + } memoryset_.insert(op.memory.begin(), op.memory.end()); registerset_.insert(op.registers.begin(), op.registers.end()); @@ -589,6 +603,19 @@ void Circuit::set_params(bool truncation) { } if (remapped_qubits) { remap_qubits(ops[pos]); + } else if (truncation && qubitmap_.size() < ops[pos].qubits.size()) { + // truncate save_expval here when remap is not needed + if (ops[pos].type == OpType::save_expval || + ops[pos].type == OpType::save_expval_var) { + int_t nparams = ops[pos].expval_params.size(); + for (int_t i = 0; i < nparams; i++) { + std::string &pauli = std::get<0>(ops[pos].expval_params[i]); + std::string new_pauli; + new_pauli.assign(pauli.end() - qubitmap_.size(), pauli.end()); + pauli = new_pauli; + } + ops[pos].qubits.resize(qubitmap_.size()); + } } if (pos != op_idx) { ops[op_idx] = std::move(ops[pos]); @@ -653,11 +680,29 @@ void Circuit::set_params(bool truncation) { } void Circuit::remap_qubits(Op &op) const { - reg_t new_qubits; - for (auto &qubit : op.qubits) { - new_qubits.push_back(qubitmap_.at(qubit)); + // truncate save_expval + if (op.type == OpType::save_expval || op.type == OpType::save_expval_var) { + int_t nparams = op.expval_params.size(); + for (int_t i = 0; i < nparams; i++) { + std::string &pauli = std::get<0>(op.expval_params[i]); + std::string new_pauli; + new_pauli.resize(qubitmap_.size()); + for (auto q = qubitmap_.cbegin(); q != qubitmap_.cend(); q++) { + new_pauli[qubitmap_.size() - 1 - q->second] = + pauli[pauli.size() - 1 - q->first]; + } + pauli = new_pauli; + } + for (int_t i = 0; i < qubitmap_.size(); i++) { + op.qubits[i] = i; + } + } else { + reg_t new_qubits; + for (auto &qubit : op.qubits) { + new_qubits.push_back(qubitmap_.at(qubit)); + } + op.qubits = std::move(new_qubits); } - op.qubits = std::move(new_qubits); } bool Circuit::check_result_ancestor( diff --git a/src/simulators/matrix_product_state/matrix_product_state_size_estimator.hpp b/src/simulators/matrix_product_state/matrix_product_state_size_estimator.hpp index dc86812d98..6a5b6022be 100644 --- a/src/simulators/matrix_product_state/matrix_product_state_size_estimator.hpp +++ b/src/simulators/matrix_product_state/matrix_product_state_size_estimator.hpp @@ -74,17 +74,19 @@ uint_t MPSSizeEstimator::estimate(const std::vector &ops, case Operations::OpType::gate: if (ops[i].qubits.size() > 1) { auto it = gateset.find(ops[i].name); - switch (it->second) { - case Gates::rxx: - case Gates::ryy: - case Gates::rzx: - pi2 = std::real(ops[i].params[0]) / M_PI; - pi2_int = (double)std::round(pi2); - if (!AER::Linalg::almost_equal(pi2, pi2_int)) - apply_qubits(ops[i].qubits); - break; - default: - break; + if (it != gateset.end()) { + switch (it->second) { + case Gates::rxx: + case Gates::ryy: + case Gates::rzx: + pi2 = std::real(ops[i].params[0]) / M_PI; + pi2_int = (double)std::round(pi2); + if (!AER::Linalg::almost_equal(pi2, pi2_int)) + apply_qubits(ops[i].qubits); + break; + default: + break; + } } } break; diff --git a/test/terra/noise/test_noise_model.py b/test/terra/noise/test_noise_model.py index 850e025192..892af7c9e7 100644 --- a/test/terra/noise/test_noise_model.py +++ b/test/terra/noise/test_noise_model.py @@ -21,7 +21,7 @@ from qiskit_aer.backends import AerSimulator from qiskit_aer.noise import NoiseModel from qiskit_aer.noise.device.models import _excited_population -from qiskit_aer.noise.errors import PauliError, PauliLindbladError +from qiskit_aer.noise.errors import QuantumError, PauliError, PauliLindbladError from qiskit_aer.noise.errors.standard_errors import amplitude_damping_error from qiskit_aer.noise.errors.standard_errors import kraus_error from qiskit_aer.noise.errors.standard_errors import pauli_error @@ -31,6 +31,8 @@ import qiskit from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit +from qiskit.circuit.library.standard_gates import IGate, XGate +from qiskit.circuit.library.generalized_gates import PauliGate from qiskit.compiler import transpile from qiskit.transpiler import CouplingMap, Target from qiskit.providers import QubitProperties, BackendV2, Options @@ -416,6 +418,25 @@ def test_pauli_lindblad_error_sampling_equiv(self): probs2 = [counts2.get(i, 0) / shots for i in ["00", "01", "10", "11"]] np.testing.assert_allclose(probs1, probs2, atol=5e-2) + def test_from_dict(self): + noise_ops_1q = [((IGate(), [0]), 0.9), ((XGate(), [0]), 0.1)] + + noise_ops_2q = [ + ((PauliGate("II"), [0, 1]), 0.9), + ((PauliGate("IX"), [0, 1]), 0.045), + ((PauliGate("XI"), [0, 1]), 0.045), + ((PauliGate("XX"), [0, 1]), 0.01), + ] + + noise_model = NoiseModel() + with self.assertWarns(DeprecationWarning): + noise_model.add_quantum_error(QuantumError(noise_ops_1q), "h", [0]) + noise_model.add_quantum_error(QuantumError(noise_ops_1q), "h", [1]) + noise_model.add_quantum_error(QuantumError(noise_ops_2q), "cx", [0, 1]) + noise_model.add_quantum_error(QuantumError(noise_ops_2q), "cx", [1, 0]) + deserialized = NoiseModel.from_dict(noise_model.to_dict()) + self.assertEqual(noise_model, deserialized) + if __name__ == "__main__": unittest.main() diff --git a/test/terra/primitives/test_estimator_v2.py b/test/terra/primitives/test_estimator_v2.py index cee5d28243..988d8dc743 100644 --- a/test/terra/primitives/test_estimator_v2.py +++ b/test/terra/primitives/test_estimator_v2.py @@ -18,6 +18,7 @@ from test.terra.common import QiskitAerTestCase import numpy as np +from qiskit import transpile from qiskit.circuit import Parameter, QuantumCircuit from qiskit.circuit.library import RealAmplitudes from qiskit.primitives import StatevectorEstimator @@ -26,6 +27,7 @@ from qiskit.primitives.containers.observables_array import ObservablesArray from qiskit.quantum_info import SparsePauliOp from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager +from qiskit.providers.fake_provider import GenericBackendV2 from qiskit_aer import AerSimulator from qiskit_aer.primitives import EstimatorV2 @@ -407,6 +409,42 @@ def test_metadata(self): {"target_precision": 0.1, "circuit_metadata": qc2.metadata}, ) + def test_truncate(self): + """Test for truncation of save_expval""" + qc = QuantumCircuit(2, 2) + qc.h(0) + qc.cx(0, 1) + qc.append(RealAmplitudes(num_qubits=2, reps=2), [0, 1]) + backend_2 = GenericBackendV2(num_qubits=2) + backend_5 = GenericBackendV2(num_qubits=5) + + qc_2 = transpile(qc, backend_2, optimization_level=0) + qc_5 = transpile(qc, backend_5, optimization_level=0) + + estimator_2 = EstimatorV2.from_backend(backend_2, options=self._options) + estimator_5 = EstimatorV2.from_backend(backend_5, options=self._options) + + H1 = self.observable + H1_2 = H1.apply_layout(qc_2.layout) + H1_5 = H1.apply_layout(qc_5.layout) + theta1 = [0, 1, 1, 2, 3, 5] + + result_2 = estimator_2.run( + [ + (qc_2, [H1_2], [theta1]), + ], + precision=0.01, + ).result() + result_5 = estimator_5.run( + [ + (qc_5, [H1_5], [theta1]), + ], + precision=0.01, + ).result() + self.assertAlmostEqual( + result_5[0].data["evs"][0], result_2[0].data["evs"][0], delta=self._rtol + ) + if __name__ == "__main__": unittest.main()