packages feed

symtegration-0.6.1: src/Symtegration/Polynomial/Indexed.hs

-- |
-- Module: Symtegration.Polynomial.Indexed
-- Description: A polynomial representation mapping the power of each term to its coefficient.
-- Copyright: Copyright 2025 Yoo Chung
-- License: Apache-2.0
-- Maintainer: dev@chungyc.org
module Symtegration.Polynomial.Indexed
  ( IndexedPolynomial,
    IndexedSymbolicPolynomial,
    IndexedPolynomialWith,
  )
where

import Data.IntMap (IntMap)
import Data.IntMap qualified as IntMap
import Data.List (intersperse)
import Data.Maybe (fromMaybe)
import Data.Ratio (denominator, numerator)
import Data.Text (unpack)
import Symtegration.Polynomial
import Symtegration.Symbolic
import TextShow

-- | Polynomial representation which maps the power of each term to its coefficient.
-- Exponents are represented with 'Int', while coefficients are represented with 'Rational'.
-- It is an instance of the 'Polynomial' type class.
type IndexedPolynomial = IndexedPolynomialWith Rational

-- | Polynomial representation which maps the power of each term to its coefficient.
-- Exponents are represented with 'Int', while coefficients are represented with 'Expression'.
-- It is an instance of the 'Polynomial' type class.
type IndexedSymbolicPolynomial = IndexedPolynomialWith Expression

-- | Polynomial representation which maps the power of each term to its coefficient.
-- Exponents are represented with 'Int'.  Coefficients have a type as specified by the type parameter.
-- These types are an instance of the 'Polynomial' type class.
type IndexedPolynomialWith a = P Int a

-- | Type with two type parameters so that it can be an instance of 'Polynomial'.
-- The first type parameter is not involved in the data constructor;
-- it is used to set the exponent type for 'Polynomial'.
newtype P a b = P (IntMap b) deriving (Eq)

instance Show (P Int Rational) where
  show = unpack . showt

instance TextShow (P Int Rational) where
  showb (P m)
    | IntMap.null m = "0"
    | otherwise =
        mconcat $
          intersperse " + " $
            map showTerm $
              IntMap.toDescList m
    where
      showTerm (0, 1) = "1"
      showTerm (0, c) = showCoefficient c
      showTerm (1, c) = showCoefficient c <> "x"
      showTerm (e, 1) = "x^" <> showb e
      showTerm (e, c) = showCoefficient c <> "x^" <> showb e
      showCoefficient r
        | 1 <- r = mempty
        | 1 <- denominator r, r > 0 = showb $ numerator r
        | 1 <- denominator r, r < 0 = showbParen True $ showb $ numerator r
        | otherwise = showbParen True $ showb r

instance (Polynomial p e c, TextShow (p e c)) => Show (IndexedPolynomialWith (p e c)) where
  show = unpack . showt

instance (Polynomial p e c, TextShow (p e c)) => TextShow (IndexedPolynomialWith (p e c)) where
  showb (P m)
    | IntMap.null m = "0"
    | otherwise = showb $ IntMap.toList m

instance Show (P Int Expression) where
  show = unpack . showt

instance TextShow (P Int Expression) where
  showb (P m)
    | IntMap.null m = "0"
    | otherwise = showb $ IntMap.toList m

instance (Eq a, Num a) => Num (P Int a) where
  (P p) + (P q) = P $ filterNonzero $ IntMap.unionWith (+) p q

  (P p) * (P q) = P $ filterNonzero $ IntMap.foldlWithKey' accumulate IntMap.empty p
    where
      accumulate m e c = IntMap.unionWith (+) m $ multiplyTerm e c
      multiplyTerm e c = IntMap.mapKeysMonotonic (+ e) $ IntMap.map (* c) q

  abs = id
  signum 0 = 0
  signum _ = 1
  fromInteger 0 = P IntMap.empty
  fromInteger n = P $ IntMap.singleton 0 $ fromInteger n
  negate (P m) = P $ IntMap.map negate m

-- | Get rid of zero coefficients to ensure that zero coefficients do not cause
-- two polynomials represented by an 'IntMap' are not considered different.
filterNonzero :: (Eq a, Num a) => IntMap a -> IntMap a
filterNonzero = IntMap.filter (/= 0)

instance (Eq a, Num a) => Polynomial P Int a where
  degree (P m) = maybe 0 fst $ IntMap.lookupMax m
  coefficient (P m) k = fromMaybe 0 $ IntMap.lookup k m
  leadingCoefficient (P m) = maybe 0 snd $ IntMap.lookupMax m
  deleteLeadingTerm (P m) = P $ IntMap.deleteMax m
  foldTerms f (P m) = IntMap.foldMapWithKey f m
  scale 0 _ = P IntMap.empty
  scale x (P m) = P $ IntMap.map (* x) m
  power n = P $ IntMap.singleton (fromIntegral n) 1