Skip to content

Commit

Permalink
Merge branch 'main' into travis-to-actions/1
Browse files Browse the repository at this point in the history
  • Loading branch information
1ucian0 authored Sep 28, 2023
2 parents 0b86af9 + b83abe2 commit 2713aa1
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 39 deletions.
29 changes: 18 additions & 11 deletions qiskit_aer/noise/device/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,15 @@ def basic_device_gate_errors(
)

# Generate custom gate time dict
# Units used in the following computation: ns (time), Hz (frequency), mK (temperature).
custom_times = {}
relax_params = []
if thermal_relaxation:
# If including thermal relaxation errors load
# T1, T2, and frequency values from properties
# T1 [ns], T2 [ns], and frequency [GHz] values from properties
relax_params = thermal_relaxation_values(properties)
# Unit conversion: GHz -> Hz
relax_params = [(t1, t2, freq * 1e9) for t1, t2, freq in relax_params]
# If we are specifying custom gate times include
# them in the custom times dict
if gate_lengths:
Expand Down Expand Up @@ -207,7 +210,7 @@ def basic_device_gate_errors(
# Get relaxation error
if thermal_relaxation:
relax_error = _device_thermal_relaxation_error(
qubits, relax_time, relax_params, temperature, thermal_relaxation
qubits, relax_time, relax_params, temperature
)

# Get depolarizing error channel
Expand Down Expand Up @@ -239,6 +242,8 @@ def _basic_device_target_gate_errors(
Note that, in the resulting error list, non-Gate instructions (e.g. Reset) will have
no gate errors while they may have thermal relaxation errors. Exceptionally,
Measure instruction will have no errors, neither gate errors nor relaxation errors.
Note: Units in use: Time [s], Frequency [Hz], Temperature [mK]
"""
errors = []
for op_name, inst_prop_dic in target.items():
Expand Down Expand Up @@ -329,12 +334,14 @@ def _device_depolarizing_error(qubits, error_param, relax_error=None):
return None


def _device_thermal_relaxation_error(
qubits, gate_time, relax_params, temperature, thermal_relaxation=True
):
"""Construct a thermal_relaxation_error for device"""
def _device_thermal_relaxation_error(qubits, gate_time, relax_params, temperature):
"""Construct a thermal_relaxation_error for device.
Expected units: frequency in relax_params [Hz], temperature [mK].
Note that gate_time and T1/T2 in relax_params must be in the same time unit.
"""
# Check trivial case
if not thermal_relaxation or gate_time is None or gate_time == 0:
if gate_time is None or gate_time == 0:
return None

# Construct a tensor product of single qubit relaxation errors
Expand Down Expand Up @@ -368,7 +375,7 @@ def _truncate_t2_value(t1, t2):


def _excited_population(freq, temperature):
"""Return excited state population from freq [GHz] and temperature [mK]."""
"""Return excited state population from freq [Hz] and temperature [mK]."""
if freq is None or temperature is None:
return 0
population = 0
Expand All @@ -379,10 +386,10 @@ def _excited_population(freq, temperature):
# Boltzman constant kB = 8.617333262e-5 (eV/K)
# Planck constant h = 4.135667696e-15 (eV.s)
# qubit temperature temperatue = T (mK)
# qubit frequency frequency = f (GHz)
# excited state population = 1/(1+exp((h*f*1e9)/(kb*T*1e-3)))
# qubit frequency frequency = f (Hz)
# excited state population = 1/(1+exp((h*f)/(kb*T*1e-3)))
# See e.g. Phys. Rev. Lett. 114, 240501 (2015).
exp_param = exp((47.99243 * freq) / abs(temperature))
exp_param = exp((47.99243 * 1e-9 * freq) / abs(temperature))
population = 1 / (1 + exp_param)
if temperature < 0:
# negative temperate implies |1> is thermal ground
Expand Down
30 changes: 12 additions & 18 deletions qiskit_aer/primitives/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,20 +382,16 @@ def _compute_with_approximation(
self._transpile_circuits(circuits)
experiment_manager = _ExperimentManager()
for i, j, value in zip(circuits, observables, parameter_values):
self._validate_parameter_length(value, i)
if (i, j) in experiment_manager.keys:
self._validate_parameter_length(value, i)
experiment_manager.append(
key=(i, j),
parameter_bind=dict(zip(self._parameters[i], value)),
)
key_index = experiment_manager.keys.index((i, j))
circuit = experiment_manager.experiment_circuits[key_index]
else:
self._validate_parameter_length(value, i)
circuit = (
self._circuits[i].copy()
if self._skip_transpilation
else self._transpiled_circuits[i].copy()
)

observable = self._observables[j]
if shots is None:
circuit.save_expectation_value(observable, self._layouts[i])
Expand All @@ -404,11 +400,11 @@ def _compute_with_approximation(
circuit.save_expectation_value(
pauli, self._layouts[i], label=str(term_ind)
)
experiment_manager.append(
key=(i, j),
parameter_bind=dict(zip(self._parameters[i], value)),
experiment_circuit=circuit,
)
experiment_manager.append(
key=(i, j),
parameter_bind=dict(zip(self._parameters[i], value)),
experiment_circuit=circuit,
)

self._cache[key] = experiment_manager
result = self._backend.run(
Expand Down Expand Up @@ -616,24 +612,22 @@ def __len__(self):
@property
def experiment_indices(self):
"""indices of experiments"""
return sum(self._input_indices, [])
return np.argsort(sum(self._input_indices, [])).tolist()

def append(
self,
key: tuple[int, int],
parameter_bind: dict[ParameterExpression, float],
experiment_circuit: QuantumCircuit | None = None,
experiment_circuit: QuantumCircuit,
):
"""append experiments"""
if experiment_circuit is not None:
self.experiment_circuits.append(experiment_circuit)

if key in self.keys:
if key in self.keys and parameter_bind:
key_index = self.keys.index(key)
for k, vs in self.parameter_binds[key_index].items():
vs.append(parameter_bind[k])
self._input_indices[key_index].append(self._num_experiment)
else:
self.experiment_circuits.append(experiment_circuit)
self.keys.append(key)
self.parameter_binds.append({k: [v] for k, v in parameter_bind.items()})
self._input_indices.append([self._num_experiment])
Expand Down
57 changes: 50 additions & 7 deletions qiskit_aer/primitives/sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@

from collections.abc import Sequence

from qiskit.circuit import QuantumCircuit
import numpy as np
from qiskit.circuit import ParameterExpression, QuantumCircuit
from qiskit.compiler import transpile
from qiskit.exceptions import QiskitError
from qiskit.primitives import BaseSampler, SamplerResult
Expand Down Expand Up @@ -88,25 +89,30 @@ def _call(
is_shots_none = "shots" in run_options and run_options["shots"] is None
self._transpile(circuits, is_shots_none)

experiments = []
parameter_binds = []
experiment_manager = _ExperimentManager()
for i, value in zip(circuits, parameter_values):
if len(value) != len(self._parameters[i]):
raise QiskitError(
f"The number of values ({len(value)}) does not match "
f"the number of parameters ({len(self._parameters[i])})."
)
parameter_binds.append({k: [v] for k, v in zip(self._parameters[i], value)})
experiments.append(self._transpiled_circuits[(i, is_shots_none)])

experiment_manager.append(
key=i,
parameter_bind=dict(zip(self._parameters[i], value)),
experiment_circuit=self._transpiled_circuits[(i, is_shots_none)],
)

result = self._backend.run(
experiments, parameter_binds=parameter_binds, **run_options
experiment_manager.experiment_circuits,
parameter_binds=experiment_manager.parameter_binds,
**run_options,
).result()

# Postprocessing
metadata = []
quasis = []
for i in range(len(experiments)):
for i in experiment_manager.experiment_indices:
if is_shots_none:
probabilities = result.data(i)["probabilities"]
num_qubits = result.results[i].metadata["num_qubits"]
Expand Down Expand Up @@ -186,3 +192,40 @@ def _transpile(self, circuit_indices: Sequence[int], is_shots_none: bool):
)
for i, circuit in zip(to_handle, circuits):
self._transpiled_circuits[(i, is_shots_none)] = circuit


class _ExperimentManager:
def __init__(self):
self.keys: list[int] = []
self.experiment_circuits: list[QuantumCircuit] = []
self.parameter_binds: list[dict[ParameterExpression, list[float]]] = []
self._input_indices: list[list[int]] = []
self._num_experiment: int = 0

def __len__(self):
return self._num_experiment

@property
def experiment_indices(self):
"""indices of experiments"""
return np.argsort(sum(self._input_indices, [])).tolist()

def append(
self,
key: tuple[int, int],
parameter_bind: dict[ParameterExpression, float],
experiment_circuit: QuantumCircuit,
):
"""append experiments"""
if parameter_bind and key in self.keys:
key_index = self.keys.index(key)
for k, vs in self.parameter_binds[key_index].items():
vs.append(parameter_bind[k])
self._input_indices[key_index].append(self._num_experiment)
else:
self.experiment_circuits.append(experiment_circuit)
self.keys.append(key)
self.parameter_binds.append({k: [v] for k, v in parameter_bind.items()})
self._input_indices.append([self._num_experiment])

self._num_experiment += 1
5 changes: 5 additions & 0 deletions releasenotes/notes/estimator-order-bug-a341d82075f47046.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
upgrade:
- |
Fixed a bug that caused results to be incorrectly ordered or errors in
:class:`~.Estimator` with ``approximation=True``.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
Fixed a bug where :meth:`~.NoiseModel.from_backend` with ``BackendV2`` and non-zero ``temperature``
produces relaxation noises with incorrect excitation population.
Fixed `#1937 <https://github.com/Qiskit/qiskit-aer/issues/1937>`__.
5 changes: 5 additions & 0 deletions releasenotes/notes/sampler-performance-81e1649ec4657aad.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
upgrade:
- |
Improved performance when the same circuits and multiple parameters are passed to
:class:`~.Sampler`.
19 changes: 17 additions & 2 deletions test/terra/noise/test_device_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
"""
Tests for utility functions to create device noise model.
"""

import numpy as np
from test.terra.common import QiskitAerTestCase

from qiskit.providers import QubitProperties
from qiskit.circuit.library.standard_gates import XGate
from qiskit.providers.fake_provider import FakeNairobi, FakeNairobiV2
from qiskit.transpiler import Target, QubitProperties, InstructionProperties
from qiskit_aer.noise.device.models import basic_device_gate_errors
from qiskit_aer.noise.errors.standard_errors import thermal_relaxation_error


class TestDeviceNoiseModel(QiskitAerTestCase):
Expand Down Expand Up @@ -70,3 +72,16 @@ def test_basic_device_gate_errors_from_target_with_no_t2_value(self):
target = FakeNairobiV2().target
target.qubit_properties[0].t2 = None
basic_device_gate_errors(target=target)

def test_non_zero_temperature(self):
"""Test if non-zero excited_state_population is obtained when positive temperature is supplied.
See https://github.com/Qiskit/qiskit-aer/issues/1937 for the details."""
t1, t2, frequency, duration = 1e-4, 1e-4, 5e9, 5e-8
target = Target(qubit_properties=[QubitProperties(t1=t1, t2=t2, frequency=frequency)])
target.add_instruction(XGate(), {(0,): InstructionProperties(duration=duration)})
errors = basic_device_gate_errors(target=target, gate_error=False, temperature=100)
_, _, x_error = errors[0]
no_excitation_error = thermal_relaxation_error(t1, t2, duration, excited_state_population=0)
x_error_matrix = x_error.to_quantumchannel().data
no_excitation_error_matrix = no_excitation_error.to_quantumchannel().data
self.assertFalse(np.allclose(x_error_matrix, no_excitation_error_matrix))
17 changes: 16 additions & 1 deletion test/terra/primitives/test_estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import numpy as np
from ddt import data, ddt
from qiskit.circuit import QuantumCircuit
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.circuit.library import RealAmplitudes
from qiskit.exceptions import QiskitError
from qiskit.opflow import PauliSumOp
Expand Down Expand Up @@ -308,6 +308,21 @@ def test_warn_shots_none_without_approximation(self):
np.testing.assert_allclose(result.values, [-1.313831587508902])
self.assertIsInstance(result.metadata[0]["variance"], float)

def test_result_order(self):
"""Test to validate the order."""
qc1 = QuantumCircuit(1)
qc1.measure_all()

param = Parameter("a")
qc2 = QuantumCircuit(1)
qc2.ry(np.pi / 2 * param, 0)
qc2.measure_all()

estimator = Estimator(approximation=True)
job = estimator.run([qc1, qc2, qc1, qc1, qc2], ["Z"] * 5, [[], [1], [], [], [1]])
result = job.result()
np.testing.assert_allclose(result.values, [1, 0, 1, 1, 0], atol=1e-10)


if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ commands =
[testenv:lint]
envdir = .tox/lint
basepython = python3
allowlist_externals = sh
commands =
sh tools/clang-format.sh --Werror -n
black --check {posargs} qiskit_aer test tools setup.py
Expand Down

0 comments on commit 2713aa1

Please sign in to comment.