def _to_choi(rep, data, input_dim, output_dim): """Transform a QuantumChannel to the Choi representation.""" if rep == 'Choi': return data if rep == 'UnitaryChannel': return _from_unitary('Choi', data, input_dim, output_dim) if rep == 'SuperOp': return _superop_to_choi(data, input_dim, output_dim) if rep == 'Kraus': return _kraus_to_choi(data, input_dim, output_dim) if rep == 'Chi': return _chi_to_choi(data, input_dim, output_dim) if rep == 'PTM': data = _ptm_to_superop(data, input_dim, output_dim) return _superop_to_choi(data, input_dim, output_dim) if rep == 'Stinespring': return _stinespring_to_choi(data, input_dim, output_dim) raise QiskitError('Invalid QuantumChannel {}'.format(rep))
def _evolve_subsystem(self, state, qargs): """Evolve a quantum state by the operator. Args: state (QuantumState): The input statevector or density matrix. qargs (list): a list of QuantumState subsystem positions to apply the operator on. Returns: QuantumState: the output quantum state. Raises: QiskitError: if the operator dimension does not match the specified QuantumState subsystem dimensions. """ mat = np.reshape(self.data, self._shape) # Hack to assume state is a N-qubit state until a proper class for states # is in place state_size = len(state) state_dims = self._automatic_dims(None, state_size) if self.input_dims() != len(qargs) * (2, ): raise QiskitError( "Operator input dimensions are not compatible with state subsystem dimensions." ) if state.ndim == 1: # Return evolved statevector tensor = np.reshape(state, state_dims) indices = [len(state_dims) - 1 - qubit for qubit in qargs] tensor = self._einsum_matmul(tensor, mat, indices) return np.reshape(tensor, state_size) # Return evolved density matrix tensor = np.reshape(state, 2 * state_dims) indices = [len(state_dims) - 1 - qubit for qubit in qargs] right_shift = len(state_dims) # Left multiply by operator tensor = self._einsum_matmul(tensor, mat, indices) # Right multiply by adjoint operator # We implement the transpose by doing left multiplication instead of right # in the _einsum_matmul function tensor = self._einsum_matmul(tensor, np.conj(mat), indices, shift=right_shift) return np.reshape(tensor, [state_size, state_size])
def subtract(self, other): """Return the operator self - other. Args: other (Operator): an operator object. Returns: Operator: the operator self - other. Raises: QiskitError: if other is not an operator, or has incompatible dimensions. """ if not isinstance(other, Operator): other = Operator(other) if self.dim != other.dim: raise QiskitError("other operator has different dimensions.") return Operator(self.data - other.data, self.input_dims(), self.output_dims())
def job_monitor(job, interval=None, monitor_async=False): """Monitor the status of a IBMQJob instance. Args: job (BaseJob): Job to monitor. interval (int): Time interval between status queries. monitor_async (bool): Monitor asyncronously (in Jupyter only). Raises: QiskitError: When trying to run async outside of Jupyter ImportError: ipywidgets not available for notebook. """ if interval is None: _interval_set = False interval = 2 else: _interval_set = True if _NOTEBOOK_ENV: if monitor_async: try: import ipywidgets as widgets # pylint: disable=import-error except ImportError: raise ImportError('These functions need ipywidgets. ' 'Run "pip install ipywidgets" before.') from qiskit.tools.jupyter.jupyter_magics import _html_checker # pylint: disable=C0412 style = "font-size:16px;" header = "<p style='{style}'>Job Status: %s </p>".format( style=style) status = widgets.HTML(value=header % job.status().value) display(status) thread = threading.Thread(target=_html_checker, args=(job, interval, status, header)) thread.start() else: _text_checker(job, interval, _interval_set) else: if monitor_async: raise QiskitError( 'monitor_async only available in Jupyter notebooks.') _text_checker(job, interval, _interval_set)
def subtract(self, other): """Return the QuantumChannel self - other. Args: other (QuantumChannel): a quantum channel. Returns: Chi: the linear subtraction self - other as Chi object. Raises: QiskitError: if other is not a QuantumChannel subclass, or has incompatible dimensions. """ if not isinstance(other, Chi): other = Chi(other) if self.dim != other.dim: raise QiskitError("other QuantumChannel dimensions are not equal") return Chi(self._data - other.data, self._input_dims, self._output_dims)
def multiply(self, other, inplace=False): """Return the QuantumChannel self + other. Args: other (complex): a complex number inplace (bool): If True modify the current object inplace [Default: False] Returns: Stinespring: the scalar multiplication other * self as a Stinespring object. Raises: QiskitError: if other is not a valid scalar. """ if not isinstance(other, Number): raise QiskitError("other is not a number") # If the number is complex or negative we need to convert to # general Stinespring representation so we first convert to # the Choi representation if isinstance(other, complex) or other < 1: # Convert to Choi-matrix tmp = Stinespring(Choi(self).multiply(other, inplace=True)) if inplace: self._data = tmp._data self._input_dim = tmp._input_dim self._output_dim = tmp._output_dim return self return tmp # If the number is real we can update the Kraus operators # directly num = np.sqrt(other) if inplace: self._data[0] *= num if self._data[1] is not None: self._data[1] *= num return self stine_l, stine_r = self._data stine_l = num * self._data[0] stine_r = None if self._data[1] is not None: stine_r = num * self._data[1] return Stinespring((stine_l, stine_r), *self.dims)
def add(self, other): """Return the QuantumChannel self + other. Args: other (QuantumChannel): a quantum channel subclass. Returns: Kraus: the linear addition self + other as a Kraus object. Raises: QiskitError: if other is not a QuantumChannel subclass, or has incompatible dimensions. """ if not issubclass(other.__class__, QuantumChannel): raise QiskitError('other is not a QuantumChannel subclass') # Since we cannot directly add two channels in the Kraus # representation we try and use the other channels method # or convert to the Choi representation return Kraus(Choi(self).add(other))
def _get_backend_instance(self, backend_cls): """ Return an instance of a backend from its class. Args: backend_cls (class): Backend class. Returns: BaseBackend: a backend instance. Raises: QiskitError: if the backend could not be instantiated. """ # Verify that the backend can be instantiated. try: backend_instance = backend_cls(provider=self) except Exception as err: raise QiskitError('Backend %s could not be instantiated: %s' % (backend_cls, err)) return backend_instance
def add(self, other): """Return the QuantumChannel self + other. Args: other (QuantumChannel): a quantum channel. Returns: Choi: the linear addition self + other as a Choi object. Raises: QiskitError: if other cannot be converted to a channel or has incompatible dimensions. """ if not isinstance(other, Choi): other = Choi(other) if self.dim != other.dim: raise QiskitError("other QuantumChannel dimensions are not equal") return Choi(self._data + other.data, self._input_dims, self._output_dims)
def subtract(self, other): """Return the QuantumChannel self - other. Args: other (QuantumChannel): a quantum channel. Returns: PTM: the linear subtraction self - other as PTM object. Raises: QiskitError: if other cannot be converted to a channel or has incompatible dimensions. """ if not isinstance(other, PTM): other = PTM(other) if self.dim != other.dim: raise QiskitError("other QuantumChannel dimensions are not equal") return PTM(self._data - other.data, self._input_dims, self._output_dims)
def multiply(self, other, inplace=False): """Return the QuantumChannel self + other. Args: other (complex): a complex number inplace (bool): If True modify the current object inplace [Default: False] Returns: Kraus: the scalar multiplication other * self as a Kraus object. Raises: QiskitError: if other is not a valid scalar. """ if not isinstance(other, Number): raise QiskitError("other is not a number") # If the number is complex we need to convert to general # kraus channel so we multiply via Choi representation if isinstance(other, complex) or other < 0: # Convert to Choi-matrix tmp = Kraus(Choi(self).multiply(other, inplace=True)) if inplace: self._data = tmp._data self._input_dim = tmp._input_dim self._output_dim = tmp._output_dim return self return tmp # If the number is real we can update the Kraus operators # directly val = np.sqrt(other) if inplace: for j, _ in enumerate(self._data[0]): self._data[0][j] *= val if self._data[1] is not None: for j, _ in enumerate(self._data[1]): self._data[1][j] *= val return self kraus_r = None kraus_l = [val * k for k in self._data[0]] if self._data[1] is not None: kraus_r = [val * k for k in self._data[1]] return Kraus((kraus_l, kraus_r), self._input_dim, self._output_dim)
def subtract(self, other): """Return the QuantumChannel self - other. Args: other (QuantumChannel): a quantum channel. Returns: SuperOp: the linear subtraction self - other as SuperOp object. Raises: QiskitError: if other cannot be converted to a channel or has incompatible dimensions. """ # Convert other to SuperOp if not isinstance(other, SuperOp): other = SuperOp(other) if self.dim != other.dim: raise QiskitError("other QuantumChannel dimensions are not equal") return SuperOp(self._data - other.data, self.input_dims(), self.output_dims())
def _tensor_product(self, other, reverse=False): """Return the tensor product channel. Args: other (QuantumChannel): a quantum channel subclass. reverse (bool): If False return self ⊗ other, if True return if True return (other ⊗ self) [Default: False Returns: Kraus: the tensor product channel as a Kraus object. Raises: QiskitError: if other is not a QuantumChannel subclass. """ # Convert other to Kraus if not issubclass(other.__class__, QuantumChannel): raise QiskitError('other is not a QuantumChannel subclass') if not isinstance(other, Kraus): other = Kraus(other) # Get tensor matrix ka_l, ka_r = self._data kb_l, kb_r = other._data if reverse: kab_l = [np.kron(b, a) for a in ka_l for b in kb_l] else: kab_l = [np.kron(a, b) for a in ka_l for b in kb_l] if ka_r is None and kb_r is None: kab_r = None else: if ka_r is None: ka_r = ka_l if kb_r is None: kb_r = kb_l if reverse: kab_r = [np.kron(b, a) for a in ka_r for b in kb_r] else: kab_r = [np.kron(a, b) for a in ka_r for b in kb_r] data = (kab_l, kab_r) input_dim = self._input_dim * other._input_dim output_dim = self._output_dim * other._output_dim return Kraus(data, input_dim, output_dim)
def multiply(self, other, inplace=False): """Return the QuantumChannel self + other. Args: other (complex): a complex number inplace (bool): If True modify the current object inplace [Default: False] Returns: SuperOp: the scalar multiplication other * self as a SuperOp object. Raises: QiskitError: if other is not a valid scalar. """ if not isinstance(other, Number): raise QiskitError("other is not a number") if inplace: self._data *= other return self input_dim, output_dim = self.dims return SuperOp(other * self._data, input_dim, output_dim)
def _evolve(self, state, qubits=None): """Evolve a quantum state by the QuantumChannel. Args: state (QuantumState): The input statevector or density matrix. qubits (list): a list of QuantumState subsystem positions to apply the operator on. Returns: QuantumState: the output quantum state. Raises: QiskitError: if the operator dimension does not match the specified QuantumState subsystem dimensions. """ # If subsystem evolution we use the SuperOp representation if qubits is not None: return SuperOp(self)._evolve(state, qubits) # Otherwise we compute full evolution directly state = self._format_state(state) if state.shape[0] != self._input_dim: raise QiskitError( "QuantumChannel input dimension is not equal to state dimension." ) if state.ndim == 1 and self._data[1] is None and \ self._data[0].shape[0] // self._output_dim == 1: # If the shape of the stinespring operator is equal to the output_dim # evolution of a state vector psi -> stine.psi return np.dot(self._data[0], state) # Otherwise we always return a density matrix state = self._format_state(state, density_matrix=True) stine_l, stine_r = self._data if stine_r is None: stine_r = stine_l din, dout = self.dim dtr = stine_l.shape[0] // dout shape = (dout, dtr, din) return np.einsum('iAB,BC,jAC->ij', np.reshape(stine_l, shape), state, np.reshape(np.conjugate(stine_r), shape))
def _tensor_product(self, other, reverse=False): """Return the tensor product channel. Args: other (QuantumChannel): a quantum channel subclass. reverse (bool): If False return self ⊗ other, if True return if True return (other ⊗ self) [Default: False Returns: Choi: the tensor product channel as a Choi object. Raises: QiskitError: if other is not a QuantumChannel subclass. """ # Convert other to Choi if not issubclass(other.__class__, QuantumChannel): raise QiskitError('other is not a QuantumChannel subclass') if not isinstance(other, Choi): other = Choi(other) # Reshuffle indicies a_in, a_out = self.dims b_in, b_out = other.dims # Combined channel dimensions input_dim = a_in * b_in output_dim = a_out * b_out if reverse: data = _bipartite_tensor( other.data, self._data, shape1=other._bipartite_shape, shape2=self._bipartite_shape) else: data = _bipartite_tensor( self._data, other.data, shape1=self._bipartite_shape, shape2=other._bipartite_shape) return Choi(data, input_dim, output_dim)
def _from_operator(rep, data, input_dim, output_dim): """Transform Operator representation to other representation.""" if rep == 'Operator': return data if rep == 'SuperOp': return np.kron(np.conj(data), data) if rep == 'Choi': vec = np.ravel(data, order='F') return np.outer(vec, np.conj(vec)) if rep == 'Kraus': return ([data], None) if rep == 'Stinespring': return (data, None) if rep == 'Chi': _check_nqubit_dim(input_dim, output_dim) data = _from_operator('Choi', data, input_dim, output_dim) return _choi_to_chi(data, input_dim, output_dim) if rep == 'PTM': _check_nqubit_dim(input_dim, output_dim) data = _from_operator('SuperOp', data, input_dim, output_dim) return _superop_to_ptm(data, input_dim, output_dim) raise QiskitError('Invalid QuantumChannel {}'.format(rep))
def least_busy(backends): """ Return the least busy available backend for those that have a `pending_jobs` in their `status`. Backends such as local backends that do not have this are not considered. Args: backends (list[BaseBackend]): backends to choose from Returns: BaseBackend: the the least busy backend Raises: QiskitError: if passing a list of backend names that is either empty or none have attribute ``pending_jobs`` """ try: return min([b for b in backends if b.status().operational], key=lambda b: b.status().pending_jobs) except (ValueError, TypeError): raise QiskitError( "Can only find least_busy backend from a non-empty list.")
def __init__(self, data, input_dim=None, output_dim=None): # Check if input is a quantum channel object # If so we disregard the dimension kwargs if issubclass(data.__class__, QuantumChannel): input_dim, output_dim = data.dims super_mat = _to_superop(data.rep, data._data, input_dim, output_dim) else: # We initialize directly from superoperator matrix super_mat = np.array(data, dtype=complex) # Determine input and output dimensions dout, din = super_mat.shape if output_dim is None: output_dim = int(np.sqrt(dout)) if input_dim is None: input_dim = int(np.sqrt(din)) # Check dimensions if output_dim**2 != dout or input_dim**2 != din: raise QiskitError( "Invalid input and output dimension for superoperator input." ) super().__init__('SuperOp', super_mat, input_dim, output_dim)
def subscribe(self, event, callback): """Subscribes to an event, so when it's emitted all the callbacks subscribed, will be executed. We are not allowing double registration. Args event (string): The event to subscribed in the form of: "terra.<component>.<method>.<action>" callback (callable): The callback that will be executed when an event is emitted. """ if not callable(callback): raise QiskitError("Callback is not a callable!") if event not in self._subscribers: self._subscribers[event] = [] new_subscription = self._Subscription(event, callback) if new_subscription in self._subscribers[event]: # We are not allowing double subscription return False self._subscribers[event].append(new_subscription) return True
def _evolve(self, state, qubits=None): """Evolve a quantum state by the QuantumChannel. Args: state (QuantumState): The input statevector or density matrix. qubits (list): a list of QuantumState subsystem positions to apply the operator on. Returns: QuantumState: the output quantum state. Raises: QiskitError: if the operator dimension does not match the specified QuantumState subsystem dimensions. """ # If subsystem evolution we use the SuperOp representation if qubits is not None: return SuperOp(self)._evolve(state, qubits) # Otherwise we compute full evolution directly state = self._format_state(state) if state.shape[0] != self._input_dim: raise QiskitError( "QuantumChannel input dimension is not equal to state dimension." ) if state.ndim == 1 and self._data[1] is None and len( self._data[0]) == 1: # If we only have a single Kraus operator we can implement unitary-type # evolution of a state vector psi -> K[0].psi return np.dot(self._data[0][0], state) # Otherwise we always return a density matrix state = self._format_state(state, density_matrix=True) kraus_l, kraus_r = self._data if kraus_r is None: kraus_r = kraus_l return np.einsum('AiB,BC,AjC->ij', kraus_l, state, np.conjugate(kraus_r))
def _tensor_product(self, other, inplace=False, reverse=False): """Return the tensor product channel. Args: other (QuantumChannel): a quantum channel subclass inplace (bool): If True modify the current object inplace [default: False] reverse (bool): If False return self ⊗ other, if True return if True return (other ⊗ self) [Default: False Returns: UnitaryChannel: the tensor product channel as a UnitaryChannel object. Raises: QiskitError: if other is not a QuantumChannel subclass. """ if not issubclass(other.__class__, QuantumChannel): raise QiskitError('Other is not a channel rep') # Convert to UnitaryChannel matrix if not isinstance(other, UnitaryChannel): other = UnitaryChannel(other) # Combined channel dimensions a_in, a_out = self.dims b_in, b_out = other.dims input_dim = a_in * b_in output_dim = a_out * b_out if reverse: data = np.kron(other._data, self._data) else: data = np.kron(self._data, other._data) if inplace: self._data = data self._input_dim = input_dim self._output_dim = output_dim return self # Not inplace so return new object return UnitaryChannel(data, input_dim, output_dim)
def __init__(self, data, input_dims=None, output_dims=None): """Initialize a quantum channel Chi-matrix 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 is not an N-qubit channel or cannot be initialized as a Chi-matrix. Additional Information ---------------------- If the input or output dimensions are None, they will be automatically determined from the input data. The Chi matrix representation is only valid for N-qubit channels. """ # If the input is a raw list or matrix we assume that it is # already a Chi matrix. if isinstance(data, (list, np.ndarray)): # Initialize from raw numpy or list matrix. chi_mat = np.array(data, dtype=complex) # Determine input and output dimensions dim_l, dim_r = chi_mat.shape if dim_l != dim_r: raise QiskitError('Invalid Chi-matrix input.') if input_dims: input_dim = np.product(input_dims) if output_dims: output_dim = np.product(input_dims) if output_dims is None and input_dims is None: output_dim = int(np.sqrt(dim_l)) input_dim = dim_l // output_dim elif input_dims is None: input_dim = dim_l // output_dim elif output_dims is None: output_dim = dim_l // input_dim # Check dimensions if input_dim * output_dim != dim_l: raise QiskitError("Invalid shape for Chi-matrix input.") 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._instruction_to_superop(data) else: # We use the QuantumChannel init transform to intialize # 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 Chi object chi_mat = _to_chi(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() # Check input is N-qubit channel n_qubits = int(np.log2(input_dim)) if 2**n_qubits != input_dim: raise QiskitError("Input is not an n-qubit Chi matrix.") # 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) super().__init__('Chi', chi_mat, input_dims, output_dims)
def _check_dups(self, qubits): """Raise exception if list of qubits contains duplicates.""" squbits = set(qubits) if len(squbits) != len(qubits): raise QiskitError("duplicate qubit arguments")
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 specifed 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.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: # 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._instruction_to_superop(data) else: # We use the QuantumChannel init transform to intialize # 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 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() 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)
def _tensor_product(self, other, reverse=False): """Return the tensor product channel. Args: other (QuantumChannel): a quantum channel subclass. reverse (bool): If False return self ⊗ other, if True return if True return (other ⊗ self) [Default: False] Returns: Stinespring: the tensor product channel as a Stinespring object. Raises: QiskitError: if other is not a Stinespring object """ # Convert other to Stinespring if not issubclass(other.__class__, QuantumChannel): raise QiskitError('other is not a QuantumChannel subclass') if not isinstance(other, Stinespring): other = Stinespring(other) # Tensor stinespring ops sa_l, sa_r = self._data sb_l, sb_r = other._data # Reshuffle tensor dimensions din_a, dout_a = self.dims din_b, dout_b = other.dims dtr_a = sa_l.shape[0] // dout_a dtr_b = sb_l.shape[0] // dout_b if reverse: shape_in = (dout_b, dtr_b, dout_a, dtr_a, din_b * din_a) shape_out = (dout_b * dtr_b * dout_a * dtr_a, din_b * din_a) else: shape_in = (dout_a, dtr_a, dout_b, dtr_b, din_a * din_b) shape_out = (dout_a * dtr_a * dout_b * dtr_b, din_a * din_b) # Compute left stinepsring op if reverse: sab_l = np.kron(sb_l, sa_l) else: sab_l = np.kron(sa_l, sb_l) # Reravel indicies sab_l = np.reshape( np.transpose(np.reshape(sab_l, shape_in), (0, 2, 1, 3, 4)), shape_out) # Compute right stinespring op if sa_r is None and sb_r is None: sab_r = None else: if sa_r is None: sa_r = sa_l elif sb_r is None: sb_r = sb_l if reverse: sab_r = np.kron(sb_r, sa_r) else: sab_r = np.kron(sa_r, sb_r) # Reravel indicies sab_r = np.reshape( np.transpose(np.reshape(sab_r, shape_in), (0, 2, 1, 3, 4)), shape_out) return Stinespring((sab_l, sab_r), din_a * din_b, dout_a * dout_b)
def add(self, gate): """Add instruction to set.""" if not isinstance(gate, Instruction): raise QiskitError("attempt to add non-Instruction" + " to InstructionSet") self.instructions.append(gate)
def check_circuit(self): """Raise exception if self.circuit is None.""" if self.circuit is None: raise QiskitError("Instruction's circuit not assigned")
def __init__(self, data, input_dim=None, output_dim=None): # Check if input is a quantum channel object # If so we disregard the dimension kwargs if issubclass(data.__class__, QuantumChannel): input_dim, output_dim = data.dims kraus = _to_kraus(data.rep, data._data, input_dim, output_dim) else: # 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.") dout, din = kraus[0][0].shape if (input_dim and input_dim != din) or (output_dim and output_dim != dout): raise QiskitError("Invalid dimensions for Kraus input.") if kraus[1] is None or np.allclose(kraus[0], kraus[1]): # Standard Kraus map super().__init__('Kraus', (kraus[0], None), input_dim=din, output_dim=dout) else: # General (non-CPTP) Kraus map super().__init__('Kraus', kraus, input_dim=din, output_dim=dout)
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)