diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -84,7 +84,7 @@
 
 -- | Command line arguments
 data Options
-    = MakeTree { matrixPath :: [String] <?> "(PATH) The path to the input directory containing the matrix output of cellranger (matrix.mtx, genes.tsv, and barcodes.tsv) or, if genes-file and cells-file are not specified, or an input csv file containing gene row names and cell column names. If given as a list (--matrixPath input1 --matrixPath input2 etc.) then will join all matrices together. Assumes the same number and order of genes in each matrix, so only cells are added."
+    = MakeTree { matrixPath :: [String] <?> "(PATH) The path to the input directory containing the matrix output of cellranger (matrix.mtx, genes.tsv, and barcodes.tsv) or an input csv file containing gene row names and cell column names. If given as a list (--matrixPath input1 --matrixPath input2 etc.) then will join all matrices together. Assumes the same number and order of genes in each matrix, so only cells are added."
                , projectionFile :: Maybe String <?> "([Nothing] | FILE) The input file containing positions of each cell for plotting. Format is \"barcode,x,y\" and matches column order in the matrix file. Useful for 10x where a TNSE projection is generated in \"projection.csv\". Cells without projections will not be plotted. If not supplied, no plot will be made."
                , cellWhitelistFile :: Maybe String <?> "([Nothing] | FILE) The input file containing the cells to include. No header, line separated list of barcodes."
                , labelsFile :: Maybe String <?> "([Nothing] | FILE) The input file containing the label for each cell barcode, with \"item,label\" header."
@@ -117,8 +117,9 @@
                , prior :: Maybe String <?> "([Nothing] | STRING) The input folder containing the output from a previous run. If specified, skips clustering by using the previous clustering files."
                , order :: Maybe Double <?> "([1] | DOUBLE) The order of diversity."
                , clumpinessMethod :: Maybe String <?> "([Majority] | Exclusive | AllExclusive) The method used when calculating clumpiness: Majority labels leaves according to the most abundant label, Exclusive only looks at leaves consisting of cells solely from one label, and AllExclusive treats the leaf as containing both labels."
+               , dense :: Bool <?> "Whether to use dense matrix algorithms for clustering. Should be faster for dense matrices, so if batch correction, PCA, or other algorithms are applied upstream to the input matrix, consider using this option to speed up the tree generation."
                , output :: Maybe String <?> "([out] | STRING) The folder containing output."}
-    | Interactive { matrixPath :: [String] <?> "(PATH) The path to the input directory containing the matrix output of cellranger (matrix.mtx, genes.tsv, and barcodes.tsv) or, if genes-file and cells-file are not specified, or an input csv file containing gene row names and cell column names. If given as a list (--matrixPath input1 --matrixPath input2 etc.) then will join all matrices together. Assumes the same number and order of genes in each matrix, so only cells are added."
+    | Interactive { matrixPath :: [String] <?> "(PATH) The path to the input directory containing the matrix output of cellranger (matrix.mtx, genes.tsv, and barcodes.tsv) or an input csv file containing gene row names and cell column names. If given as a list (--matrixPath input1 --matrixPath input2 etc.) then will join all matrices together. Assumes the same number and order of genes in each matrix, so only cells are added."
                   , cellWhitelistFile :: Maybe String <?> "([Nothing] | FILE) The input file containing the cells to include. No header, line separated list of barcodes."
                   , labelsFile :: Maybe String <?> "([Nothing] | FILE) The input file containing the label for each cell barcode, with \"item,label\" header."
                   , delimiter :: Maybe Char <?> "([,] | CHAR) The delimiter for the csv file if using a normal csv rather than cellranger output and for --labels-file."
@@ -127,7 +128,7 @@
                   , noFilter :: Bool <?> "Whether to bypass filtering genes and cells by low counts."
                   , filterThresholds :: Maybe String <?> "([(250, 1)] | (DOUBLE, DOUBLE)) The minimum filter thresholds for (MINCELL, MINFEATURE) when filtering cells and features by low read counts. See also --no-filter."
                   , prior :: Maybe String <?> "([Nothing] | STRING) The input folder containing the output from a previous run. If specified, skips clustering by using the previous clustering files."}
-    | Differential { matrixPath :: [String] <?> "(PATH) The path to the input directory containing the matrix output of cellranger (matrix.mtx, genes.tsv, and barcodes.tsv) or, if genes-file and cells-file are not specified, or an input csv file containing gene row names and cell column names. If given as a list (--matrixPath input1 --matrixPath input2 etc.) then will join all matrices together. Assumes the same number and order of genes in each matrix, so only cells are added."
+    | Differential { matrixPath :: [String] <?> "(PATH) The path to the input directory containing the matrix output of cellranger (matrix.mtx, genes.tsv, and barcodes.tsv) or an input csv file containing gene row names and cell column names. If given as a list (--matrixPath input1 --matrixPath input2 etc.) then will join all matrices together. Assumes the same number and order of genes in each matrix, so only cells are added."
                    , cellWhitelistFile :: Maybe String <?> "([Nothing] | FILE) The input file containing the cells to include. No header, line separated list of barcodes."
                    , labelsFile :: Maybe String <?> "([Nothing] | FILE) The input file containing the label for each cell barcode, with \"item,label\" header."
                    , pca :: Maybe Double <?> "([Nothing] | DOUBLE) The percent variance to retain for PCA dimensionality reduction before clustering. Default is no PCA at all in order to keep all information."
@@ -168,21 +169,23 @@
     short "drawColors"           = Just 'R'
     short "drawDendrogram"       = Just 'D'
     short "drawLeaf"             = Just 'L'
+    short "drawLegendAllLabels"  = Just 'J'
+    short "drawLegendSep"        = Just 'Q'
     short "drawMark"             = Just 'K'
-    short "drawMaxNodeSize"      = Just 'A'
     short "drawMaxLeafNodeSize"  = Nothing
+    short "drawMaxNodeSize"      = Just 'A'
     short "drawNoScaleNodes"     = Just 'W'
     short "drawNodeNumber"       = Just 'N'
-    short "drawLegendSep"        = Just 'Q'
-    short "drawLegendAllLabels"  = Just 'J'
     short "drawPalette"          = Just 'Y'
     short "drawScaleSaturation"  = Just 'V'
     short "eigenGroup"           = Just 'B'
     short "filterThresholds"     = Just 'H'
     short "labels"               = Nothing
+    short "matrixOutput"         = Nothing
     short "maxDistance"          = Just 'T'
     short "maxProportion"        = Just 'X'
     short "maxStep"              = Just 'S'
+    short "minDistance"          = Nothing
     short "minSize"              = Just 'M'
     short "noFilter"             = Just 'F'
     short "normalization"        = Just 'z'
@@ -316,6 +319,7 @@
               . unHelpful
               . eigenGroup
               $ opts
+        dense'            = DenseFlag . unHelpful . dense $ opts
         normalization'    = getNormalization opts
         numEigen'         = fmap NumEigen . unHelpful . numEigen $ opts
         minSize'          = fmap MinClusterSize . unHelpful . minSize $ opts
@@ -445,9 +449,10 @@
     -- Load previous results or calculate results if first run.
     originalClusterResults <- case prior' of
         Nothing -> do
-            let (fullCr, _) = hSpecClust eigenGroup' normalization' numEigen'
-                            . extractSc
-                            $ processedSc
+            let (fullCr, _) =
+                  hSpecClust dense' eigenGroup' normalization' numEigen'
+                    . extractSc
+                    $ processedSc
 
             return fullCr :: IO ClusterResults
         (Just x) -> do
diff --git a/src/TooManyCells/MakeTree/Cluster.hs b/src/TooManyCells/MakeTree/Cluster.hs
--- a/src/TooManyCells/MakeTree/Cluster.hs
+++ b/src/TooManyCells/MakeTree/Cluster.hs
@@ -52,12 +52,14 @@
 import qualified Data.Text as T
 import qualified Data.Vector as V
 import qualified Data.Vector.Unboxed as VU
+import qualified Math.Clustering.Hierarchical.Spectral.Dense as HSD
 import qualified Numeric.LinearAlgebra as H
 
 -- Local
 import TooManyCells.MakeTree.Adjacency
 import TooManyCells.MakeTree.Types
 import TooManyCells.Matrix.Types
+import TooManyCells.Matrix.Utility
 import TooManyCells.Diversity.Types
 
 -- | Cluster cLanguage.R.QQ (r)olumns of a sparse matrix using HDBSCAN.
@@ -129,12 +131,13 @@
         $ (R.fromSomeSEXP clusters :: [Int32])
 
 -- | Hierarchical spectral clustering.
-hSpecClust :: EigenGroup
+hSpecClust :: DenseFlag
+           -> EigenGroup
            -> NormType
            -> Maybe NumEigen
            -> SingleCells
            -> (ClusterResults, ClusterGraph CellInfo)
-hSpecClust eigenGroup norm numEigen sc =
+hSpecClust (DenseFlag isDense) eigenGroup norm numEigen sc =
     ( ClusterResults { _clusterList = clustering
                      , _clusterDend = dendToTree dend
                      }
@@ -152,8 +155,7 @@
             $ gr
     gr         = dendrogramToGraph dend
     dend       = clusteringTreeToDendrogram tree
-    tree       = hSpecCommand norm
-               . Left
+    tree       = hSpecCommand norm isDense
                . unMatObsRow
                . _matrix
                $ sc
@@ -161,7 +163,7 @@
                     (\x y -> CellInfo x y)
                     (_rowNames sc)
                     (fmap Row . flip V.generate id . V.length . _rowNames $ sc)
-    hSpecCommand TfIdfNorm   =
+    hSpecCommand TfIdfNorm False =
         hierarchicalSpectralCluster
           eigenGroup
           True
@@ -169,7 +171,8 @@
           Nothing
           Nothing
           items
-    hSpecCommand BothNorm =
+        . Left
+    hSpecCommand BothNorm False =
         hierarchicalSpectralCluster
           eigenGroup
           True
@@ -177,7 +180,8 @@
           Nothing
           Nothing
           items
-    hSpecCommand _        =
+        . Left
+    hSpecCommand _ False =
         hierarchicalSpectralCluster
           eigenGroup
           False
@@ -185,6 +189,37 @@
           Nothing
           Nothing
           items
+        . Left
+    hSpecCommand TfIdfNorm True =
+        HSD.hierarchicalSpectralCluster
+          eigenGroup
+          True
+          (fmap unNumEigen numEigen)
+          Nothing
+          Nothing
+          items
+        . Left
+        . sparseToHMat
+    hSpecCommand BothNorm True =
+        HSD.hierarchicalSpectralCluster
+          eigenGroup
+          True
+          (fmap unNumEigen numEigen)
+          Nothing
+          Nothing
+          items
+        . Left
+        . sparseToHMat
+    hSpecCommand _ True =
+        HSD.hierarchicalSpectralCluster
+          eigenGroup
+          False
+          (fmap unNumEigen numEigen)
+          Nothing
+          Nothing
+          items
+        . Left
+        . sparseToHMat
 
 dendrogramToClusterList :: HC.Dendrogram (V.Vector CellInfo)
                         -> [(CellInfo, [Cluster])]
diff --git a/src/TooManyCells/MakeTree/Types.hs b/src/TooManyCells/MakeTree/Types.hs
--- a/src/TooManyCells/MakeTree/Types.hs
+++ b/src/TooManyCells/MakeTree/Types.hs
@@ -51,6 +51,8 @@
     { unPreNormalization :: Bool
     } deriving (Read,Show)
 newtype IsLeaf = IsLeaf {unIsLeaf :: Bool} deriving (Eq, Ord, Read, Show)
+newtype DenseFlag = DenseFlag { unDenseFlag :: Bool }
+                    deriving (Eq, Ord, Read, Show)
 newtype AdjacencyMat = AdjacencyMat
     { unAdjacencyMat :: H.Matrix H.R
     } deriving (Read,Show)
diff --git a/src/TooManyCells/Matrix/Utility.hs b/src/TooManyCells/Matrix/Utility.hs
--- a/src/TooManyCells/Matrix/Utility.hs
+++ b/src/TooManyCells/Matrix/Utility.hs
@@ -83,7 +83,7 @@
 sparseToHMat :: S.SpMatrix Double -> H.Matrix H.R
 sparseToHMat mat = H.assoc (S.dimSM mat) 0
                  . fmap (\(!x, !y, !z) -> ((x, y), z))
-                 . S.toDenseListSM
+                 . S.toListSM
                  $ mat
 
 -- | Convert a sparse matrix to an hmatrix.
diff --git a/too-many-cells.cabal b/too-many-cells.cabal
--- a/too-many-cells.cabal
+++ b/too-many-cells.cabal
@@ -1,9 +1,9 @@
 cabal-version: >=1.10
 name: too-many-cells
-version: 0.1.0.1
+version: 0.1.1.0
 license: GPL-3
 license-file: LICENSE
-copyright: 2018 Gregory W. Schwartz
+copyright: 2019 Gregory W. Schwartz
 maintainer: gsch@pennmedicine.upenn.edu
 author: Gregory W. Schwartz
 homepage: http://github.com/GregorySchwartz/too-many-cells#readme
@@ -65,14 +65,14 @@
         foldl >=1.4.2,
         graphviz >=2999.20.0.2,
         hierarchical-clustering >=0.4.6,
-        hierarchical-spectral-clustering >=0.2.1.0,
+        hierarchical-spectral-clustering >=0.2.2.0,
         hmatrix >=0.19.0.0,
         inline-r >=0.9.2,
         lens >=4.16.1,
         managed >=1.0.6,
         matrix-market-attoparsec >=0.1.0.8,
         mltool >=0.2.0.1,
-        modularity >=0.2.0.3,
+        modularity >=0.2.1.0,
         mtl >=2.2.2,
         palette >=0.3.0.1,
         parallel >=3.2.1.1,
@@ -113,7 +113,7 @@
         filepath >=1.4.2,
         find-clumpiness >=0.2.3.1,
         graphviz >=2999.20.0.2,
-        hierarchical-spectral-clustering >=0.2.1.0,
+        hierarchical-spectral-clustering >=0.2.2.0,
         inline-r >=0.9.2,
         lens >=4.16.1,
         matrix-market-attoparsec >=0.1.0.8,
@@ -121,7 +121,7 @@
         optparse-generic >=1.3.0,
         palette >=0.3.0.1,
         plots >=0.1.0.2,
-        spectral-clustering >=0.2.1.2,
+        spectral-clustering >=0.2.2.3,
         streaming >=0.2.1.0,
         streaming-bytestring >=0.1.6,
         streaming-utils >=0.1.4.7,
