packages feed

brush-strokes-0.1.0.0: src/lib/Math/Monomial.hs

{-# LANGUAGE AllowAmbiguousTypes   #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE ParallelListComp      #-}
{-# LANGUAGE TemplateHaskell       #-}
{-# LANGUAGE UndecidableInstances  #-}

module Math.Monomial
  ( Mon(..)
  , MonomialBasis(..), MonomialBasisQ(..), Deg, Vars
  , zeroMonomial, isZeroMonomial
  , totalDegree, isLinear, linearMonomial

  , split, mons

  , multiSubsetSumFaà, multiSubsetsSum, multiSubsetSum
  , partitionFaà, partitions

  , prodRuleQ

  ) where

-- base
import Data.Foldable
  ( toList )
import Data.Kind
  ( Type, Constraint )
import GHC.Exts
  ( proxy# )
import GHC.TypeNats
--  ( KnownNat, Nat, natVal' )
import Unsafe.Coerce
  ( unsafeCoerce )

-- template-haskell
import Language.Haskell.TH
  ( CodeQ )

-- brush-strokes
import Math.Linear
  ( Vec(..), Fin(..) )
import TH.Utils
  ( foldQ )

--------------------------------------------------------------------------------

-- | @Mon k n@ is the set of monomials in @n@ variables of degree less than or equal to @k@.
type Mon :: Nat -> Nat -> Type
newtype Mon k n = Mon { monDegs :: Vec n Word } -- sum <= k
  deriving stock ( Show, Eq, Ord )

type Deg  :: ( Type -> Type ) -> Nat
type Vars :: ( Type -> Type ) -> Nat
type family Deg  f
type family Vars f

zeroMonomial :: forall k n. KnownNat n => Mon k n
zeroMonomial = Mon $ Vec $ replicate ( fromIntegral $ word @n ) 0
{-# INLINE zeroMonomial #-}

isZeroMonomial :: Mon k n -> Bool
isZeroMonomial ( Mon ( Vec pows ) ) = all ( == 0 ) pows
{-# INLINE isZeroMonomial #-}

totalDegree :: Mon k n -> Word
totalDegree ( Mon ( Vec pows ) ) = sum pows

linearMonomial :: forall k n. ( KnownNat n, 1 <= k ) => Fin n -> Mon k n
linearMonomial ( Fin i' ) =
  Mon $ Vec $ replicate ( i - 1 ) 0 ++ [ 1 ] ++ replicate ( n - i ) 0
  where
    n, i :: Int
    n = fromIntegral $ word @n
    i = fromIntegral i'
{-# INLINE linearMonomial #-}

isLinear :: Mon k n -> Maybe ( Fin n )
isLinear ( Mon ( Vec pows ) ) = fmap Fin $ go 1 pows
  where
    go :: Word -> [ Word ] -> Maybe Word
    go _ [] = Nothing
    go j ( i : is )
      | i == 0
      = go ( j + 1 ) is
      | i == 1 && all ( == 0 ) is
      = Just j
      | otherwise
      = Nothing

-- | Co-multiplication of monomials.
split :: Mon k n -> [ ( Mon k n, Mon k n ) ]
split ( Mon ( Vec [] ) ) = [ ( Mon ( Vec [] ), Mon ( Vec [] ) ) ]
split ( Mon ( Vec ( d : ds ) ) ) =
      [ ( Mon ( Vec ( i : as ) ), Mon ( Vec ( ( ( d - i ) : bs ) ) ) )
      | i <- [ 0 .. d ]
      , ( Mon ( Vec as ), Mon ( Vec bs ) ) <- ( split ( Mon ( Vec ds ) ) )
      ]

-- | All monomials of degree less than or equal to @k@ in @n@ variables,
-- in lexicographic order.
mons :: forall k n. ( KnownNat n ) => Word -> [ Mon k n ]
mons k = unsafeCoerce ( mons' k ( word @n ) )

mons' :: Word -> Word -> [ [ Word ] ]
mons' k _ | k < 0 = []
mons' _ 0 = [ [] ]
mons' 0 n = [ replicate ( fromIntegral n ) 0 ]
mons' k n = [ i : is | i <- reverse [ 0 .. k ], is <- mons' ( k - i ) ( n - 1 ) ]

subs :: Mon k n -> [ ( Mon k n, Word ) ]
subs ( Mon ( Vec []) ) = [ ( Mon ( Vec [] ), maxBound ) ]
subs ( Mon ( Vec ( i : is ) ) )
  = [ ( Mon ( Vec ( j : js ) )
      , if j == 0 then mult else min ( i `quot` j ) mult )
    | j <- [ 0 .. i ]
    , ( Mon ( Vec js ), mult ) <- subs ( Mon ( Vec is ) )
    ]

word :: forall n. KnownNat n => Word
word = fromIntegral $ natVal' @n proxy#

-- | The factorial function \( n! = n \cdot (n-1) \cdot `ldots` \cdot 2 `cdot` 1 \).
factorial :: Word -> Word
factorial i = product [ 1 .. i ]

vecFactorial :: Vec n Word -> Word
vecFactorial ( Vec [] ) = 1
vecFactorial ( Vec ( i : is ) ) = factorial i * vecFactorial ( Vec is )

--------------------------------------------------------------------------------
-- Computations for the chain rule R^1 -> R^n -> R^m

-- | Faà di Bruno coefficient (naive implementation).
multiSubsetSumFaà :: Word -> Vec n [ ( Word, Word ) ] -> Word
multiSubsetSumFaà k multisubsets =
  factorial k `div`
    product [ factorial p * ( factorial i ) ^ p
            | multisubset <- toList multisubsets
            , ( i, p ) <- multisubset ]

-- | Computes the multisubsets of the given set which have the specified sum
-- and number of elements.
multiSubsetSum :: Word -- ^ size of multisubset
               -> Word -- ^ desired sum
               -> [ Word ] -- ^ set to pick from
               -> [ [ ( Word, Word ) ] ]
multiSubsetSum 0 0 _  = [ [] ]
multiSubsetSum 0 _ _  = []
multiSubsetSum _ _ [] = []
multiSubsetSum n s ( i : is ) =
  [ if j == 0 then js else ( i, j ) : js
  | j <- [ 0 .. n ]
  , js <- multiSubsetSum ( n - j ) ( s - i * j ) is
  ]

-- | @multiSubsetsSum is s ns@ computes all collection of multisubsets of @is@,
-- with sizes specified by @ns@, such that the total sum is @s@.
multiSubsetsSum :: forall n
                .  [ Word ]   -- ^ set to pick from
                -> Word       -- ^ desired total sum
                -> Vec n Word -- ^ sizes of each multisets
                -> [ Vec n [ ( Word, Word ) ] ]
multiSubsetsSum is = goMSS
  where
    goMSS :: forall i. Word -> Vec i Word -> [ Vec i [ ( Word, Word ) ] ]
    goMSS 0 ( Vec [] ) = [ Vec [] ]
    goMSS _ ( Vec [] ) = [ ]
    goMSS s ( Vec ( n : ns ) ) =
      [ Vec ( multi : rest )
      | s_i <- [ n * i_min .. s ]
      , multi <- multiSubsetSum n s_i is
      , Vec rest <- goMSS ( s - s_i ) ( Vec ns ) ]
    i_min = case is of
      [] -> 0
      _  -> max 0 $ minimum is

--------------------------------------------------------------------------------
-- Computations for the chain rule R^n -> R^1 -> R^1

partitionFaà :: Mon k n -> [ ( Mon k n, Word ) ] -> Word
partitionFaà ( Mon mon ) multiIndexes =
  vecFactorial mon `div`
    product [ factorial p * ( vecFactorial i ) ^ p
            | ( Mon i, p ) <- toList multiIndexes ]

-- | @partitions p mon@ computes all partitions of the monomial @mon@ into
-- @p@ (non-zero) parts, allowing repetition.
partitions :: forall k n
           .  Word    -- ^ number of parts
           -> Mon k n -- ^ monomial to sum to
           -> [ [ ( Mon k n, Word ) ] ]
partitions n_parts0 mon0 = go n_parts0 Nothing mon0
  where
    go :: Word -> Maybe ( Mon k n ) -> Mon k n -> [ [ ( Mon k n , Word ) ] ]
    go 0 _ mon
      | isZeroMonomial mon
      = [ [] ]
      | otherwise
      = []
    go 1 mbMaxMon mon
      |  isZeroMonomial mon
      || case mbMaxMon of { Just maxMon -> mon >= maxMon ; _ -> False }
      = []
      | otherwise
      = [ [ ( mon, 1 ) ] ]
    go n_parts mbMaxMon mon
      = [ ( part, l ) : parts
        | ( part, l_max ) <- subs mon
        , not ( isZeroMonomial part ) -- parts must be non-empty
        -- use a total ordering on monomials to ensure uniqueness
        , case mbMaxMon of
            Just maxMon -> part < maxMon
            Nothing     -> True
        , l <- [ 1 .. min n_parts l_max ]
        , parts <- go ( n_parts - l ) ( Just part ) ( subPart l mon part )
        ]

subPart :: Word -> Mon k n -> Mon k n -> Mon k n
subPart = unsafeCoerce subPart'

subPart' :: Word -> [ Word ] -> [ Word ] -> [ Word ]
subPart' m = zipWith ( \ i j -> i - m * j )

--------------------------------------------------------------------------------

-- | @'MonomialBasis' f@ exhibits @f u@ as a free @r@-module with basis the
-- monomials in @Vars u@ variables, of degree up to (and including) @Deg u@.
type MonomialBasis :: ( Type -> Type ) -> Constraint
class MonomialBasis f where
  monTabulate :: ( ( Mon ( Deg f ) ( Vars f ) ) -> u ) -> f u
  monIndex :: f u -> Mon ( Deg f ) ( Vars f ) -> u

-- | @'MonomialBasisQ' f@ exhibits @f u@ as a free @r@-module with basis the
-- monomials in @Vars u@ variables, of degree up to (and including) @Deg u@,
-- at compile-time (to be spliced in using TemplateHaskell).
type MonomialBasisQ :: ( Type -> Type ) -> Constraint
class MonomialBasisQ f where
  monTabulateQ :: ( ( Mon ( Deg f ) ( Vars f ) ) -> CodeQ u ) -> CodeQ ( f u )
  monIndexQ :: CodeQ ( f u ) -> Mon ( Deg f ) ( Vars f ) -> CodeQ u

--------------------------------------------------------------------------------

prodRuleQ :: forall f r. MonomialBasisQ f
          => CodeQ r -> CodeQ ( r -> r -> r ) -> CodeQ ( r -> r -> r )
            -- Ring r constraint (circumvent TH constraint problem)
          -> CodeQ ( f r ) -> CodeQ ( f r ) -> CodeQ ( f r )
prodRuleQ zero plus times df1 df2 =
  monTabulateQ @f \ mon ->
    [|| $$( foldQ plus zero
           [ [|| $$times $$( monIndexQ @f df1 m1 ) $$( monIndexQ @f df2 m2 ) ||]
           | (m1, m2) <- split mon
           ] )
    ||]