//  [python]
def _setup(self, L, A, B, C, log_dt, p, q, trainable=None, lr=None, use_length=True):
        """ Preprocess arguments into a representation. This occurs after init so that these operations can occur after moving model to device

        L: Maximum length; this module computes Krylov function of length L
        A: (..., N, N)
        B: (..., N)
        C: (..., N)
        dt: (...)
        p: (..., N) low-rank correction to A
        q: (..., N)
        """

        # Rank of low-rank correction
        assert p.shape[-2] == q.shape[-2]
        self.rank = p.shape[-2]
        self.L = L
        self.use_length = use_length

        # dt = torch.exp(log_dt)

        # Store diagonalization of A+pp^T
        Ap = A + torch.sum(q.unsqueeze(-2)*p.unsqueeze(-1), dim=-3)
        w, V = torch.linalg.eig(Ap) # (..., N) (..., N, N)
        w = w[..., 0::2].contiguous()
        V = V[..., 0::2].contiguous()

        # Discretize
        # dA, _ = KrylovNPLR.bilinear(dt, w, V, p, q, B)

        # # Handle (I - dA^L) term to move coefficients into evaluations
        # dA_L = power(L, dA)
        # C_ = torch.einsum('... m n, ... m -> ... n', dA_L, C)
        # C = C - C_
        # if use_length:
        #     C = self._process_C(self.L, w, V, p, q, B, C)

        ### Process B, C, p, q, V into V @ [B p], V @ [C q]

        # Augment B and C with low rank correction
        B = B.unsqueeze(-2) # (..., 1, N)
        C = C.unsqueeze(-2) # (..., 1, N)
        if len(B.shape) > len(p.shape):
            p = p.repeat(B.shape[:-2] + (1, 1))
        B = torch.cat([B, p], dim=-2)
        if len(C.shape) > len(q.shape):
            q = q.repeat(C.shape[:-2] + (1, 1))
        C = torch.cat([C, q], dim=-2)

        # Multiply by V
        # BV = (B.to(V).unsqueeze(-2) @ V).squeeze(-2)
        # CV = (C.to(V).unsqueeze(-2) @ V).squeeze(-2)
        BV = contract('... r n, ... n m -> ... r m', B.to(V), V).contiguous()
        CV = contract('... r n, ... n m -> ... r m', C.to(V), V).contiguous()


        if self.use_length:
            assert L is not None
            nodes, z = self._nodes(L, dtype=w.dtype, device=w.device)
            self.register_buffer('nodes', torch.view_as_real(nodes))
            self.register_buffer('z', torch.view_as_real(z))
        # # Cache FFT nodes and their "unprocessed" them with the bilinear transform
        # # nodes = torch.tensor(np.exp(-2j * np.pi / (L)), dtype=torch.cfloat, device=Ap.device) # \omega_{2L}
        # nodes = torch.tensor(np.exp(-2j * np.pi / (L)), dtype=w.dtype, device=w.device) # \omega_{2L}
        # nodes = nodes ** torch.arange(0, L//2+1, device=w.device)
        # z = 2*(1-nodes)/(1+nodes)
        # self.register_buffer('nodes', torch.view_as_real(nodes))
        # self.register_buffer('z', torch.view_as_real(z))
        self.register_buffer('V', torch.view_as_real(V))

        # Register parameters
        if trainable is None: trainable = DictConfig({'A': 0, 'B': 0, 'C': 0, 'dt': 0})
        if lr is None: lr = DictConfig({'A': None, 'B': None, 'C': None, 'dt': None})
        repeat = CV.size(0)
        self.register('log_dt', log_dt, trainable.dt, lr.dt, 0.0)
        self.register('w', torch.view_as_real(w), trainable.A, lr.A, 0.0, repeat=repeat)
        self.register('BV', torch.view_as_real(BV), trainable.B, lr.B, 0.0, repeat=repeat)
        self.register('CV', torch.view_as_real(CV), trainable.C, lr.C)

        if self.use_length:
            # original_CV = CV
            # contract('... m, ... m n -> ... n', original_CV, V.transpose(-1, -2).conj()).real*2 - C
            CV = self._process_C(L)
            delattr(self, 'CV')
            self.register('CV', torch.view_as_real(CV), trainable.C, lr.C)