Skip to content

Commit

Permalink
Merge branch 'main' into bump_0.16
Browse files Browse the repository at this point in the history
  • Loading branch information
doichanj authored Sep 12, 2024
2 parents 0b57765 + dd7a261 commit 14a5c55
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 7 deletions.
102 changes: 101 additions & 1 deletion qiskit_aer/noise/noise_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)):
Expand Down
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions releasenotes/notes/truncate_expval-7fa814c732cca8db.yaml
Original file line number Diff line number Diff line change
@@ -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.
55 changes: 50 additions & 5 deletions src/framework/circuit.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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(
Expand Down
23 changes: 22 additions & 1 deletion test/terra/noise/test_noise_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
38 changes: 38 additions & 0 deletions test/terra/primitives/test_estimator_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()

0 comments on commit 14a5c55

Please sign in to comment.