docs: private void similarTransform( int k) {
          double t[] = QT.data;
  
          // find the largest value in this column
          // this is used to normalize the column and mitigate overflow/underflow
          double max = QrHelperFunctions_ZDRM.computeRowMax(QT,k,k+1,N);
  
          if( max > 0 ) {
              double gamma = QrHelperFunctions_ZDRM.computeTauGammaAndDivide(k*N+k+1, k*N+N, t, max, tau);
              gammas[k] = gamma;
  
              // divide u by u_0
              double real_u_0 = t[(k*N+k+1)*2]   + tau.real;
              double imag_u_0 = t[(k*N+k+1)*2+1] + tau.imaginary;
              QrHelperFunctions_ZDRM.divideElements(k+2, N, t, k*N, real_u_0,imag_u_0 );
  
              // A column is zeroed first.  However a row is being used to store because it reduces
              // cache misses.  Need to compute the conjugate to have the correct householder operation
              for (int i = k+2; i < N; i++) {
                  t[(k*N+i)*2+1] = -t[(k*N+i)*2+1];
              }
  
              t[(k*N+k+1)*2]   = 1.0;
              t[(k*N+k+1)*2+1] = 0;
  
              // ---------- Specialized householder that takes advantage of the symmetry
  //            QrHelperFunctions_ZDRM.rank1UpdateMultR(QT,QT.data,k*N,gamma,k+1,k+1,N,w);
  //            QrHelperFunctions_ZDRM.rank1UpdateMultL(QT,QT.data,k*N,gamma,k+1,k+1,N);
  
              householderSymmetric(k,gamma);
  
              // since the first element in the householder vector is known to be 1
              // store the full upper hessenberg
              t[(k*N+k+1)*2]   = -tau.real*max;
              t[(k*N+k+1)*2+1] = -tau.imaginary*max;
  
          } else {
              gammas[k] = 0;
          }
      }