Source code for unitaria.nodes.nonlinear

from typing import Sequence

import numpy as np
import tequila as tq

from unitaria.nodes.node import Node
from unitaria.nodes.multilinear_node import MultilinearNode
from unitaria.subspace import Subspace
from unitaria.circuit import Circuit


[docs] class ComponentwiseMul(MultilinearNode): """ Node implementing the (bilinear) componentwise multiplication operator More specifically this implements the bilinear map ``(x, y) -> [x_1 * y_1, ..., x_n * y_n]``. Usually you will want the elementwise product of two vectors, in which case the correct result will be obtained by building the tensor product of the vectors and then multiplying it with this operation, i.e. ``Mul(ComponentwiseMul(a.subspace_out()), Tensor(a, b))`` :param subspace: The vector space in which to perform the element-wise operation """ subspace: Subspace def __init__(self, subspace_or_first: Subspace | Node, second: Node | None = None): if isinstance(subspace_or_first, Subspace): self.subspace = subspace_or_first super().__init__([self.subspace.dimension] * 2, self.subspace.dimension) else: self.subspace = subspace_or_first.subspace_out super().__init__([self.subspace.dimension] * 2, self.subspace.dimension, subspace_or_first, second) def definition(self) -> Node: return ComponentwiseMulMultilinear(self.subspace)
class ComponentwiseMulMultilinear(Node): """ :no-index: Internal class for implementing `ComponentwiseMul`, see also `MultilinearNode` """ subspace: Subspace def __init__(self, subspace: Subspace): super().__init__(subspace.dimension**2, subspace.dimension) self.subspace = subspace def _subspace_in(self) -> Subspace: return self.subspace & self.subspace def _subspace_out(self) -> Subspace: return Subspace("0" * self.subspace.total_qubits) & self.subspace def _normalization(self) -> float: return 1 def compute(self, input: np.ndarray) -> np.ndarray: shape = list(input.shape[:-1]) dim = self.subspace.dimension input_reshaped = input.reshape(shape + [dim, dim]) return np.diagonal(input_reshaped, axis1=-2, axis2=-1) def compute_adjoint(self, input: np.ndarray) -> np.ndarray: if input.ndim == 1: result = np.diag(input) else: result = np.zeros(list(input.shape[:-1]) + [input.shape[-1]] * 2, dtype=np.complex128) indices = np.arange(input.shape[-1]) result[:, indices, indices] = input[:, indices] return result.reshape(list(input.shape[:-1]) + [input.shape[-1] ** 2]) def _circuit( self, target: Sequence[int], clean_ancillae: Sequence[int], borrowed_ancillae: Sequence[int] ) -> Circuit: circuit = Circuit() for i in range(self.subspace.total_qubits): circuit += tq.gates.CNOT(target[i], target[i + self.subspace.total_qubits]) return circuit def clean_ancilla_count(self) -> int: return 0 def borrowed_ancilla_count(self) -> int: return 0