docs: def canonical(self):
          """ 
          Compute canonical preference representation
          Uses auxiliary problem of 9.4.2, with the preference shock process reintroduced
          Calculates pihat, llambdahat and ubhat for the equivalent canonical household technology
          """
          Ac1 = np.hstack((self.deltah, np.zeros((self.nh, self.nz))))
          Ac2 = np.hstack((np.zeros((self.nz, self.nh)), self.a22))
          Ac = np.vstack((Ac1, Ac2))
          Bc = np.vstack((self.thetah, np.zeros((self.nz, self.nc))))
          Cc = np.vstack((np.zeros((self.nh, self.nw)), self.c2))
          Rc1 = np.hstack((self.llambda.T.dot(self.llambda), -
                           self.llambda.T.dot(self.ub)))
          Rc2 = np.hstack((-self.ub.T.dot(self.llambda), self.ub.T.dot(self.ub)))
          Rc = np.vstack((Rc1, Rc2))
          Qc = self.pih.T.dot(self.pih)
          Nc = np.hstack(
              (self.pih.T.dot(self.llambda), -self.pih.T.dot(self.ub)))
  
          lq_aux = LQ(Qc, Rc, Ac, Bc, N=Nc, beta=self.beta)
  
          P1, F1, d1 = lq_aux.stationary_values()
  
          self.F_b = F1[:, 0:self.nh]
          self.F_f = F1[:, self.nh:]
  
          self.pihat = np.linalg.cholesky(self.pih.T.dot(
              self.pih) + self.beta.dot(self.thetah.T).dot(P1[0:self.nh, 0:self.nh]).dot(self.thetah)).T
          self.llambdahat = self.pihat.dot(self.F_b)
          self.ubhat = - self.pihat.dot(self.F_f)
  
          return