Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add optional labels to standard gates #175

Merged
merged 11 commits into from
Apr 29, 2019
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ The format is based on `Keep a Changelog`_.

Added
-----
- Add support for labelled gates in noise models (#175).
- Improve efficiency of parallelization with max_memory_mb a new parameter of backend_opts (#61)
- Add optimized mcx, mcy, mcz, mcu1, mcu2, mcu3, gates to QubitVector (#124)
- Add optimized controlled-swap gate to QubitVector
- Add gate-fusion optimization for QasmContoroller, which is enabled by setting fusion_enable=true (#136)

Changed
-------
- Add basis_gates kwarg to NoiseModel init (#175).
- Depreciated "initial_statevector" backend option for QasmSimulator and StatevectorSimulator (#185)
- Rename "chop_threshold" backend option to "zero_threshold" and change default value to 1e-10 (#185).
- Add an optional parameter to `NoiseModel.as_dict()` for returning dictionaries that can be
Expand Down
12 changes: 7 additions & 5 deletions qiskit/providers/aer/noise/errors/errorutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,12 +521,12 @@ def kraus2instructions(kraus_ops, standard_gates, atol=ATOL_DEFAULT):
# Check threshold
if atol < 0:
raise NoiseError("atol cannot be negative")
if atol > 1e-3:
if atol > 1e-5:
raise NoiseError(
"atol value is too large. It should be close to zero.")

# Check CPTP
if not Kraus(kraus_ops).is_cptp():
if not Kraus(kraus_ops).is_cptp(atol=atol):
raise NoiseError("Input Kraus channel is not CPTP.")

# Get number of qubits
Expand All @@ -553,8 +553,8 @@ def kraus2instructions(kraus_ops, standard_gates, atol=ATOL_DEFAULT):
# Get the value of the maximum diagonal element
# of op.H * op for rescaling
prob = abs(max(np.diag(np.conj(np.transpose(mat)).dot(mat))))
if prob > atol:
if abs(prob - 1) > atol:
if prob > 0.0:
if abs(prob - 1) > 0.0:
# Rescale the operator by square root of prob
rescaled_mat = np.array(mat) / np.sqrt(prob)
else:
Expand Down Expand Up @@ -622,4 +622,6 @@ def kraus2instructions(kraus_ops, standard_gates, atol=ATOL_DEFAULT):
]
instructions.append(make_kraus_instruction(non_unitaries, qubits))
probabilities.append(prob_kraus)
return zip(instructions, probabilities)
# Normalize probabilities to account for any rounding errors
probabilities = list(np.array(probabilities) / np.sum(probabilities))
return zip(instructions, probabilities)
61 changes: 43 additions & 18 deletions qiskit/providers/aer/noise/errors/quantum_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import numpy as np

from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit.quantum_info.operators import Kraus, SuperOp
from qiskit.quantum_info.operators import Kraus, SuperOp, Operator
from qiskit.quantum_info.operators.predicates import ATOL_DEFAULT, RTOL_DEFAULT

from ..noiseerror import NoiseError
Expand Down Expand Up @@ -97,7 +97,8 @@ def __init__(self,

# Convert operator subclasses into Kraus list
if issubclass(noise_ops.__class__, BaseOperator) or hasattr(
noise_ops, 'to_channel') or hasattr(noise_ops, 'to_operator'):
noise_ops, 'to_quantumchannel') or hasattr(
noise_ops, 'to_operator'):
noise_ops, other = Kraus(noise_ops)._data
if other is not None:
# A Kraus map with different left and right Kraus matrices
Expand All @@ -113,7 +114,7 @@ def __init__(self,
minimum_qubits = 0
# Add non-zero probability error circuits to the error
for circuit, prob in noise_ops:
if prob > atol:
if prob > 0.0:
self._noise_circuits.append(circuit)
self._noise_probabilities.append(prob)
# Determinine minimum qubit number for error from circuits
Expand Down Expand Up @@ -146,7 +147,10 @@ def __init__(self,
total_probs))
if [p for p in self._noise_probabilities if p < 0]:
raise NoiseError("Probabilities are invalid.")

# Rescale probabilities if their sum is ok to avoid
# accumulation of rounding errors
self._noise_probabilities = list(np.array(self._noise_probabilities) / total_probs)

def __repr__(self):
"""Display QuantumError."""
return "QuantumError({})".format(
Expand Down Expand Up @@ -230,7 +234,7 @@ def ideal(self):
return True
return False

def to_channel(self):
def to_quantumchannel(self):
"""Convet the QuantumError to a SuperOp quantum channel."""
# Initialize as an empty superoperator of the correct size
dim = 2**self.number_of_qubits
Expand All @@ -241,6 +245,10 @@ def to_channel(self):
channel = channel + component
return channel

def to_instruction(self):
"""Convet the QuantumError to a circuit Instruction."""
return self.to_quantumchannel().to_instruction()

def error_term(self, position):
"""
Return a single term from the error.
Expand Down Expand Up @@ -329,7 +337,7 @@ def compose(self, other, front=False):
if (can_combine and self._check_instr(last_name)
and self._check_instr(name)):
combined_circuit[-1] = self._compose_instr(
last_instr, instr)
last_instr, instr, self.number_of_qubits)
else:
# If they cannot be combined append the operation
combined_circuit.append(instr)
Expand All @@ -338,7 +346,8 @@ def compose(self, other, front=False):
combined_circuit.append({'name': 'id', 'qubits': [0]})
# Add circuit
combined_noise_circuits.append(combined_circuit)
noise_ops = self._combine_kraus(zip(combined_noise_circuits, combined_noise_probabilities))
noise_ops = self._combine_kraus(
zip(combined_noise_circuits, combined_noise_probabilities))
return QuantumError(noise_ops)

def power(self, n):
Expand Down Expand Up @@ -469,7 +478,8 @@ def _tensor_product(self, other, reverse=False):
# Add circuit
combined_noise_circuits.append(combined_circuit)
# Now we combine any error circuits containing only Kraus operations
noise_ops = self._combine_kraus(zip(combined_noise_circuits, combined_noise_probabilities))
noise_ops = self._combine_kraus(
zip(combined_noise_circuits, combined_noise_probabilities))
return QuantumError(noise_ops)

@staticmethod
Expand Down Expand Up @@ -550,29 +560,44 @@ def _tensor_instr(instr0, instr1):
return {'name': name, 'qubits': qubits, 'params': params}

@staticmethod
def _compose_instr(instr0, instr1):
def _compose_instr(instr0, instr1, num_qubits):
"""Helper function for compose a kraus with another instruction."""
# If one of the instructions is an identity we only need
# to return the other instruction
if instr0['name'] == 'id':
return instr1
if instr1['name'] == 'id':
return instr0
# Check qubits match
qubits0 = instr0['qubits']
qubits1 = instr1['qubits']
if qubits0 != qubits1:
raise NoiseError("Unitary instructions are on different qubits")
# Convert to ops
op0 = QuantumError._instr2op(instr0)
op1 = QuantumError._instr2op(instr1)
# Check if at least one of the instructions is a channel
# and if so convert to Kraus representation.
# and if so convert both to SuperOp representation
if isinstance(op0,
(SuperOp, Kraus)) or isinstance(op1, (SuperOp, Kraus)):
name = 'kraus'
params = Kraus(SuperOp(op0).compose(op1)).data
op0 = SuperOp(op0)
op1 = SuperOp(op1)
else:
name = 'unitary'
params = [op0.compose(op1).data]
return {'name': name, 'qubits': qubits0, 'params': params}
# Check qubits for compositions
qubits0 = instr0['qubits']
qubits1 = instr1['qubits']
if qubits0 == qubits1:
composed = op0.compose(op1)
qubits = qubits0
else:
# If qubits don't match we compose with total number of qubits
# for the error
if name == 'kraus':
composed = SuperOp(np.eye(4 ** num_qubits))
else:
composed = Operator(np.eye(2 ** num_qubits))
composed.compose(op0, qargs=qubits0).compose(op1, qargs=qubits1)
qubits = list(range(num_qubits))
# Get instruction params
if name == 'kraus':
params = Kraus(composed).data
else:
params = [composed.data]
return {'name': name, 'qubits': qubits, 'params': params}
Loading