{- Math.Elbow
Gregory W. Schwartz

Collects the functions pertaining to the blah.
-}

{-# LANGUAGE TupleSections #-}

module Math.Elbow
  ( findElbow
  , findElbowList
  , getRotationAngle
  , MinMax (..)
  ) where

-- Remote
import Safe (headMay, atMay)
import qualified Numeric.LinearAlgebra as H

-- Local


data MinMax = Min | Max deriving (Read, Show)

-- | Convert a list to a tuple.
listToTuple :: [a] -> Maybe (a, a)
listToTuple [x, y] = Just (x, y)
listToTuple _      = Nothing

-- | Use the minimum and maximum points to identify the appropriate rotation in
-- radians. Matrix rows are observations, columns are dimensions.
getRotationAngle :: (RealFloat a, H.Numeric a) => H.Matrix a -> Maybe a
getRotationAngle = fmap (uncurry atan2)
                 . listToTuple
                 . reverse
                 . fmap (\x -> H.maxElement x - H.minElement x)
                 . H.toColumns

-- | Get the elbow point (and its index) of a two-dimensional distribution. Uses
-- the Max or Min to identify convex / concave point, depending on the choice
-- (for positive x and y, top left hump would be Max, bottom right dip would be
-- Min). Matrix rows are observations, columns are dimensions.
findElbow :: (RealFloat a, H.Numeric a)
          => MinMax -> H.Matrix a -> Maybe (Int, (a, a))
findElbow minMax m = do
  theta <- getRotationAngle m

  let co = cos theta
      si = sin theta
      rot = (2 H.>< 2) [co, -si, si, co]
      rotated = m H.<> rot
      findMinMax Max = H.maxIndex
      findMinMax Min = H.minIndex

  idx <- fmap (findMinMax minMax) . flip atMay 1 . H.toColumns $ rotated

  fmap (idx,) . listToTuple . H.toList $ m H.! idx

-- | Get the elbow point (and its index) of a two-dimensional distribution. Uses
-- the Max or Min to identify convex / concave point, depending on the choice
-- (for positive x and y, top left hump would be Max, bottom right dip would be
-- Min). Matrix rows are observations, columns are dimensions. List of columns.
findElbowList :: (RealFloat a, H.Numeric a)
              => MinMax -> [[a]] -> Maybe (Int, (a, a))
findElbowList minMax = findElbow minMax . H.fromColumns . fmap H.fromList