docs: def ltsa(geom, n_components, eigen_solver='auto',
           random_state=None, solver_kwds=None):
      """
      Perform a Local Tangent Space Alignment analysis on the data.
  
      Parameters
      ----------
      geom : a Geometry object from megaman.geometry.geometry
      n_components : integer
          number of coordinates for the manifold.
      eigen_solver : {'auto', 'dense', 'arpack', 'lobpcg', or 'amg'}
          'auto' :
              algorithm will attempt to choose the best method for input data
          'dense' :
              use standard dense matrix operations for the eigenvalue decomposition.
              For this method, M must be an array or matrix type.  This method should be avoided for large problems.
          'arpack' :
              use arnoldi iteration in shift-invert mode. For this method,
              M may be a dense matrix, sparse matrix, or general linear operator.
              Warning: ARPACK can be unstable for some problems.  It is best to
              try several random seeds in order to check results.
          'lobpcg' :
              Locally Optimal Block Preconditioned Conjugate Gradient Method.
              A preconditioned eigensolver for large symmetric positive definite
              (SPD) generalized eigenproblems.
          'amg' :
              AMG requires pyamg to be installed. It can be faster on very large,
              sparse problems, but may also lead to instabilities.
      random_state : numpy.RandomState or int, optional
          The generator or seed used to determine the starting vector for arpack
          iterations.  Defaults to numpy.random.
      solver_kwds : any additional keyword arguments to pass to the selected eigen_solver
  
      Returns
      -------
      embedding : array-like, shape [n_samples, n_components]
          Embedding vectors.
      squared_error : float
          Reconstruction error for the embedding vectors. Equivalent to
          ``norm(Y - W Y, 'fro')**2``, where W are the reconstruction weights.
  
      References
      ----------
      * Zhang, Z. & Zha, H. Principal manifolds and nonlinear
        dimensionality reduction via tangent space alignment.
        Journal of Shanghai Univ.  8:406 (2004)
      """
      if geom.X is None:
          raise ValueError("Must pass data matrix X to Geometry")
      (N, d_in) = geom.X.shape
      if n_components > d_in:
          raise ValueError("output dimension must be less than or equal "
                           "to input dimension")
      # get the distance matrix and neighbors list
      if geom.adjacency_matrix is None:
          geom.compute_adjacency_matrix()
      (rows, cols) = geom.adjacency_matrix.nonzero()
      eigen_solver, solver_kwds = check_eigen_solver(eigen_solver, solver_kwds,
                                                     size=geom.adjacency_matrix.shape[0],
                                                     nvec=n_components + 1)
      if eigen_solver != 'dense':
          M = sparse.csr_matrix((N, N))
      else:
          M = np.zeros((N, N))
      for i in range(N):
          neighbors_i = cols[rows == i]
          n_neighbors_i = len(neighbors_i)
          use_svd = (n_neighbors_i > d_in)
          Xi = geom.X[neighbors_i]
          Xi -= Xi.mean(0)
          # compute n_components largest eigenvalues of Xi * Xi^T
          if use_svd:
              v = svd(Xi, full_matrices=True)[0]
          else:
              Ci = np.dot(Xi, Xi.T)
              v = eigh(Ci)[1][:, ::-1]
          Gi = np.zeros((n_neighbors_i, n_components + 1))
          Gi[:, 1:] = v[:, :n_components]
          Gi[:, 0] = 1. / np.sqrt(n_neighbors_i)
          GiGiT = np.dot(Gi, Gi.T)
          nbrs_x, nbrs_y = np.meshgrid(neighbors_i, neighbors_i)
          with warnings.catch_warnings():
              # sparse will complain this is better with lil_matrix but it doesn't work
              warnings.simplefilter("ignore")
              M[nbrs_x, nbrs_y] -= GiGiT
              M[neighbors_i, neighbors_i] += 1
      return null_space(M, n_components, k_skip=1, eigen_solver=eigen_solver,
                        random_state=random_state,solver_kwds=solver_kwds)