symtegration-0.6.1: src/Symtegration/Polynomial/Symbolic.hs
-- |
-- Module: Symtegration.Polynomial.Symbolic
-- Description: Conversion between data structures storing general mathematical expressions and those specialized for storing polynomials.
-- Copyright: Copyright 2024 Yoo Chung
-- License: Apache-2.0
-- Maintainer: dev@chungyc.org
module Symtegration.Polynomial.Symbolic
( -- * Converting expression to polynomial
fromExpression,
forVariable,
withSymbolicCoefficients,
-- * Converting polynomial to expression
toExpression,
toRationalCoefficient,
toSymbolicCoefficient,
)
where
import Data.Maybe (fromMaybe)
import Data.Monoid (Sum (..))
import Data.Ratio (denominator, numerator)
import Data.Text (Text)
import Symtegration.Polynomial
import Symtegration.Symbolic
-- $setup
-- >>> import Symtegration
-- >>> import Symtegration.Polynomial
-- >>> import Symtegration.Polynomial.Indexed
-- | Converts an 'Expression' into a 'Polynomial'.
-- 'Nothing' will be returned if the conversion is not possible.
--
-- Specify the symbol representing the variable for the polynomial with 'forVariable'.
-- For example,
--
-- >>> fromExpression (forVariable "x") (("x" + 4) ** 3) :: Maybe IndexedPolynomial
-- Just x^3 + 12x^2 + 48x + 64
--
-- By default, symbols other than the variable for the polynomial are not allowed.
-- To use symbols representing constants, use 'withSymbolicCoefficients' as well.
-- Note that the polynomial type the expression is being converted into
-- must be able to handle symbolic mathematical expressions for the coefficients.
-- For example,
--
-- >>> let expr = ("a" + "b") * "x" + "c" :: Expression
-- >>> let (Just p) = fromExpression (withSymbolicCoefficients (forVariable "x")) expr :: Maybe IndexedSymbolicPolynomial
-- >>> toHaskell $ simplify $ coefficient p 1
-- "a + b"
--
-- The expressions which can be converted must only use 'negate', '(+)', '(*)', '(-)',
-- '(/)' with only numbers, coefficients which do not contain the variable,
-- '(**)' with a non-negative integral exponent, and expressions formed thereof.
fromExpression ::
(Polynomial p e c, Num (p e c), Fractional c) =>
(Text -> Maybe (p e c), Expression -> Maybe c) ->
Expression ->
Maybe (p e c)
fromExpression _ (Number n) = Just $ fromInteger n
fromExpression (cf, _) (Symbol x) = cf x
fromExpression t (Negate' x) = negate <$> fromExpression t x
fromExpression t (x :+: y) = (+) <$> fromExpression t x <*> fromExpression t y
fromExpression t (x :*: y) = (*) <$> fromExpression t x <*> fromExpression t y
fromExpression t (x :-: y) = (-) <$> fromExpression t x <*> fromExpression t y
fromExpression t (x :**: (Number n))
| n >= 0 = (^ n) <$> fromExpression t x
| otherwise = Nothing
fromExpression _ (_ :**: _) = Nothing
fromExpression _ (_ :/: Number 0) = Nothing
fromExpression _ (Number n :/: Number m) = Just $ scale r 1
where
r = fromInteger n / fromInteger m
fromExpression (_, eval) e
| Just e' <- eval e = Just $ scale e' 1
| otherwise = Nothing
-- | Specifies the symbol representing the variable for 'fromExpression'.
forVariable ::
(Polynomial p e c, Num (p e c), Fractional c) =>
Text ->
(Text -> Maybe (p e c), Expression -> Maybe c)
forVariable v = (fromSymbol, toCoefficient)
where
fromSymbol s
| v == s = Just $ power 1
| otherwise = Nothing
toCoefficient (Symbol _) = Nothing
toCoefficient (Number n) = Just $ fromInteger n
toCoefficient (Negate' x) = negate <$> toCoefficient x
toCoefficient (Abs' x) = abs <$> toCoefficient x
toCoefficient (Signum' x) = signum <$> toCoefficient x
toCoefficient (x :+: y) = (+) <$> toCoefficient x <*> toCoefficient y
toCoefficient (x :*: y) = (*) <$> toCoefficient x <*> toCoefficient y
toCoefficient (x :-: y) = (-) <$> toCoefficient x <*> toCoefficient y
toCoefficient (_ :/: 0) = Nothing
toCoefficient (x :/: y) = (/) <$> toCoefficient x <*> toCoefficient y
toCoefficient (x :**: (Number n)) = (^^ n) <$> toCoefficient x
toCoefficient _ = Nothing
-- | Specifies that non-variable symbols are allowed for 'fromExpression'.
-- The coefficients will be represented by 'Expression' values.
withSymbolicCoefficients ::
(Polynomial p e Expression, Num (p e Expression), Integral e) =>
(Text -> Maybe (p e Expression), Expression -> Maybe Expression) ->
(Text -> Maybe (p e Expression), Expression -> Maybe Expression)
withSymbolicCoefficients (fromSymbol, _) = (fromSymbol', toCoefficient)
where
fromSymbol' s = Just $ fromMaybe (scale (Symbol s) 1) (fromSymbol s)
toCoefficient e@(Symbol s)
| Nothing <- fromSymbol s = Just e
| otherwise = Nothing
toCoefficient e@(Number _) = Just e
toCoefficient (UnaryApply func x) = UnaryApply func <$> toCoefficient x
toCoefficient (BinaryApply func x y) = BinaryApply func <$> x' <*> y'
where
x' = toCoefficient x
y' = toCoefficient y
-- | Converts a 'Polynomial' into an 'Expression'.
-- The symbol which will represent the variable is the first argument.
--
-- How the coefficients are converted must also be specified.
-- To evaluate the coefficients to an exact rational number,
-- use 'toRationalCoefficient'. For example,
--
-- >>> let (Just p) = fromExpression (forVariable "x") (3 * "x"**4 + 1) :: Maybe IndexedPolynomial
-- >>> toHaskell $ simplify $ toExpression "x" toRationalCoefficient p
-- "1 + 3 * x ** 4"
--
-- To evaluate the coefficients symbolically, use 'toSymbolicCoefficient'.
--
-- >>> let (Just p) = fromExpression (withSymbolicCoefficients (forVariable "x")) (("a"+"b") * "x"**4 + 1) :: Maybe IndexedSymbolicPolynomial
-- >>> toHaskell $ simplify $ toExpression "x" toSymbolicCoefficient p
-- "1 + x ** 4 * (a + b)"
toExpression :: (Polynomial p e c) => Text -> (c -> Expression) -> p e c -> Expression
toExpression x cf p = getSum $ foldTerms convert p
where
convert 0 c = Sum $ cf c
convert e c = Sum $ cf c * xp
where
xp = Symbol x ** Number (fromIntegral e)
-- | Specifies that coefficients are numbers for 'toExpression'.
toRationalCoefficient :: (Real c) => c -> Expression
toRationalCoefficient c
| d == 1 = Number n
| otherwise = Number n :/: Number d
where
r = toRational c
n = fromInteger $ numerator r
d = fromInteger $ denominator r
-- | Specifies that coefficients are symbolic for 'toExpression'.
toSymbolicCoefficient :: Expression -> Expression
toSymbolicCoefficient = id