{-# LANGUAGE NoImplicitPrelude #-}
module TBit.Framework ( meshBZ
                      , meshBigBZ
                      , bzIntegral
                      , bzIntegral'
                      , bzIntegral''
                      , kPath ) where

import TBit.Types
import TBit.Parameterization
import TBit.Numerical.Integration

import Data.Foldable hiding (foldl', concatMap)
import Data.List.Stream hiding (sum)
import Data.Traversable (traverse)
import qualified Data.Map as M

import Numeric.LinearAlgebra.HMatrix (norm_2, scale, (!))

import Control.Monad (liftM)
import Control.Monad.State (get)
import Prelude.Listless 

{-|
    Given a list of points in /k/-space, return a list of
    points that interpolates affine paths between them, in
    turn; a typical usage case might be

    > kPath [gammaPoint, kPoint, mPoint, gammaPoint]

    which is used in the 'Plots.bandPlot' function. The spacing
    between points on the interpolated path is determined by
    the 'Types.meshingData' parameter.
-}
kPath :: [Wavevector] -> Parameterized ([Wavevector])
kPath []        = return []
kPath (k:[])    = return [k]
kPath (k:k':ks) = do (Spacing eps) <- getMesh
                     nextPath <- kPath (k':ks)
                     return $ [ k + scale n (dk eps)
                              | n <- takeWhile ((>) (norm_2 (k'-k)) . (*) eps)
                                               [0.0, 1.0 ..]]
                              ++ nextPath

    where dk eps = scale eps $ normalize (k'-k)
          normalize v = scale (1.0/(norm_2 v)) v

{-|
    Given a function defined on the Brillouin zone, evaluate it everywhere
    by doing nested single integrations and using Takahashi and Mori's
    Tanh-Sinh quadrature method. Should be robust against singularities
    and the like, and is /properly/ set up for massive parallelization (via
    EdwardKmett's integration library, certainly not mine). Watch Dirac run
    this thing. Only implemented in 2D.
-}
bzIntegral'' :: (Wavevector -> Parameterized Double) -> Parameterized Double
bzIntegral'' f = do (b1:b2:_) <- recipPrimitiveLattice
                    ps        <- get
                    let g s t = f (scale s b1 + scale t b2) :: Parameterized Double
                    return . (*) (jac b1 b2)
                           . fst 
                           $ integrate (\x y -> either e id 
                                               . flip crunch ps 
                                               $ g x y) (0.0,1.0) (0.0,1.0)
    where e err = error (show err)
          jac a b = abs $ (a!0) * (b!1) - (a!1) * (b!0)


{-|
    As 'bzIntegral', but also gives the absolute error as the second value
    in the returned tuple.
-}
bzIntegral' :: (Wavevector -> Parameterized Double) -> Parameterized (Double, Double)
bzIntegral' f = do (b1:b2:_) <- recipPrimitiveLattice
                   ps        <- get
                   let g s t = f (scale s b1 + scale t b2) :: Parameterized Double
                   return . (\(x,y) -> (x * (jac b1 b2), y))
                          $ integrate (\x y -> either e id 
                                              . flip crunch ps 
                                              $ g x y) (0.0,1.0) (0.0,1.0)
    where e err = error (show err)
          jac a b = abs $ (a!0) * (b!1) - (a!1) * (b!0)

{-|
    Integrates using a simple grid-sum.
-}
bzIntegral :: (Wavevector -> Parameterized Double) -> Parameterized Double
bzIntegral f = do kGrid        <- meshBZ
                  (Spacing dk) <- getMesh
                  let df = liftM ((*) dk . (*) dk) . f
                  liftM sum $! traverse df kGrid

{-|
    Returns a mesh of points within the /n/-parallelepiped subtended by the
    reciprocal lattice vectors. Covers the entire brillouin zone, though not
    in the shape you might expect, and not in a way that's pretty for graphing.
-}
meshBZ :: Parameterized (Grid Wavevector)
meshBZ = do bs <- recipPrimitiveLattice
            (Spacing dk) <- getMesh
            let frame = map (makeAxis dk) bs 
            return . foldl' (flip (uncurry M.insert)) M.empty 
                   $ populate frame

{-|
    As 'meshBZ', but with parallelepipeds extending in each quadrant, octant...
    /n/-ant of the reciprocal lattice basis. Should cover more than the entire
    first Brillouin zone.
-}
meshBigBZ :: Parameterized (Grid Wavevector)
meshBigBZ = do bs <- recipPrimitiveLattice
               (Spacing dk) <- getMesh
               let frame = map (makeDoubleAxis dk) bs 
               return . foldl' (flip (uncurry M.insert)) M.empty 
                      $ populate frame

--

makeAxis :: Double -> Wavevector -> [Wavevector]
makeAxis dk b = pos ++ [b]
    where pos = [ scale n bdk 
                | n <- takeWhile (\m -> (m*dk) < (norm_2 b)) [0..]]
          bdk = scale (dk / (norm_2 b)) b

makeDoubleAxis :: Double -> Wavevector -> [Wavevector]
makeDoubleAxis dk b = (reverse neg) ++ pos ++ [b]
    where pos = [ scale n bdk 
                | n <- takeWhile (\m -> (m*dk) < (norm_2 b)) [0..]]
          neg = [ scale n bdk 
                | n <- takeWhile (\m -> (abs $ m*dk) < (norm_2 b)) nins]
          bdk = scale (dk / (norm_2 b)) b
          nins = map negate [1..]

populate :: [[Wavevector]] -> [(GridIndex, Wavevector)]
populate [] = []
populate (ks:[])  = zipWith (\n k -> (GID [n],k)) [0..] ks
populate (ks:kss) = concatMap (\(GID i,k') -> [ (GID (j:i), k + k') 
                                              | (j,k) <- zip [0..] ks]) 
                  $ populate kss
