packages feed

symtegration-0.6.1: src/Symtegration/Integration/Factor.hs

-- |
-- Module: Symtegration.Integration.Factor
-- Description: Factor a term into constant and non-constant parts.
-- Copyright: Copyright 2024 Yoo Chung
-- License: Apache-2.0
-- Maintainer: dev@chungyc.org
module Symtegration.Integration.Factor (factor, isConstant) where

import Data.Text (Text)
import Symtegration.Symbolic
import Symtegration.Symbolic.Simplify

-- $setup
-- >>> import Symtegration

-- | Factor a multiplicative term into a constant portion and the variable-dependent portion.
-- E.g., \(2a x \sin x\) into \(2a\) and \(x \sin x\) when the variable is \(x\).
--
-- >>> let s (x, y) = (toHaskell $ simplify x, toHaskell $ simplify y)
-- >>> s $ factor "x" $ 2 * ("a" * sin "x")
-- ("2 * a","sin x")
-- >>> s $ factor "x" $ "a" / "x"
-- ("a","1 / x")
--
-- Assumes algebraic ring ordering has been applied to the term.
factor ::
  -- | Symbol for the variable.
  Text ->
  -- | Term to separate into constant and non-constant portions.
  Expression ->
  (Expression, Expression)
factor _ e@(Number _) = (e, Number 1)
factor v e@(Symbol s) | v == s = (Number 1, e) | otherwise = (e, Number 1)
factor v e@(UnaryApply _ x) | isConstant v x = (e, Number 1) | otherwise = (Number 1, e)
factor v e@(x :*: (y :*: z))
  | isConstant v x, isConstant v y, isConstant v z = (e, Number 1)
  | isConstant v x, isConstant v y = (simplifyForVariable v $ x :*: (y :*: c), z')
  | isConstant v x = (simplifyForVariable v $ x :*: d, y')
  | otherwise = (Number 1, e)
  where
    (c, z') = factor v z
    (d, y') = factor v $ y :*: z
factor v e@(x :*: y)
  | isConstant v x, isConstant v y = (e, Number 1)
  | isConstant v x = (x, y)
  | otherwise = (Number 1, e)
factor v (x :/: y) = (simplify $ constX :/: constY, simplify $ varX :/: varY)
  where
    (constX, varX) = factor v x
    (constY, varY) = factor v y
factor v e | isConstant v e = (e, Number 1) | otherwise = (Number 1, e)

-- | Returns whether an expression contains the variable.
--
-- >>> isConstant "x" $ 1 + "x"
-- False
-- >>> isConstant "x" $ 1 + "a"
-- True
isConstant ::
  -- | Symbol for the variable.
  Text ->
  -- | Expression to check.
  Expression ->
  -- | Whether the expression is a constant.
  Bool
isConstant _ (Number _) = True
isConstant v (Symbol s) = s /= v
isConstant v (UnaryApply _ x) = isConstant v x
isConstant v (BinaryApply _ x y) = isConstant v x && isConstant v y