From 68edb62b96ca8ca717c6304fb445a93d4233475b Mon Sep 17 00:00:00 2001 From: cjwood Date: Fri, 14 Dec 2018 15:54:43 -0500 Subject: [PATCH] combine device noise models into single model --- qiskit_aer/noise/__init__.py | 61 ++-- qiskit_aer/noise/device/__init__.py | 5 +- qiskit_aer/noise/device/models.py | 418 +++++++++++++++++--------- qiskit_aer/noise/device/parameters.py | 33 ++ 4 files changed, 345 insertions(+), 172 deletions(-) diff --git a/qiskit_aer/noise/__init__.py b/qiskit_aer/noise/__init__.py index 02d145923..bdf339374 100644 --- a/qiskit_aer/noise/__init__.py +++ b/qiskit_aer/noise/__init__.py @@ -11,7 +11,7 @@ This module contains classes and functions to build a noise model for simulating a Qiskit quantum circuit in the presence of errors. Noise Models ----------------------- +------------ Noise models for a noisy simulator are represented using the `NoiseModel` class. This can be used to generate a custom noise model, or an automatic noise model can be generated for a device using built in functions. @@ -21,20 +21,14 @@ Automatically Generated Noise Models Approximate noise models for a hardware device can be generated from the device properties using the functions from the `device` submodule. - * `device.depolarizing_noise_model`: Generates a noise model based on - one-qubit depolarizing errors acting after X90 pulses during u1, u2, - and u3 gates, two-qubit depolarizing errors acting after cx gates, - and readout errors acting after measurement. The error parameters - are tuned for each individual qubit based on 1 and 2-qubit error - parameters from the device backend properties. - - * `device.thermal_relaxation_noise_model`: Generates a noise mode - based on one-qubit thermal relaxation errors sacting after X90 - pulses during u1, u2, and u3 gates, acting on both qubits after cx - gates, and readout errors acting after measurement. The error - parameters are tuned for each individual qubit based on the T_1, - T_2, and single qubit gate time parameters from the device backend - properties. + Basic device noise model + ------------------------ + Generates a noise mode based on 1 and 2 qubit gate errors consisting of + a depolarizing error followed by a thermal relaxation error, and readout + errors on measurement outcomes. The error parameters are tuned for each + individual qubit based on the T_1, T_2, frequency and readout error + parameters for each qubit, and the gate error and gate time parameters + for each gate obtained from the device backend properties. Custom Noise Models ------------------- @@ -42,26 +36,31 @@ Custom noise models may be constructed by adding errors to a NoiseModel object. Errors are represented using by the `QuantumError` and `ReadoutError` classes from the `noise.errors` module: - * `QuantumError`: Errors that affect the quantum state during a - simulation. They may be applied after specific circuit gates or - reset operations, or before measure operations of qubits. + Quantum errors + -------------- + Errors that affect the quantum state during a simulation. They may be + applied after specific circuit gates or reset operations, or before + measure operations of qubits. - * `ReadoutError`: Errors that apply to classical bit registers - after a measurement. They do not change the quantum state of the - system, only the recorded classical measurement outcome. + ReadoutError + ------------ + Errors that apply to classical bit registers after a measurement. They + do not change the quantum state of the system, only the recorded + classical measurement outcome. Helper functions exist for generating standard quantum error channels in -the `noise.errors` module. These functions are: +the `noise.errors` module. These allow simple generation of the follow +canonical types of quantum errors: - * `kraus_error` - * `mixed_unitary_error` - * `coherent_unitary_error` - * `pauli_error` - * `depolarizing_error` - * `thermal_relaxation_error` - * `phase_amplitude_damping_error` - * `amplitude_damping_error` - * `phase_damping_error` + Kraus error + Mixed unitary error + Coherent unitary error + Pauli error + Depolarizing error + Thermal relaxation error + Amplitude damping error + Phase damping error + Combined phase and amplitude damping error """ from .noise_model import NoiseModel diff --git a/qiskit_aer/noise/device/__init__.py b/qiskit_aer/noise/device/__init__.py index af8a4a258..2224e9918 100644 --- a/qiskit_aer/noise/device/__init__.py +++ b/qiskit_aer/noise/device/__init__.py @@ -7,6 +7,7 @@ """Device noise models module Qiskit Aer.""" -from .models import depolarizing_noise_model -from .models import thermal_relaxation_noise_model +from .models import basic_device_noise_model +from .models import basic_device_readout_errors +from .models import basic_device_gate_errors from . import parameters diff --git a/qiskit_aer/noise/device/models.py b/qiskit_aer/noise/device/models.py index 435e59ca3..62f802980 100644 --- a/qiskit_aer/noise/device/models.py +++ b/qiskit_aer/noise/device/models.py @@ -12,39 +12,60 @@ Simplified noise models for devices backends. from numpy import inf, exp from .parameters import readout_error_values -from .parameters import gate_time_values -from .parameters import gate_error_values +from .parameters import gate_param_values from .parameters import thermal_relaxation_values +from ..noiseerror import NoiseError from ..noise_model import NoiseModel from ..errors.readout_error import ReadoutError from ..errors.standard_errors import depolarizing_error from ..errors.standard_errors import thermal_relaxation_error -def depolarizing_noise_model(properties, +def basic_device_noise_model(properties, readout_error=True, + thermal_relaxation=True, + temperature=0, + gate_times=None, standard_gates=True): - """Generae a depolarizing noise model from device backend properties. + """Approximate device noise model derived from backend properties. Params: properties (BackendProperties): backend properties - readout_error (Bool): Include readout errors in model - (Default: True) + readout_errors (Bool): Include readout errors in model + (Default: True). + thermal_relaxation (Bool): Include thermal relaxation errors + (Default: True). + temperature (double): qubit temperature in milli-Kelvin (mK) for + thermal relaxation errors (Default: 0). + gate_times (list): Custom gate times for thermal relaxation errors. + Used to extend or override the gate times in + the backend properties (Default: None). standard_gates (bool): If true return errors as standard qobj gates. If false return as unitary qobj instructions (Default: True) Returns: - NoiseModel: An approximate noise model for the backend. + NoiseModel: An approximate noise model for the device backend. Additional Information: - The noise model will consist: - * Depolarizing errors on gates defined be defined in - `BackendProperties.gates`. - * Single qubit readout errors on measurements if `readout_errors` - is set to True. + The noise model includes the following errors: + + If readout_error is True: + * Single qubit readout errors on measurements. + + If thermal_relaxation is True: + * Single-qubit gate errors consisting of a depolarizing error + followed by a thermal relaxation error for the qubit the gate + acts on. + * Two-qubit gate errors consisting of a 2-qubit depolarizing + error followed by single qubit thermal relaxation errors for + all qubits participating in the gate. + + Else if thermal_relaxation is False: + * Single-qubit depolarizing gate errors. + * Multi-qubit depolarizing gate errors. For best practice in simulating a backend make sure that the circuit is compiled using the set of basis gates in the noise @@ -52,43 +73,6 @@ def depolarizing_noise_model(properties, `basis_gates = noise_model.basis_gates` and using the device coupling map with: `coupling_map = backend.configuration().coupling_map` - """ - noise_model = NoiseModel() - - # Add single-qubit readout errors - if readout_error: - for qubits, error in device_readout_errors(properties): - noise_model.add_readout_error(error, qubits) - - # Add depolarizing gate errors - gate_errors = device_depolarizing_errors(properties, - standard_gates=standard_gates) - for name, qubits, error in gate_errors: - noise_model.add_quantum_error(error, name, qubits) - - return noise_model - - -def thermal_relaxation_noise_model(properties, - readout_error=True, - temperature=0, - gate_times=None): - """Thermal relaxation noise model derived from backend properties. - - Params: - properties (BackendProperties): backend properties - readout_errors (Bool): Include readout errors in model - (Default: True) - temperature (double): qubit temperature in milli-Kelvin (mK) - (Default: 0) - gate_times (list): Override device gate times with custom - values. If None use gate times from - backend properties. (Default: None) - - Returns: - NoiseModel: An approximate noise model for the device backend. - - Additional Information: Secifying custom gate times: @@ -103,62 +87,28 @@ def thermal_relaxation_noise_model(properties, the backend properties, the `gate_times` value will override the gate time value from the backend properties. If non-default values are used gate_times should be a list - - The noise model will consist: - - * Single qubit readout errors on measurements. - * Single-qubit relaxation errors on all qubits participating in - a noisy quantum gate. The relaxation strength is determined by - the individual qubit T1 and T2 values and the gate_time of the - gate as defined in `BackendProperties.gates`. - - For best practice in simulating a backend make sure that the - circuit is compiled using the set of basis gates in the noise - module by setting: - `basis_gates = noise_model.basis_gates` - and using the device coupling map with: - `coupling_map = backend.configuration().coupling_map` """ noise_model = NoiseModel() # Add single-qubit readout errors if readout_error: - for qubits, error in device_readout_errors(properties): + for qubits, error in basic_device_readout_errors(properties): noise_model.add_readout_error(error, qubits) - # Add depolarizing gate errors - gate_errors = device_thermal_relaxation_errors(properties, - temperature=temperature, - gate_times=gate_times) + # Add gate errors + gate_errors = basic_device_gate_errors(properties, + thermal_relaxation=thermal_relaxation, + gate_times=gate_times, + temperature=temperature, + standard_gates=standard_gates) for name, qubits, error in gate_errors: noise_model.add_quantum_error(error, name, qubits) return noise_model -def device_depolarizing_errors(properties, standard_gates=True): - """Get depolarizing noise quantum error objects for backend gates - - Args: - properties (BackendProperties): device backend properties - standard_gates (bool): If true return errors as standard - qobj gates. If false return as unitary - qobj instructions (Default: True) - - Returns: - list: A list of tuples (name, qubits, error). - """ - errors = [] - for name, qubits, value in gate_error_values(properties): - if value is not None and value > 0: - error = depolarizing_error(value, len(qubits), - standard_gates=standard_gates) - errors.append((name, qubits, error)) - return errors - - -def device_readout_errors(properties): +def basic_device_readout_errors(properties): """Get readout error objects for each qubit from backend properties Args: @@ -176,7 +126,9 @@ def device_readout_errors(properties): return errors -def device_thermal_relaxation_errors(properties, temperature=0, gate_times=None): +def basic_device_gate_errors(properties, thermal_relaxation=True, + gate_times=None, temperature=0, + standard_gates=True): """Get depolarizing noise quantum error objects for backend gates Args: @@ -193,55 +145,243 @@ def device_thermal_relaxation_errors(properties, temperature=0, gate_times=None) Additional Information: If non-default values are used gate_times should be a list - of tuples (name, value) where name is the gate name string, - and value is the gate time in nanoseconds. + of tuples (name, qubits, value) where name is the gate name string, + qubits is a list of qubits or None to apply gate time to this + gate one any set of qubits, and value is the gate time in + nanoseconds. """ # Generate custom gate time dict custom_times = {} - if gate_times: - custom_times = {name: value for name, value in gate_times} - else: - custom_times = {} - # Append device gate times to custom gate times - device_gate_times = gate_time_values(properties) - relax_values = thermal_relaxation_values(properties) - errors = [] - for name, qubits, value in device_gate_times: - if name in custom_times: - gate_time = custom_times[name] - else: - gate_time = value - if gate_time is not None and gate_time > 0: - # convert gate time to same units as T1 and T2 (microseconds) - gate_time = gate_time / 1000 - # Construct a tensor product of single qubit relaxation errors - # for any multi qubit gates - first = True - error = None - for qubit in reversed(qubits): - t1, t2, freq = relax_values[qubit] - population = 0 - if freq != inf and temperature != 0: - # Compute the excited state population from qubit - # frequency and temperature - # Boltzman constant kB = 6.62607015e-34 (eV/K) - # Planck constant h = 6.62607015e-34 (eV.s) - # qubit temperature temperatue = T (mK) - # qubit frequency frequency = f (GHz) - # excited state population = 1/(1+exp((2hf*1e9)/(kbT*1e-3))) - exp_param = exp((95.9849 * freq) / abs(temperature)) - population = 1 / (1 + exp_param) - if temperature < 0: - # negative temperate implies |1> is thermal ground - population = 1 - population - if first: - error = thermal_relaxation_error(t1, t2, gate_time, - population) - first = False + if thermal_relaxation: + # If including thermal relaxation errors load + # T1, T2, and frequency values from properties + relax_params = thermal_relaxation_values(properties) + # If we are specifying custom gate times include + # them in the custom times dict + if gate_times: + for name, qubits, value in gate_times: + if name in custom_times: + custom_times[name].append((qubits, value)) else: - single = thermal_relaxation_error(t1, t2, gate_time, - population) - error = error.kron(single) - if error is not None: - errors.append((name, qubits, error)) + custom_times[name] = [(qubits, value)] + # Get the device gate parameters from properties + device_gate_params = gate_param_values(properties) + + # Construct quantum errors + errors = [] + for name, qubits, gate_time, gate_error in device_gate_params: + # Check for custom gate time + relax_time = gate_time + # Override with custom value + if name in custom_times: + filtered = [val for q, val in custom_times[name] + if q is None or q == qubits] + if filtered: + # get first value + relax_time = filtered[0] + # Get depolarizing error channel + depol_error = _device_depolarizing_error(qubits, gate_error, + relax_time, + relax_params, + temperature, + thermal_relaxation, + standard_gates) + # Get relaxation error + relax_error = _device_thermal_relaxation_error(qubits, relax_time, + relax_params, + temperature, + thermal_relaxation) + # Combine errors + if depol_error is None and relax_error is None: + # No error for this gate + pass + elif depol_error is not None and relax_error is None: + # Append only the depolarizing error + errors.append((name, qubits, depol_error)) + # Append only the relaxation error + elif relax_error is not None and depol_error is None: + errors.append((name, qubits, relax_error)) + else: + # Append a combined error of depolarizing error + # followed by a relaxation error + combined_error = depol_error.compose(relax_error) + errors.append((name, qubits, combined_error)) return errors + + +def _device_depolarizing_error(qubits, gate_error, gate_time, relax_params, + temperature, thermal_relaxation=True, + standard_gates=True): + """Construct a depolarizing_error for device""" + error = None + if not thermal_relaxation: + # Model gate error entirely as depolarizing error + p_depol = _depol_error_value_one_qubit(gate_error) + else: + # Model gate error as thermal relaxation and depolarizing + # error. + # Get depolarizing probability + if len(qubits) == 1: + t1, t2, _ = relax_params[qubits[0]] + p_depol = _depol_error_value_one_qubit(gate_error, + gate_time, + t1=t1, t2=t2) + elif len(qubits) == 2: + q0_t1, q0_t2, _ = relax_params[qubits[0]] + q1_t1, q1_t2, _ = relax_params[qubits[1]] + p_depol = _depol_error_value_two_qubit(gate_error, + gate_time, + qubit0_t1=q0_t1, + qubit0_t2=q0_t2, + qubit1_t1=q1_t1, + qubit1_t2=q1_t2) + else: + raise NoiseError("Device noise model only supports" + + "1 and 2-qubit gates when using " + "thermal_relaxation=True.") + if p_depol > 0: + error = depolarizing_error(p_depol, len(qubits), + standard_gates=standard_gates) + return error + + +def _device_thermal_relaxation_error(qubits, gate_time, relax_params, + temperature, thermal_relaxation=True): + """Construct a thermal_relaxation_error for device""" + # Check trivial case + if not thermal_relaxation or gate_time is None or gate_time == 0: + return None + # convert gate time to same units as T1 and T2 (microseconds) + gate_time = gate_time / 1000 + # Construct a tensor product of single qubit relaxation errors + # for any multi qubit gates + first = True + error = None + for qubit in reversed(qubits): + t1, t2, freq = relax_params[qubit] + population = _excited_population(freq, temperature) + if first: + error = thermal_relaxation_error(t1, t2, gate_time, + population) + first = False + else: + single = thermal_relaxation_error(t1, t2, gate_time, + population) + error = error.kron(single) + return error + + +def _excited_population(freq, temperature): + """Return excited state population""" + population = 0 + if freq != inf and temperature != 0: + # Compute the excited state population from qubit + # frequency and temperature + # Boltzman constant kB = 6.62607015e-34 (eV/K) + # Planck constant h = 6.62607015e-34 (eV.s) + # qubit temperature temperatue = T (mK) + # qubit frequency frequency = f (GHz) + # excited state population = 1/(1+exp((2hf*1e9)/(kbT*1e-3))) + exp_param = exp((95.9849 * freq) / abs(temperature)) + population = 1 / (1 + exp_param) + if temperature < 0: + # negative temperate implies |1> is thermal ground + population = 1 - population + return population + + +def _depol_error_value_one_qubit(gate_error, gate_time=0, t1=inf, t2=inf): + """Return 2-qubit depolarizing channel probability for device model""" + # Check trivial case where there is no gate error + if gate_error is None: + return None + if gate_error == 0: + return 0 + + # Check t1 and t2 are valid + if t1 <= 0: + raise NoiseError("Invalid T_1 relaxation time parameter: T_1 <= 0.") + if t2 <= 0: + raise NoiseError("Invalid T_2 relaxation time parameter: T_2 <= 0.") + if t2 - 2 * t1 > 0: + raise NoiseError("Invalid T_2 relaxation time parameter: T_2 greater than 2 * T_1.") + + # If T1 or T2 we have only a depolarizing error model + # in this case p_depol = dim * gate_error / (dim - 1) + # with dim = 2 for 1-qubit + if gate_time is None: + gate_time = 0 + if gate_time == 0 or (t1 == inf and t2 == inf): + if gate_error is not None and gate_error > 0: + return 2 * gate_error + else: + return 0 + + # Otherwise we calculate the depolarizing error probability to account + # for the difference between the relaxation error and gate error + if t1 == inf: + par1 = 1 + else: + par1 = exp(-gate_time / t1) + if t2 == inf: + par2 = 1 + else: + par2 = exp(-gate_time / t2) + p_depol = 1 + 3 * (2 * gate_error - 1) / (par1 + 2 * par2) + return p_depol + + +def _depol_error_value_two_qubit(gate_error, gate_time=0, + qubit0_t1=inf, qubit0_t2=inf, + qubit1_t1=inf, qubit1_t2=inf): + """Return 2-qubit depolarizing channel probability for device model""" + # Check trivial case where there is no gate error + if gate_error is None: + return None + if gate_error == 0: + return 0 + + # Check t1 and t2 are valid + if qubit0_t1 <= 0 or qubit1_t1 <= 0: + raise NoiseError("Invalid T_1 relaxation time parameter: T_1 <= 0.") + if qubit0_t2 <= 0 or qubit1_t2 <= 0: + raise NoiseError("Invalid T_2 relaxation time parameter: T_2 <= 0.") + if qubit0_t2 - 2 * qubit0_t1 > 0 or qubit1_t2 - 2 * qubit1_t1 > 0: + raise NoiseError("Invalid T_2 relaxation time parameter: T_2 greater than 2 * T_1.") + + # If T1 or T2 we have only a depolarizing error model + # in this case p_depol = dim * gate_error / (dim - 1) + # with dim = 4 for 2-qubits + if gate_time is None: + gate_time = 0 + if gate_time == 0 or (qubit0_t1 == inf and qubit0_t2 == inf and + qubit1_t1 == inf and qubit1_t2 == inf): + if gate_error is not None and gate_error > 0: + return 4 * gate_error / 3 + else: + return 0 + + # Otherwise we calculate the depolarizing error probability to account + # for the difference between the relaxation error and gate error + if qubit0_t1 == inf: + q0_par1 = 1 + else: + q0_par1 = exp(-gate_time / qubit0_t1) + if qubit0_t2 == inf: + q0_par2 = 1 + else: + q0_par2 = exp(-gate_time / qubit0_t2) + if qubit1_t1 == inf: + q1_par1 = 1 + else: + q1_par1 = exp(-gate_time / qubit1_t1) + if qubit1_t2 == inf: + q1_par2 = 1 + else: + q1_par2 = exp(-gate_time / qubit1_t2) + denom = (q0_par1 + q1_par1 + q0_par1 * q1_par1 + + 4 * q0_par2 * q1_par2 + + 2 * (q0_par2 + q1_par2) + + 2 * (q1_par1 * q0_par2 + q0_par1 * q1_par2)) + p_depol = 1 + (5 / 3) * (4 * gate_error - 3) / denom + return p_depol diff --git a/qiskit_aer/noise/device/parameters.py b/qiskit_aer/noise/device/parameters.py index 0d498739f..f90892f78 100644 --- a/qiskit_aer/noise/device/parameters.py +++ b/qiskit_aer/noise/device/parameters.py @@ -17,6 +17,39 @@ _NANOSECOND_UNITS = {'s': 1e9, 'ms': 1e6, 'µs': 1e3, 'us': 1e3, 'ns': 1} _GHZ_UNITS = {'Hz': 1e-9, 'KHz': 1e-6, 'MHz': 1e-3, 'GHz': 1, 'THz': 1e3} +def gate_param_values(properties): + """Get gate error values for backend gate from backend properties + + Args: + properties (BackendProperties): device backend properties + + Returns: + list: A list of tuples (name, qubits, time, error). If gate + error or gate_time information is not available None will be + returned for value. + """ + values = [] + for gate in properties.gates: + name = gate.gate + qubits = gate.qubits + # Check for gate time information + gate_time = None # default value + time_param = _check_for_item(gate.parameters, 'gate_time') + if hasattr(time_param, 'value'): + gate_time = time_param.value + if hasattr(time_param, 'unit'): + # Convert gate time to ns + gate_time *= _NANOSECOND_UNITS.get(time_param.unit, 1) + # Check for gate error information + gate_error = None # default value + error_param = _check_for_item(gate.parameters, 'gate_error') + if hasattr(error_param, 'value'): + gate_error = error_param.value + values.append((name, qubits, gate_time, gate_error)) + + return values + + def gate_error_values(properties): """Get gate error values for backend gate from backend properties