def compose(self, other, qargs=None, front=False): """Return the composition channel self∘other. Args: other (SparsePauliOp): an operator object. qargs (list or None): a list of subsystem positions to compose other on. front (bool or None): If False compose in standard order other(self(input)) otherwise compose in reverse order self(other(input)) [default: False] Returns: SparsePauliOp: The composed operator. Raises: QiskitError: if other cannot be converted to an Operator or has incompatible dimensions. """ # pylint: disable=invalid-name if qargs is None: qargs = getattr(other, 'qargs', None) if not isinstance(other, SparsePauliOp): other = SparsePauliOp(other) # Validate composition dimensions and qargs match self._op_shape.compose(other._op_shape, qargs, front) # Implement composition of the Pauli table x1, x2 = PauliTable._block_stack(self.table.X, other.table.X) z1, z2 = PauliTable._block_stack(self.table.Z, other.table.Z) c1, c2 = PauliTable._block_stack(self.coeffs, other.coeffs) if qargs is not None: ret_x, ret_z = x1.copy(), z1.copy() x1 = x1[:, qargs] z1 = z1[:, qargs] ret_x[:, qargs] = x1 ^ x2 ret_z[:, qargs] = z1 ^ z2 table = np.hstack([ret_x, ret_z]) else: table = np.hstack((x1 ^ x2, z1 ^ z2)) # Take product of coefficients and add phase correction coeffs = c1 * c2 # We pick additional phase terms for the products # X.Y = i * Z, Y.Z = i * X, Z.X = i * Y # Y.X = -i * Z, Z.Y = -i * X, X.Z = -i * Y if front: plus_i = (x1 & ~z1 & x2 & z2) | (x1 & z1 & ~x2 & z2) | (~x1 & z1 & x2 & ~z2) minus_i = (x2 & ~z2 & x1 & z1) | (x2 & z2 & ~x1 & z1) | (~x2 & z2 & x1 & ~z1) else: minus_i = (x1 & ~z1 & x2 & z2) | (x1 & z1 & ~x2 & z2) | (~x1 & z1 & x2 & ~z2) plus_i = (x2 & ~z2 & x1 & z1) | (x2 & z2 & ~x1 & z1) | (~x2 & z2 & x1 & ~z1) coeffs *= 1j**np.array(np.sum(plus_i, axis=1), dtype=int) coeffs *= (-1j)**np.array(np.sum(minus_i, axis=1), dtype=int) return SparsePauliOp(table, coeffs)
def compose(self, other, qargs=None, front=False): if qargs is None: qargs = getattr(other, "qargs", None) if not isinstance(other, SparsePauliOp): other = SparsePauliOp(other) # Validate composition dimensions and qargs match self._op_shape.compose(other._op_shape, qargs, front) # Implement composition of the Pauli table x1, x2 = PauliTable._block_stack(self.table.X, other.table.X) z1, z2 = PauliTable._block_stack(self.table.Z, other.table.Z) c1, c2 = PauliTable._block_stack(self.coeffs, other.coeffs) if qargs is not None: ret_x, ret_z = x1.copy(), z1.copy() x1 = x1[:, qargs] z1 = z1[:, qargs] ret_x[:, qargs] = x1 ^ x2 ret_z[:, qargs] = z1 ^ z2 table = np.hstack([ret_x, ret_z]) else: table = np.hstack((x1 ^ x2, z1 ^ z2)) # Take product of coefficients and add phase correction coeffs = c1 * c2 # We pick additional phase terms for the products # X.Y = i * Z, Y.Z = i * X, Z.X = i * Y # Y.X = -i * Z, Z.Y = -i * X, X.Z = -i * Y if front: plus_i = (x1 & ~z1 & x2 & z2) | (x1 & z1 & ~x2 & z2) | (~x1 & z1 & x2 & ~z2) minus_i = (x2 & ~z2 & x1 & z1) | (x2 & z2 & ~x1 & z1) | (~x2 & z2 & x1 & ~z1) else: minus_i = (x1 & ~z1 & x2 & z2) | (x1 & z1 & ~x2 & z2) | (~x1 & z1 & x2 & ~z2) plus_i = (x2 & ~z2 & x1 & z1) | (x2 & z2 & ~x1 & z1) | (~x2 & z2 & x1 & ~z1) coeffs *= 1j**np.array(np.sum(plus_i, axis=1), dtype=int) coeffs *= (-1j)**np.array(np.sum(minus_i, axis=1), dtype=int) return SparsePauliOp(table, coeffs)