def to_instruction(self): """Convert to a Kraus or UnitaryGate circuit instruction. If the channel is unitary it will be added as a unitary gate, otherwise it will be added as a kraus simulator instruction. Returns: qiskit.circuit.Instruction: A kraus instruction for the channel. Raises: QiskitError: if input data is not an N-qubit CPTP quantum channel. """ from qiskit.circuit.instruction import Instruction # Check if input is an N-qubit CPTP channel. num_qubits = int(np.log2(self._input_dim)) if self._input_dim != self._output_dim or 2**num_qubits != self._input_dim: raise QiskitError( 'Cannot convert QuantumChannel to Instruction: channel is not an N-qubit channel.' ) if not self.is_cptp(): raise QiskitError( 'Cannot convert QuantumChannel to Instruction: channel is not CPTP.' ) # Next we convert to the Kraus representation. Since channel is CPTP we know # that there is only a single set of Kraus operators kraus, _ = _to_kraus(self._channel_rep, self._data, *self.dim) # If we only have a single Kraus operator then the channel is # a unitary channel so can be converted to a UnitaryGate. We do this by # converting to an Operator and using its to_instruction method if len(kraus) == 1: return Operator(kraus[0]).to_instruction() return Instruction('kraus', num_qubits, 0, kraus)
def __init__(self, data, input_dims=None, output_dims=None): """Initialize a quantum channel Kraus operator. Args: data (QuantumCircuit or Instruction or BaseOperator or matrix): data to initialize superoperator. input_dims (tuple): the input subsystem dimensions. [Default: None] output_dims (tuple): the output subsystem dimensions. [Default: None] Raises: QiskitError: if input data cannot be initialized as a a list of Kraus matrices. Additional Information: If the input or output dimensions are None, they will be automatically determined from the input data. If the input data is a list of Numpy arrays of shape (2**N, 2**N) qubit systems will be used. If the input does not correspond to an N-qubit channel, it will assign a single subsystem with dimension specified by the shape of the input. """ # If the input is a list or tuple we assume it is a list of Kraus # matrices, if it is a numpy array we assume that it is a single Kraus # operator if isinstance(data, (list, tuple, np.ndarray)): # Check if it is a single unitary matrix A for channel: # E(rho) = A * rho * A^\dagger if isinstance(data, np.ndarray) or np.array(data).ndim == 2: # Convert single Kraus op to general Kraus pair kraus = ([np.asarray(data, dtype=complex)], None) shape = kraus[0][0].shape # Check if single Kraus set [A_i] for channel: # E(rho) = sum_i A_i * rho * A_i^dagger elif isinstance(data, list) and len(data) > 0: # Get dimensions from first Kraus op kraus = [np.asarray(data[0], dtype=complex)] shape = kraus[0].shape # Iterate over remaining ops and check they are same shape for i in data[1:]: op = np.asarray(i, dtype=complex) if op.shape != shape: raise QiskitError( "Kraus operators are different dimensions.") kraus.append(op) # Convert single Kraus set to general Kraus pair kraus = (kraus, None) # Check if generalized Kraus set ([A_i], [B_i]) for channel: # E(rho) = sum_i A_i * rho * B_i^dagger elif isinstance(data, tuple) and len(data) == 2 and len(data[0]) > 0: kraus_left = [np.asarray(data[0][0], dtype=complex)] shape = kraus_left[0].shape for i in data[0][1:]: op = np.asarray(i, dtype=complex) if op.shape != shape: raise QiskitError( "Kraus operators are different dimensions.") kraus_left.append(op) if data[1] is None: kraus = (kraus_left, None) else: kraus_right = [] for i in data[1]: op = np.asarray(i, dtype=complex) if op.shape != shape: raise QiskitError( "Kraus operators are different dimensions.") kraus_right.append(op) kraus = (kraus_left, kraus_right) else: raise QiskitError("Invalid input for Kraus channel.") else: # Otherwise we initialize by conversion from another Qiskit # object into the QuantumChannel. if isinstance(data, (QuantumCircuit, Instruction)): # If the input is a Terra QuantumCircuit or Instruction we # convert it to a SuperOp data = SuperOp._init_instruction(data) else: # We use the QuantumChannel init transform to initialize # other objects into a QuantumChannel or Operator object. data = self._init_transformer(data) input_dim, output_dim = data.dim # Now that the input is an operator we convert it to a Kraus rep = getattr(data, '_channel_rep', 'Operator') kraus = _to_kraus(rep, data._data, input_dim, output_dim) if input_dims is None: input_dims = data.input_dims() if output_dims is None: output_dims = data.output_dims() output_dim, input_dim = kraus[0][0].shape # Check and format input and output dimensions input_dims = self._automatic_dims(input_dims, input_dim) output_dims = self._automatic_dims(output_dims, output_dim) # Initialize either single or general Kraus if kraus[1] is None or np.allclose(kraus[0], kraus[1]): # Standard Kraus map super().__init__((kraus[0], None), input_dims, output_dims, 'Kraus') else: # General (non-CPTP) Kraus map super().__init__(kraus, input_dims, output_dims, 'Kraus')
def __init__(self, data, input_dims=None, output_dims=None): """Initialize a Kraus quantum channel operator.""" if issubclass(data.__class__, BaseOperator): # If not a channel we use `to_operator` method to get # the unitary-representation matrix for input if not issubclass(data.__class__, QuantumChannel): data = data.to_operator() input_dim, output_dim = data.dim kraus = _to_kraus(data.rep, data._data, input_dim, output_dim) if input_dims is None: input_dims = data.input_dims() if output_dims is None: output_dims = data.output_dims() elif isinstance(data, (list, tuple, np.ndarray)): # Check if it is a single unitary matrix A for channel: # E(rho) = A * rho * A^\dagger if isinstance(data, np.ndarray) or np.array(data).ndim == 2: # Convert single Kraus op to general Kraus pair kraus = ([np.array(data, dtype=complex)], None) shape = kraus[0][0].shape # Check if single Kraus set [A_i] for channel: # E(rho) = sum_i A_i * rho * A_i^dagger elif isinstance(data, list) and len(data) > 0: # Get dimensions from first Kraus op kraus = [np.array(data[0], dtype=complex)] shape = kraus[0].shape # Iterate over remaining ops and check they are same shape for i in data[1:]: op = np.array(i, dtype=complex) if op.shape != shape: raise QiskitError( "Kraus operators are different dimensions.") kraus.append(op) # Convert single Kraus set to general Kraus pair kraus = (kraus, None) # Check if generalized Kraus set ([A_i], [B_i]) for channel: # E(rho) = sum_i A_i * rho * B_i^dagger elif isinstance(data, tuple) and len(data) == 2 and len(data[0]) > 0: kraus_left = [np.array(data[0][0], dtype=complex)] shape = kraus_left[0].shape for i in data[0][1:]: op = np.array(i, dtype=complex) if op.shape != shape: raise QiskitError( "Kraus operators are different dimensions.") kraus_left.append(op) if data[1] is None: kraus = (kraus_left, None) else: kraus_right = [] for i in data[1]: op = np.array(i, dtype=complex) if op.shape != shape: raise QiskitError( "Kraus operators are different dimensions.") kraus_right.append(op) kraus = (kraus_left, kraus_right) else: raise QiskitError("Invalid input for Kraus channel.") else: raise QiskitError("Invalid input data format for Krausß") output_dim, input_dim = kraus[0][0].shape # Check and format input and output dimensions input_dims = self._automatic_dims(input_dims, input_dim) output_dims = self._automatic_dims(output_dims, output_dim) # Initialize either single or general Kraus if kraus[1] is None or np.allclose(kraus[0], kraus[1]): # Standard Kraus map super().__init__('Kraus', (kraus[0], None), input_dims, output_dims) else: # General (non-CPTP) Kraus map super().__init__('Kraus', kraus, input_dims, output_dims)