summaryrefslogtreecommitdiff
path: root/macros/luatex/latex/luacas/tex/core
diff options
context:
space:
mode:
Diffstat (limited to 'macros/luatex/latex/luacas/tex/core')
-rw-r--r--macros/luatex/latex/luacas/tex/core/_init.lua16
-rw-r--r--macros/luatex/latex/luacas/tex/core/atomicexpression.lua70
-rw-r--r--macros/luatex/latex/luacas/tex/core/binaryoperation.lua800
-rw-r--r--macros/luatex/latex/luacas/tex/core/binaryoperation/difference.lua14
-rw-r--r--macros/luatex/latex/luacas/tex/core/binaryoperation/power.lua169
-rw-r--r--macros/luatex/latex/luacas/tex/core/binaryoperation/product.lua231
-rw-r--r--macros/luatex/latex/luacas/tex/core/binaryoperation/quotient.lua14
-rw-r--r--macros/luatex/latex/luacas/tex/core/binaryoperation/sum.lua226
-rw-r--r--macros/luatex/latex/luacas/tex/core/compoundexpression.lua52
-rw-r--r--macros/luatex/latex/luacas/tex/core/constantexpression.lua62
-rw-r--r--macros/luatex/latex/luacas/tex/core/expression.lua280
-rw-r--r--macros/luatex/latex/luacas/tex/core/functionexpression.lua295
-rw-r--r--macros/luatex/latex/luacas/tex/core/symbolexpression.lua132
13 files changed, 2361 insertions, 0 deletions
diff --git a/macros/luatex/latex/luacas/tex/core/_init.lua b/macros/luatex/latex/luacas/tex/core/_init.lua
new file mode 100644
index 0000000000..cfd640a214
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/core/_init.lua
@@ -0,0 +1,16 @@
+-- Loads core files in the correct order.
+
+require("core.expression")
+require("core.atomicexpression")
+require("core.compoundexpression")
+require("core.constantexpression")
+require("core.symbolexpression")
+require("core.binaryoperation")
+require("core.functionexpression")
+
+
+require("core.binaryoperation.power")
+require("core.binaryoperation.product")
+require("core.binaryoperation.sum")
+require("core.binaryoperation.quotient")
+require("core.binaryoperation.difference") \ No newline at end of file
diff --git a/macros/luatex/latex/luacas/tex/core/atomicexpression.lua b/macros/luatex/latex/luacas/tex/core/atomicexpression.lua
new file mode 100644
index 0000000000..6cc6e4f58e
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/core/atomicexpression.lua
@@ -0,0 +1,70 @@
+--- @class AtomicExpression
+--- Interface for an atomic mathematical expression that has no sub-expressions.
+AtomicExpression = {}
+__AtomicExpression = {}
+
+
+----------------------
+-- Required methods --
+----------------------
+
+--- Converts an atomic expression to its equivalent compound expression, if it has one.
+--- @return Expression
+function AtomicExpression:tocompoundexpression()
+ return self
+end
+
+----------------------
+-- Instance methods --
+----------------------
+
+--- @return AtomicExpression
+function AtomicExpression:evaluate()
+ return self
+end
+
+--- @return AtomicExpression
+function AtomicExpression:autosimplify()
+ return self
+end
+
+--- @return table<number, Expression>
+function AtomicExpression:subexpressions()
+ return {}
+end
+
+--- @param subexpressions table<number, Expression>
+--- @return AtomicExpression
+function AtomicExpression:setsubexpressions(subexpressions)
+ return self
+end
+
+--- @param map table<Expression, Expression>
+--- @return Expression
+function AtomicExpression:substitute(map)
+ for expression, replacement in pairs(map) do
+ if self == expression then
+ return replacement
+ end
+ end
+ return self
+end
+
+--- @return boolean
+function AtomicExpression:isatomic()
+ return true
+end
+
+--- @return string
+function AtomicExpression:tolatex()
+ -- Most atomic expressions should have the same __tostring as LaTeX's output
+ return tostring(self)
+end
+
+
+-----------------
+-- Inheritance --
+-----------------
+
+__AtomicExpression.__index = Expression
+AtomicExpression = setmetatable(AtomicExpression, __AtomicExpression) \ No newline at end of file
diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation.lua
new file mode 100644
index 0000000000..708a6c1540
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/core/binaryoperation.lua
@@ -0,0 +1,800 @@
+--- @class BinaryOperation
+--- Represents a binary operation with two inputs and one output.
+--- Represents a generic function that takes zero or more expressions as inputs.
+--- @field name string
+--- @field operation function
+--- @field expressions table<number, Expression>
+BinaryOperation = {}
+__BinaryOperation = {}
+
+----------------------------
+-- Instance functionality --
+----------------------------
+
+--- Creates a new binary operation with the given operation.
+--- @param operation function
+--- @param expressions table<number, Expression>
+--- @return BinaryOperation
+function BinaryOperation:new(operation, expressions)
+ local o = {}
+ local __o = Copy(__ExpressionOperations)
+
+ if type(operation) ~= "function" then
+ error("Sent parameter of wrong type: operation must be a function")
+ end
+
+ if type(expressions) ~= "table" then
+ error("Sent parameter of wrong type: expressions must be an array")
+ end
+
+ o.name = BinaryOperation.DEFAULT_NAMES[operation]
+ o.operation = operation
+ o.expressions = Copy(expressions)
+
+ if BinaryOperation.COMMUTATIVITY[operation] then
+ function o:iscommutative()
+ return true
+ end
+ else
+ function o:iscommutative()
+ return false
+ end
+ end
+
+ if not o:iscommutative() and o.operation ~= BinaryOperation.SUB and #o.expressions ~= 2 then
+ error("Sent parameter of wrong type: noncommutative operations cannot have an arbitrary number of paramaters")
+ end
+
+ __o.__index = BinaryOperation
+ __o.__tostring = function(a)
+ local expressionnames = ''
+ for index, expression in ipairs(a.expressions) do
+ if index == 1 and not a.expressions[index + 1] then
+ expressionnames = expressionnames .. a.name .. ' '
+ end
+ if index > 1 then
+ expressionnames = expressionnames .. ' '
+ end
+ if expression:isatomic() and not (a.operation == BinaryOperation.POW and expression:type() == Rational) then
+ expressionnames = expressionnames .. tostring(expression)
+ else
+ expressionnames = expressionnames .. '(' .. tostring(expression) .. ')'
+ end
+ if a.expressions[index + 1] then
+ expressionnames = expressionnames .. ' ' .. a.name
+ end
+ end
+ return expressionnames
+ end
+ __o.__eq = function(a, b)
+ -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always runs this anyway
+ if not a.operation or not b.operation then
+ return false
+ end
+ local loc = 1
+ while a.expressions[loc] or b.expressions[loc] do
+ if not a.expressions[loc] or not b.expressions[loc] or
+ (a.expressions[loc] ~= b.expressions[loc]) then
+ return false
+ end
+ loc = loc + 1
+ end
+ return a.operation == b.operation
+ end
+ o = setmetatable(o, __o)
+
+ return o
+end
+
+--- @return Expression
+function BinaryOperation:evaluate()
+ local results = {}
+ local reducible = true
+ for index, expression in ipairs(self:subexpressions()) do
+ results[index] = expression:evaluate()
+ if not results[index]:isconstant() then
+ reducible = false
+ end
+ end
+ if not reducible then
+ return BinaryOperation(self.operation, results)
+ end
+
+ if not self.expressions[1] then
+ error("Execution error: cannot perform binary operation on zero expressions")
+ end
+
+ local result = results[1]
+ for index, expression in ipairs(results) do
+ if not (index == 1) then
+ result = self.operation(result, expression)
+ end
+ end
+ return result
+end
+
+--- @return Expression
+function BinaryOperation:autosimplify()
+ local results = {}
+ for index, expression in ipairs(self:subexpressions()) do
+ results[index] = expression:autosimplify()
+ end
+ local simplified = BinaryOperation(self.operation, results)
+ if simplified.operation == BinaryOperation.POW then
+ return simplified:simplifypower()
+ end
+ if simplified.operation == BinaryOperation.MUL then
+ return simplified:simplifyproduct()
+ end
+ if simplified.operation == BinaryOperation.ADD then
+ return simplified:simplifysum()
+ end
+ if simplified.operation == BinaryOperation.DIV then
+ return simplified:simplifyquotient()
+ end
+ if simplified.operation == BinaryOperation.SUB then
+ return simplified:simplifydifference()
+ end
+ return simplified
+end
+
+--- @return table<number, Expression>
+function BinaryOperation:subexpressions()
+ return self.expressions
+end
+
+--- @param subexpressions table<number, Expression>
+--- @return BinaryOperation
+function BinaryOperation:setsubexpressions(subexpressions)
+ return BinaryOperation(self.operation, subexpressions)
+end
+
+--- @return Expression
+function BinaryOperation:expand()
+ local results = {}
+ for index, expression in ipairs(self:subexpressions()) do
+ results[index] = expression:expand()
+ end
+ local expanded = BinaryOperation(self.operation, results)
+ if expanded.operation == BinaryOperation.MUL then
+ local allsums = BinaryOperation(BinaryOperation.ADD, {Integer.one()})
+ for _, expression in ipairs(expanded.expressions) do
+ allsums = allsums:expand2(expression)
+ end
+ return allsums:autosimplify()
+ end
+ if expanded.operation == BinaryOperation.POW and expanded.expressions[2]:type() == Integer then
+ if expanded.expressions[1]:type() ~= BinaryOperation then
+ return expanded:autosimplify()
+ end
+ local exp = BinaryOperation.MULEXP({Integer.one()})
+ local pow = expanded.expressions[2]:asnumber()
+ for _ = 1, math.abs(pow) do
+ exp = exp:expand2(expanded.expressions[1])
+ if _ > 1 then
+ exp = exp:autosimplify()
+ end
+ end
+ if pow < 0 then
+ exp = exp^Integer(-1)
+ end
+ return exp
+ end
+ if expanded.operation == BinaryOperation.POW and expanded.expressions[2].operation == BinaryOperation.ADD then
+ local exp = {}
+ for i = 1, #expanded.expressions[2].expressions do
+ exp[#exp+1] = (expanded.expressions[1]^expanded.expressions[2].expressions[i]):autosimplify()
+ end
+ return BinaryOperation.MULEXP(exp)
+ end
+ return expanded:autosimplify()
+end
+
+--- Helper for expand - multiplies two addition expressions.
+--- @return Expression
+function BinaryOperation:expand2(other)
+ local result = {}
+ for _, expression in ipairs(self:subexpressions()) do
+ if other:type() == BinaryOperation and other.operation == BinaryOperation.ADD then
+ for _, expression2 in ipairs(other.expressions) do
+ result[#result+1] = expression * expression2
+ end
+ else
+ result[#result+1] = expression * other
+ end
+ end
+ return BinaryOperation(BinaryOperation.ADD, result)
+end
+
+--- @return Expression
+function BinaryOperation:factor()
+ local results = {}
+
+ -- Recursively factors sub-expressions
+ for index, expression in ipairs(self:subexpressions()) do
+ results[index] = expression:factor()
+ end
+
+ -- Attempts to factor expressions as monovariate polynomials
+ local factoredsubs = BinaryOperation(self.operation, results)
+ local subs = factoredsubs:getsubexpressionsrec()
+ for index, sub in ipairs(subs) do
+ local substituted = factoredsubs:substitute({[sub]=SymbolExpression("_")}):autosimplify()
+ local polynomial, result = substituted:topolynomial()
+ if result then
+ local factored = polynomial:factor():autosimplify()
+ if factored ~= substituted then
+ return factored:substitute({[SymbolExpression("_")]=sub})
+ end
+ end
+ end
+
+ -- Pulls common sub-expressions out of sum expressions
+ if self.operation == BinaryOperation.ADD then
+ local gcf
+ for _, expression in ipairs(factoredsubs:subexpressions()) do
+ if expression.operation ~= BinaryOperation.MUL then
+ expression = BinaryOperation.MULEXP({expression})
+ end
+ if not gcf then
+ gcf = expression
+ else
+ local newgcf = Integer.one()
+ for _, gcfterm in ipairs(gcf:subexpressions()) do
+ local gcfpower = Integer.one()
+ if gcfterm:type() == BinaryOperation and gcfterm.operation == BinaryOperation.POW and gcfterm.expressions[2]:type() == Integer then
+ gcfpower = gcfterm.expressions[2]
+ gcfterm = gcfterm.expressions[1]
+ end
+ for _, term in ipairs(expression:subexpressions()) do
+ local power = Integer.one()
+ if term:type() == BinaryOperation and term.operation == BinaryOperation.POW and term.expressions[2]:type() == Integer then
+ power = term.expressions[2]
+ term = term.expressions[1]
+ end
+ if term == gcfterm then
+ newgcf = newgcf * term^Integer.min(power, gcfpower)
+ end
+ end
+ end
+ gcf = newgcf
+ end
+ end
+ if gcf:type() ~= Integer then
+ local out = Integer.zero()
+ for _, expression in ipairs(factoredsubs:subexpressions()) do
+ out = out + expression/gcf
+ end
+ out = gcf*(out:autosimplify():factor())
+ return out:autosimplify()
+ end
+ end
+
+ return factoredsubs
+end
+
+--- @return Expression
+function BinaryOperation:combine()
+ local den, num, aux, mul, input = {}, {}, {}, {}, self:autosimplify():expand()
+ if input.operation ~= BinaryOperation.ADD then
+ return input
+ end
+ for _, expr in ipairs(input.expressions) do
+ local numpart, denpart = Integer.one(), Integer.one()
+ if expr.operation == BinaryOperation.POW and expr.expressions[2]:type() == Integer and expr.expressions[2] < Integer.zero() then
+ denpart = denpart*expr.expressions[1] ^ expr.expressions[2]:neg()
+ for index,term in ipairs(den) do
+ if expr.expressions[1] == den[index] then
+ if expr.expressions[2]:neg() > mul[index] then
+ mul[index] = expr.expressions[2]:neg()
+ goto continue
+ else
+ goto continue
+ end
+ end
+ end
+ table.insert(den,expr.expressions[1])
+ table.insert(mul,expr.expressions[2]:neg())
+ ::continue::
+ end
+ if expr.operation == BinaryOperation.MUL then
+ for _,subexpr in ipairs(expr.expressions) do
+ if subexpr.operation == BinaryOperation.POW and subexpr.expressions[2]:type() == Integer and subexpr.expressions[2] < Integer.zero() then
+ denpart = denpart*subexpr.expressions[1] ^ subexpr.expressions[2]:neg()
+ for index,term in ipairs(den) do
+ if subexpr.expressions[1] == den[index] then
+ if subexpr.expressions[2]:neg() > mul[index] then
+ mul[index] = subexpr.expressions[2]:neg()
+ goto continue
+ else
+ goto continue
+ end
+ end
+ end
+ table.insert(den,subexpr.expressions[1])
+ table.insert(mul,subexpr.expressions[2]:neg())
+ ::continue::
+ else
+ numpart = numpart*subexpr
+ end
+ end
+ end
+ if expr.operation ~= BinaryOperation.POW and expr.operation ~= BinaryOperation.MUL then
+ numpart = expr
+ end
+ table.insert(num,numpart)
+ table.insert(aux,denpart)
+ end
+ local denominator = Integer.one()
+ local numerator = Integer.zero()
+ for index,expr in ipairs(den) do
+ denominator = denominator*den[index] ^ mul[index]
+ end
+ denominator = denominator:autosimplify()
+ for index,expr in ipairs(num) do
+ local uncommon = denominator/aux[index]
+ uncommon = uncommon:factor():simplify()
+ numerator = numerator + expr*uncommon
+ end
+ numerator = numerator:simplify():factor()
+ if denominator == Integer.one() then
+ return numerator
+ else
+ return numerator/denominator
+ end
+end
+
+--- @param collect Expression
+--- @return Expression
+function BinaryOperation:collect(collect)
+ -- Constant expressions cannot be collected
+ if collect:isconstant() then
+ return self
+ end
+
+ -- Recusively collect subexpressions
+ local results = {}
+ for index, expression in ipairs(self:subexpressions()) do
+ results[index] = expression:collect(collect)
+ end
+ local collected = BinaryOperation(self.operation, results)
+
+ if not (collected.operation == BinaryOperation.ADD) then
+ return collected:autosimplify()
+ end
+
+ local coefficients = {}
+
+ -- TODO: Add an expression map class
+ setmetatable(coefficients, {__index =
+ function(table, key)
+ local out = rawget(table, tostring(key))
+ return out or Integer.zero()
+ end,
+ __newindex =
+ function (table, key, value)
+ rawset(table, tostring(key), value)
+ end
+ })
+
+ -- Finds all instances of a constant power of the expression to be collected, and maps each power to all terms it is multiplied by
+ for _, expression in ipairs(collected:subexpressions()) do
+ if expression == collect then
+ coefficients[Integer.one()] = coefficients[Integer.one()] + Integer.one()
+ elseif expression.operation == BinaryOperation.POW and expression:subexpressions()[1] == collect and expression:subexpressions()[2]:isconstant() then
+ coefficients[expression:subexpressions()[2]] = coefficients[expression:subexpressions()[2]] + Integer.one()
+ elseif collect:type() == BinaryOperation and collect.operation == BinaryOperation.POW and
+ expression.operation == BinaryOperation.POW and expression:subexpressions()[1] == collect:subexpressions()[1] then
+ -- Handle the fact that autosimplify turns (a^x^n -> a^(xn)), this is needed if the term to collect is itself an exponential
+ local power = (expression:subexpressions()[2] / collect:subexpressions()[2]):autosimplify()
+ if power:isconstant() then
+ coefficients[power] = coefficients[power] + Integer.one()
+ else
+ coefficients[Integer.zero()] = coefficients[Integer.zero()] + expression
+ end
+ elseif expression.operation == BinaryOperation.MUL then
+ local varpart
+ local coeffpart = Integer.one()
+ for _, term in ipairs(expression:subexpressions()) do
+ if term == collect then
+ varpart = Integer.one()
+ elseif (term.operation == BinaryOperation.POW and term:subexpressions()[1] == collect and term:subexpressions()[2]:isconstant()) then
+ varpart = term:subexpressions()[2]
+ elseif collect:type() == BinaryOperation and collect.operation == BinaryOperation.POW and
+ term.operation == BinaryOperation.POW and term:subexpressions()[1] == collect:subexpressions()[1] then
+ local power = (term:subexpressions()[2] / collect:subexpressions()[2]):autosimplify()
+ if power:isconstant() then
+ varpart = power
+ end
+ else
+ coeffpart = coeffpart * term
+ end
+ end
+ if varpart then
+ coefficients[varpart] = coefficients[varpart] + coeffpart
+ else
+ coefficients[Integer.zero()] = coefficients[Integer.zero()] + expression
+ end
+ else
+ coefficients[Integer.zero()] = coefficients[Integer.zero()] + expression
+ end
+
+
+ end
+
+ local out = Integer.zero()
+ for index, value in pairs(coefficients) do
+ out = out + collect ^ Rational.fromstring(index) * value
+ end
+
+ return out:autosimplify()
+end
+
+--- @param other Expression
+--- @return boolean
+function BinaryOperation:order(other)
+ if other:isconstant() then
+ return false
+ end
+
+ if other:isatomic() then
+ if self.operation == BinaryOperation.POW then
+ return self:order(BinaryOperation(BinaryOperation.POW, {other, Integer.one()}))
+ end
+
+ if self.operation == BinaryOperation.MUL then
+ return self:order(BinaryOperation(BinaryOperation.MUL, {other}))
+ end
+
+ if self.operation == BinaryOperation.ADD then
+ return self:order(BinaryOperation(BinaryOperation.ADD, {other}))
+ end
+ end
+
+ if self.operation == BinaryOperation.POW and other.operation == BinaryOperation.POW then
+ if self.expressions[1] ~= other.expressions[1] then
+ return self.expressions[1]:order(other.expressions[1])
+ end
+ return self.expressions[2]:order(other.expressions[2])
+ end
+
+ if (self.operation == BinaryOperation.MUL and other.operation == BinaryOperation.MUL) or
+ (self.operation == BinaryOperation.ADD and other.operation == BinaryOperation.ADD) then
+ local k = 0
+ while #self.expressions - k > 0 and #other.expressions - k > 0 do
+ if self.expressions[#self.expressions - k] ~= other.expressions[#other.expressions - k] then
+ return self.expressions[#self.expressions - k]:order(other.expressions[#other.expressions - k])
+ end
+ k = k + 1
+ end
+ return #self.expressions < #other.expressions
+ end
+
+ if (self.operation == BinaryOperation.MUL) and (other.operation == BinaryOperation.POW or other.operation == BinaryOperation.ADD) then
+ return self:order(BinaryOperation(BinaryOperation.MUL, {other}))
+ end
+
+ if (self.operation == BinaryOperation.POW) and (other.operation == BinaryOperation.MUL) then
+ return BinaryOperation(BinaryOperation.MUL, {self}):order(other)
+ end
+
+ if (self.operation == BinaryOperation.POW) and (other.operation == BinaryOperation.ADD) then
+ return self:order(BinaryOperation(BinaryOperation.POW, {other, Integer.one()}))
+ end
+
+ if (self.operation == BinaryOperation.ADD) and (other.operation == BinaryOperation.MUL) then
+ return BinaryOperation(BinaryOperation.MUL, {self}):order(other)
+ end
+
+ if (self.operation == BinaryOperation.ADD) and (other.operation == BinaryOperation.POW) then
+ return BinaryOperation(BinaryOperation.POW, {self, Integer.one()}):order(other)
+ end
+
+ if other:type() == FunctionExpression or other:type() == TrigExpression or other:type() == Logarithm then
+ if self.operation == BinaryOperation.ADD or self.operation == BinaryOperation.MUL then
+ return self:order(BinaryOperation(self.operation, {other}))
+ end
+
+ if self.operation == BinaryOperation.POW then
+ return self:order(other^Integer.one())
+ end
+ end
+
+ return true
+end
+
+--- Returns whether the binary operation is commutative.
+--- @return boolean
+function BinaryOperation:iscommutative()
+ error("Called unimplemented method: iscommutative()")
+end
+
+--- @return PolynomialRing, boolean
+function BinaryOperation:topolynomial()
+ local addexp = self
+ if not self.operation or self.operation ~= BinaryOperation.ADD then
+ addexp = BinaryOperation(BinaryOperation.ADD, {self})
+ end
+
+ local poly = {}
+ local degree = 0
+ local symbol
+ for _, expression in ipairs(addexp.expressions) do
+ local coefficient
+ local sym
+ local power
+ -- Expressions of the form c
+ if expression:isconstant() then
+ coefficient = expression
+ power = 0
+ -- Expressions of the form x
+ elseif expression:type() == SymbolExpression then
+ coefficient = Integer.one()
+ sym = expression.symbol
+ power = 1
+ -- Expressions of the form c*x
+ elseif expression.operation and expression.operation == BinaryOperation.MUL and #expression.expressions == 2
+ and expression.expressions[1]:isconstant() and expression.expressions[2]:type() == SymbolExpression then
+
+ coefficient = expression.expressions[1]
+ sym = expression.expressions[2].symbol
+ power = 1
+ -- Expressions of the form c*x^n (totally not confusing)
+ elseif expression.operation and expression.operation == BinaryOperation.MUL and #expression.expressions == 2
+ and expression.expressions[1]:isconstant() and expression.expressions[2].operation and
+ expression.expressions[2].operation == BinaryOperation.POW and #expression.expressions[2].expressions == 2
+ and expression.expressions[2].expressions[1]:type() == SymbolExpression and expression.expressions[2].expressions[2].getring
+ and expression.expressions[2].expressions[2]:getring() == Integer.getring() and expression.expressions[2].expressions[2] > Integer.zero() then
+
+ coefficient = expression.expressions[1]
+ sym = expression.expressions[2].expressions[1].symbol
+ power = expression.expressions[2].expressions[2]:asnumber()
+ -- Expressions of the form x^n
+ elseif expression.operation and expression.operation == BinaryOperation.POW and #expression.expressions == 2
+ and expression.expressions[1]:type() == SymbolExpression and expression.expressions[2].getring
+ and expression.expressions[2]:getring() == Integer.getring() and expression.expressions[2] > Integer.zero() then
+
+ coefficient = Integer.one()
+ sym = expression.expressions[1].symbol
+ power = expression.expressions[2]:asnumber()
+ else
+ return self, false
+ end
+
+ if symbol and sym and symbol ~= sym then
+ return self, false
+ end
+ if not symbol then
+ symbol = sym
+ end
+ poly[power + 1] = coefficient
+ if power > degree then
+ degree = power
+ end
+ end
+
+ for i = 1,degree+1 do
+ poly[i] = poly[i] or Integer.zero()
+ end
+
+ return PolynomialRing(poly, symbol), true
+end
+
+function BinaryOperation:tolatex()
+ if self.operation == BinaryOperation.POW then
+ if self.expressions[2]:type() == Integer and self.expressions[2] < Integer.zero() then
+ local base = self.expressions[1]
+ local exponent = self.expressions[2]
+ if exponent == Integer(-1) then
+ return "\\frac{1}{" .. base:tolatex() .. "}"
+ else
+ if base:isatomic() then
+ return "\\frac{1}{" .. base:tolatex() .. "^{" .. exponent:neg():tolatex() .. "}}"
+ else
+ return "\\frac{1}{\\left(" .. base:tolatex() .. "\\right)^{" .. exponent:neg():tolatex() .. "}}"
+ end
+ end
+ end
+ if self.expressions[1]:isatomic() then
+ if self.expressions[2]:isconstant() and self.expressions[2]:getring() == Rational:getring() and self.expressions[2].numerator == Integer.one() then
+ if self.expressions[2].denominator == Integer(2) then
+ return "\\sqrt{" .. self.expressions[1]:tolatex() .. '}'
+ end
+ return "\\sqrt[" .. self.expressions[2].denominator:tolatex() .. ']{' .. self.expressions[1]:tolatex() .. '}'
+ end
+ return self.expressions[1]:tolatex() .. '^{' .. self.expressions[2]:tolatex() .. '}'
+ else
+ if self.expressions[2]:isconstant() and self.expressions[2]:getring() == Rational:getring() and self.expressions[2].numerator == Integer.one() then
+ if self.expressions[2].denominator == Integer(2) then
+ return "\\sqrt{" .. self.expressions[1]:tolatex() .. '}'
+ end
+ return "\\sqrt[" .. self.expressions[2].denominator:tolatex() .. ']{' .. self.expressions[1]:tolatex() .. '}'
+ end
+ return "\\left(" .. self.expressions[1]:tolatex() .. "\\right)" .. '^{' .. self.expressions[2]:tolatex() .. '}'
+ end
+ end
+ if self.operation == BinaryOperation.MUL then
+ local sign = ''
+ local out = ''
+ local denom = ''
+ if self:autosimplify():isconstant() then
+ for index, expression in ipairs(self.expressions) do
+ if index == 1 then
+ out = out .. expression:tolatex()
+ else
+ out = out .. "\\cdot " .. expression:tolatex()
+ end
+ end
+ return out
+ end
+ if #self.expressions == 2 and self.expressions[2]:type() == BinaryOperation and self.expressions[2].operation == BinaryOperation.POW and self.expressions[2].expressions[2] == -Integer.one() then
+ out = '\\frac{' .. self.expressions[1]:tolatex() .. '}{' .. self.expressions[2].expressions[1]:tolatex() .. '}'
+ return out
+ end
+ for _, expression in ipairs(self.expressions) do
+ if expression:type() == BinaryOperation then
+ if expression.operation == BinaryOperation.POW and expression.expressions[2]:isconstant() and expression.expressions[2] < Integer.zero() then
+ local reversed = (Integer.one() / expression):autosimplify()
+ if reversed.operation == BinaryOperation.ADD or expression.operation == BinaryOperation.SUB then
+ denom = denom .. '\\left('.. reversed:tolatex() .. '\\right)'
+ else
+ denom = denom .. reversed:tolatex()
+ end
+ elseif expression.operation == BinaryOperation.ADD or expression.operation == BinaryOperation.SUB then
+ out = out .. '\\left(' .. expression:tolatex() .. '\\right)'
+ else
+ out = out .. expression:tolatex()
+ end
+ else
+ if expression == Integer(-1) then
+ out = out .. '-'
+ elseif expression:type() == Rational and expression.numerator == Integer.one() then
+ denom = denom .. expression.denominator:tolatex()
+ elseif expression:type() == Rational and expression.numerator == Integer(-1) then
+ out = out .. '-'
+ denom = denom .. expression.denominator:tolatex()
+ elseif expression:type() == Rational then
+ out = out .. expression.numerator:tolatex()
+ denom = denom .. expression.denominator:tolatex()
+ else
+ out = out .. expression:tolatex()
+ end
+ end
+ end
+ if string.sub(out,1,1) == '-' then
+ sign = '-'
+ out = string.sub(out,2,-1)
+ end
+ if denom ~= '' and out == '' then
+ return sign .. '\\frac{' .. '1' .. '}{' .. denom .. '}'
+ end
+ if denom ~= '' then
+ return sign .. '\\frac{' .. out .. '}{' .. denom .. '}'
+ end
+ return sign..out
+ end
+ if self.operation == BinaryOperation.ADD then
+ local out = ''
+ for index, expression in ipairs(self.expressions) do
+ out = out .. expression:tolatex()
+ if self.expressions[index + 1] and string.sub(self.expressions[index + 1]:tolatex(), 1, 1) ~= "-" then
+ out = out .. '+'
+ end
+ end
+ return out
+ end
+ if self.operation == BinaryOperation.DIV then
+ return '\\frac{' .. self.expressions[1]:tolatex() .. '}{' .. self.expressions[2]:tolatex() .. '}'
+ end
+ if self.operation == BinaryOperation.SUB then
+ local out = ''
+ if not self.expressions[2] then
+ if not self.expressions[1]:isatomic() then
+ out = '-\\left(' .. self.expressions[1]:tolatex() .. '\\right)'
+ else
+ out = '-' .. self.expressions[1]:tolatex()
+ end
+ else
+ for index, expression in ipairs(self.expressions) do
+ if expression.operation and (expression.operation == BinaryOperation.ADD or expression.operation == BinaryOperation.SUB) and index >1 then
+ out = out .. "\\left(" .. expression:tolatex() .. "\\right)"
+ else
+ out = out .. expression:tolatex()
+ end
+ if self.expressions[index + 1] then
+ out = out .. '-'
+ end
+ end
+ end
+ return out
+ end
+ return self
+end
+
+-----------------
+-- Inheritance --
+-----------------
+
+__BinaryOperation.__index = CompoundExpression
+__BinaryOperation.__call = BinaryOperation.new
+BinaryOperation = setmetatable(BinaryOperation, __BinaryOperation)
+
+----------------------
+-- Static constants --
+----------------------
+
+BinaryOperation.ADD = function(a, b)
+ return a + b
+end
+
+BinaryOperation.SUB = function(a, b)
+ return a - b
+end
+
+BinaryOperation.MUL = function(a, b)
+ return a * b
+end
+
+BinaryOperation.DIV = function(a, b)
+ return a / b
+end
+
+BinaryOperation.IDIV = function(a, b)
+ return a // b
+end
+
+BinaryOperation.MOD = function(a, b)
+ return a % b
+end
+
+BinaryOperation.POW = function(a, b)
+ return a ^ b
+end
+
+BinaryOperation.DEFAULT_NAMES = {
+ [BinaryOperation.ADD] = "+",
+ [BinaryOperation.SUB] = "-",
+ [BinaryOperation.MUL] = "*",
+ [BinaryOperation.DIV] = "/",
+ [BinaryOperation.IDIV] = "//",
+ [BinaryOperation.MOD] = "%",
+ [BinaryOperation.POW] = "^"
+}
+
+BinaryOperation.COMMUTATIVITY = {
+ [BinaryOperation.ADD] = true,
+ [BinaryOperation.SUB] = false,
+ [BinaryOperation.MUL] = true,
+ [BinaryOperation.DIV] = false,
+ [BinaryOperation.IDIV] = false,
+ [BinaryOperation.MOD] = false,
+ [BinaryOperation.POW] = false
+}
+
+BinaryOperation.ADDEXP = function(expressions, name)
+ return BinaryOperation(BinaryOperation.ADD, expressions, name)
+end
+
+BinaryOperation.SUBEXP = function(expressions, name)
+ return BinaryOperation(BinaryOperation.SUB, expressions, name)
+end
+
+BinaryOperation.MULEXP = function(expressions, name)
+ return BinaryOperation(BinaryOperation.MUL, expressions, name)
+end
+
+BinaryOperation.DIVEXP = function(expressions, name)
+ return BinaryOperation(BinaryOperation.DIV, expressions, name)
+end
+
+BinaryOperation.IDIVEXP = function(expressions, name)
+ return BinaryOperation(BinaryOperation.IDIV, expressions, name)
+end
+
+BinaryOperation.MODEXP = function(expressions, name)
+ return BinaryOperation(BinaryOperation.MOD, expressions, name)
+end
+
+BinaryOperation.POWEXP = function(expressions, name)
+ return BinaryOperation(BinaryOperation.POW, expressions, name)
+end \ No newline at end of file
diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/difference.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/difference.lua
new file mode 100644
index 0000000000..2cc56b6600
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/difference.lua
@@ -0,0 +1,14 @@
+-- Seperates the various binary operations into their own files for readability
+
+--- Automatic simplification of difference expressions.
+--- @return BinaryOperation
+function BinaryOperation:simplifydifference()
+ local term1 = self.expressions[1]
+ local term2 = self.expressions[2]
+
+ if not term2 then
+ return BinaryOperation(BinaryOperation.MUL, {Integer(-1), term1}):autosimplify()
+ end
+
+ return BinaryOperation(BinaryOperation.ADD, {term1, BinaryOperation(BinaryOperation.MUL, {Integer(-1), term2}):autosimplify()}):autosimplify()
+end \ No newline at end of file
diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/power.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/power.lua
new file mode 100644
index 0000000000..35df72c440
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/power.lua
@@ -0,0 +1,169 @@
+-- Seperates the various binary operations into their own files for readability
+
+--- Automatic simplification of power expressions.
+--- @return BinaryOperation
+function BinaryOperation:simplifypower()
+ local base = self.expressions[1]
+ local exponent = self.expressions[2]
+
+ if base:isconstant() and exponent:isconstant() and exponent:getring() ~= Rational:getring() then
+ return self:evaluate()
+ end
+
+ -- Simplifies i^x for x integer.
+ if base == I and exponent:isconstant() and exponent:getring() == Integer:getring() then
+ if exponent % Integer(4) == Integer(0) then
+ return Integer(1)
+ end
+ if exponent % Integer(4) == Integer(1) then
+ return I
+ end
+ if exponent % Integer(4) == Integer(2) then
+ return Integer(-1)
+ end
+ if exponent % Integer(4) == Integer(3) then
+ return -I
+ end
+ end
+
+ -- Simplifies complex numbers raised to negative integer powers
+ if not base:isrealconstant() and base:iscomplexconstant() and exponent:isconstant() and exponent:getring() == Integer:getring() and exponent < Integer.zero() then
+ local a
+ local b
+ if base.operation == BinaryOperation.MUL then
+ a = Integer.zero()
+ b = base.expressions[1]
+ elseif base.operation == BinaryOperation.ADD and base.expressions[2] == I then
+ a = base.expressions[1]
+ b = Integer.one()
+ else
+ a = base.expressions[1]
+ b = base.expressions[2].expressions[1]
+ end
+ return (((a-b*I)/(a^Integer(2)+b^Integer(2)))^(-exponent)):expand():autosimplify()
+ end
+
+ -- Uses the property that 0^x = 0 if x does not equal 0
+ if base:isconstant() and base == base:zero() then
+ return Integer.zero()
+ end
+
+ -- Uses the property that 1^x = 1
+ if base:isconstant() and base == base:one() then
+ return base:one()
+ end
+
+ -- Uses the property that x^0 = 1
+ if exponent:isconstant() and exponent == exponent:zero() then
+ return exponent:one()
+ end
+
+ -- Uses the property that x^1 = x
+ if exponent:isconstant() and exponent == exponent:one() then
+ return base
+ end
+
+ -- Uses the property that b ^ (log(b, x)) == x
+ if exponent:type() == Logarithm and exponent.base == base then
+ return exponent.expression
+ end
+
+ -- Uses the property that b ^ (a * log(b, x)) == x ^ a
+ if exponent.operation == BinaryOperation.MUL then
+ local x
+ local rest = Integer.one()
+ for _, expression in ipairs(exponent.expressions) do
+ if expression:type() == Logarithm and expression.base == base and not log then
+ x = expression.expression
+ else
+ rest = rest * expression
+ end
+ end
+ if x then
+ return (x ^ rest):autosimplify()
+ end
+ end
+
+ -- Uses the property that (x^a)^b = x^(a*b)
+ if not base:isatomic() and base.operation == BinaryOperation.POW and exponent:isconstant() then
+ base, exponent = base.expressions[1], BinaryOperation(BinaryOperation.MUL, {exponent, base.expressions[2]}):autosimplify()
+ return BinaryOperation(BinaryOperation.POW, {base, exponent}):autosimplify()
+ end
+
+ -- Uses the property that (x_1*x_2*...*x_n)^a = x_1^a*x_2^a*..x_n^a if a is an integer
+ if base.operation == BinaryOperation.MUL and exponent:type() == Integer then
+ local results = {}
+ for index, expression in ipairs(base.expressions) do
+ results[index] = BinaryOperation(BinaryOperation.POW, {expression, exponent}):autosimplify()
+ end
+ return BinaryOperation(BinaryOperation.MUL, results):autosimplify()
+ end
+
+ -- Uses the property that sqrt(x,r)^d == sqrt(x,r/d)
+ if base:type() == SqrtExpression and exponent:type() == Integer and exponent > Integer.zero() then
+ local root = base.root
+ local expr = base.expression
+ local comm = Integer.gcd(root,exponent)
+ root = root / comm
+ local expo = exponent / comm
+ expr = expr ^ expo
+ return SqrtExpression(expr,root):autosimplify()
+ end
+
+ -- Rationalizing SqrtExpressions
+ if base:type() == SqrtExpression and exponent:type() == Integer and base.expression:type() == Integer and exponent < Integer.zero() then
+ local root = base.root
+ local expr = base.expression
+ local result = (SqrtExpression(expr ^ (root - Integer.one()),root) / expr) ^ exponent:neg()
+ return result:autosimplify()
+ end
+
+ if base:isconstant() and exponent:isconstant() and exponent:getring() == Rational.getring() then
+ return self --:simplifyrationalpower()
+ end
+
+ -- Our expression cannot be simplified
+ return self
+end
+
+-- Automatic simplification of rational power expressions
+function BinaryOperation:simplifyrationalpower()
+ local base = self.expressions[1]
+ local exponent = self.expressions[2]
+
+ if base:getring() == Rational.getring() then
+ return (BinaryOperation(BinaryOperation.POW, {base.numerator, exponent}):simplifyrationalpower()) /
+ (BinaryOperation(BinaryOperation.POW, {base.denominator, exponent}):simplifyrationalpower())
+ end
+
+ if base == Integer(-1) then
+ if exponent == Integer(1) / Integer(2) then
+ return I
+ end
+
+ return self
+ end
+
+ local primes = base:primefactorization()
+
+ if primes.expressions[1] and not primes.expressions[2] then
+ local primeexponent = primes.expressions[1].expressions[2]
+ local primebase = primes.expressions[1].expressions[1]
+ local newexponent = primeexponent * exponent
+ local integerpart
+ if newexponent.getring() == Rational.getring() then
+ integerpart = newexponent.numerator // newexponent.denominator
+ else
+ integerpart = newexponent
+ end
+
+ if integerpart == Integer.zero() then
+ return BinaryOperation(BinaryOperation.POW, {primebase, newexponent})
+ end
+ return BinaryOperation(BinaryOperation.MUL,
+ {BinaryOperation(BinaryOperation.POW, {primebase, integerpart}),
+ BinaryOperation(BinaryOperation.POW, {primebase, newexponent - integerpart})}):autosimplify()
+ end
+
+ return BinaryOperation(BinaryOperation.POW, {primes:autosimplify(), exponent})
+end \ No newline at end of file
diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/product.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/product.lua
new file mode 100644
index 0000000000..b104cb5e8a
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/product.lua
@@ -0,0 +1,231 @@
+-- Seperates the various binary operations into their own files for readability
+
+--- Automatic simplification of multiplication expressions.
+--- @return BinaryOperation
+function BinaryOperation:simplifyproduct()
+ if not self.expressions[1] then
+ error("Execution error: attempted to simplify empty product")
+ end
+
+ if not self.expressions[2] then
+ return self.expressions[1]
+ end
+
+ -- Uses the property that x*0=0
+ for _, expression in ipairs(self.expressions) do
+ if expression:isconstant() and expression == expression:zero() then
+ return expression:zero()
+ end
+ end
+
+ local result = self:simplifyproductrec()
+
+ -- We don't really know what ring we are working in here, so just assume the integer ring
+ if not result.expressions[1] then
+ return Integer.one()
+ end
+
+ if not result.expressions[2] then
+ return result.expressions[1]
+ end
+
+ return result
+end
+
+function BinaryOperation:simplifyproductrec()
+ local term1 = self.expressions[1]
+ local term2 = self.expressions[2]
+
+ if not self.expressions[3] then
+ if (term1:isconstant() or not (term1.operation == BinaryOperation.MUL)) and
+ (term2:isconstant() or not (term2.operation == BinaryOperation.MUL)) then
+
+ if term1:isconstant() and term2:isconstant() then
+ local result = self:evaluate()
+ if result == result:one() then
+ return BinaryOperation(BinaryOperation.MUL, {})
+ end
+ return BinaryOperation(BinaryOperation.MUL, {result})
+ end
+
+ -- Uses the property that x*1 = x
+ if term1:isconstant() and term1 == term1:one() then
+ return BinaryOperation(BinaryOperation.MUL, {term2})
+ end
+
+ if term2:isconstant() and term2 == term2:one() then
+ return BinaryOperation(BinaryOperation.MUL, {term1})
+ end
+
+ -- Distributes constants if the other term is a sum expression.
+ if term1:isconstant() and term2.operation == BinaryOperation.ADD then
+ local distributed = BinaryOperation(BinaryOperation.ADD, {})
+ for i, exp in ipairs(term2:subexpressions()) do
+ distributed.expressions[i] = term1 * exp
+ end
+ return BinaryOperation(BinaryOperation.MUL, {distributed:autosimplify()})
+ end
+
+ if term2:isconstant() and term1.operation == BinaryOperation.ADD then
+ local distributed = BinaryOperation(BinaryOperation.ADD, {})
+ for i, exp in ipairs(term1:subexpressions()) do
+ distributed.expressions[i] = term2 * exp
+ end
+ return BinaryOperation(BinaryOperation.MUL, {distributed:autosimplify()})
+ end
+
+ -- Uses the property that sqrt(a,r)*sqrt(b,r) = sqrt(a*b,r) if a,r are positive integers
+ if term1:type() == SqrtExpression and term2:type() == SqrtExpression and term1.expression:isconstant() and term2.expression:isconstant() and term1.root:type() == Integer then
+ if term1.root == term2.root and term1.expression > Integer.zero() then
+ local expression = term1.expression*term2.expression
+ local result = SqrtExpression(expression,term1.root):autosimplify()
+ if result == Integer.one() then
+ return BinaryOperation(BinaryOperation.MUL,{})
+ else
+ return BinaryOperation(BinaryOperation.MUL,{result})
+ end
+ end
+ end
+
+ --if term1.operation == BinaryOperation.POW and term2.operation == BinaryOperation.POW and term1.expressions[1]:type() == SqrtExpression and term2.expressions[1]:type() == SqrtExpression and term1.expressions[2]:type() == Integer and term2.expressions[2]:type() == Integer and term1.expressions[2] < Integer.zero() and term2.expressions[2] < Integer.zero() and term1.expressions[1].root == term2.expressions[1].root then
+ -- local expo1 = term1.expressions[2]:neg()
+ -- local expo2 = term2.expressions[2]:neg()
+ -- local root = term1.expressions[1].root
+ -- local expr1 = term1.expressions[1].expression
+ -- local expr2 = term2.expressions[1].expression
+ -- local result1 = BinaryOperation(BinaryOperation.POW,{SqrtExpression(expr1,root),expo1}):simplifypower()
+ -- local result2 = BinaryOperation(BinaryOperation.POW,{SqrtExpression(expr2,root),expo2}):simplifypower()
+ -- local result = BinaryOperation(BinaryOperation.MUL,{result1,result2}):autosimplify()
+ -- if result == Integer.one() then
+ -- return BinaryOperation(BinaryOperation.MUL,{})
+ -- end
+ -- if result:type() == Integer then
+ -- return BinaryOperation(BinaryOperation.MUL,{Rational(Integer.one(),result)})
+ -- end
+ -- return BinaryOperation(BinaryOperation.MUL,{BinaryOperation(BinaryOperation.POW, {result,Integer(-1)})}):autosimplify()
+ --end
+
+ -- Uses the property that x^a*x^b=x^(a+b)
+ local revertterm1 = false
+ local revertterm2 = false
+ if term1.operation ~= BinaryOperation.POW then
+ term1 = BinaryOperation(BinaryOperation.POW, {term1, Integer.one()})
+ revertterm1 = true
+ end
+ if term2.operation ~= BinaryOperation.POW then
+ term2 = BinaryOperation(BinaryOperation.POW, {term2, Integer.one()})
+ revertterm2 = true
+ end
+ if term1.expressions[1] == term2.expressions[1]
+ --and not
+ -- (term1.expressions[1]:type() == Integer and
+ -- term1.expressions[2]:type() ~= term2.--expressions[2]:type())
+ then
+ local result = BinaryOperation(BinaryOperation.POW,
+ {term1.expressions[1],
+ BinaryOperation(BinaryOperation.ADD,
+ {term1.expressions[2], term2.expressions[2]}):autosimplify()}):autosimplify()
+ if result:isconstant() and result == result:one() then
+ return BinaryOperation(BinaryOperation.MUL, {})
+ end
+ return BinaryOperation(BinaryOperation.MUL, {result})
+ end
+
+ if revertterm1 then
+ term1 = term1.expressions[1]
+ end
+ if revertterm2 then
+ term2 = term2.expressions[1]
+ end
+
+ if term2:order(term1) then
+ return BinaryOperation(BinaryOperation.MUL, {term2, term1})
+ end
+
+ return self
+ end
+
+ if term1.operation == BinaryOperation.MUL and not (term2.operation == BinaryOperation.MUL) then
+ return term1:mergeproducts(BinaryOperation(BinaryOperation.MUL, {term2}))
+ end
+
+ if not (term1.operation == BinaryOperation.MUL) and term2.operation == BinaryOperation.MUL then
+ return BinaryOperation(BinaryOperation.MUL, {term1}):mergeproducts(term2)
+ end
+
+ return term1:mergeproducts(term2)
+ end
+
+ local rest = {}
+ for index, expression in ipairs(self.expressions) do
+ if index > 1 then
+ rest[index - 1] = expression
+ end
+ end
+
+ local result = BinaryOperation(BinaryOperation.MUL, rest):simplifyproductrec()
+
+ if term1.operation ~= BinaryOperation.MUL then
+ term1 = BinaryOperation(BinaryOperation.MUL, {term1})
+ end
+ if result.operation ~= BinaryOperation.MUL then
+ result = BinaryOperation(BinaryOperation.MUL, {result})
+ end
+ return term1:mergeproducts(result)
+end
+
+-- Merges two lists of products
+function BinaryOperation:mergeproducts(other)
+ if not self.expressions[1] then
+ return other
+ end
+
+ if not other.expressions[1] then
+ return self
+ end
+
+ local first = BinaryOperation(BinaryOperation.MUL, {self.expressions[1], other.expressions[1]}):simplifyproductrec()
+
+ local selfrest = {}
+ for index, expression in ipairs(self.expressions) do
+ if index > 1 then
+ selfrest[index - 1] = expression
+ end
+ end
+
+ local otherrest = {}
+ for index, expression in ipairs(other.expressions) do
+ if index > 1 then
+ otherrest[index - 1] = expression
+ end
+ end
+
+ if first.operation ~= BinaryOperation.MUL or not first.expressions[2] then
+ local result = BinaryOperation(self.operation, selfrest):mergeproducts(BinaryOperation(other.operation, otherrest))
+ if not first.expressions[1] then
+ return result
+ end
+ table.insert(result.expressions, 1, first.expressions[1])
+
+ if result.operation == BinaryOperation.MUL and not result.expressions[3] and result.expressions[1] and result.expressions[2] then
+ if result.expressions[1]:isconstant() and result.expressions[2]:isconstant() then
+ return result:simplifyproductrec()
+ end
+ end
+ return result
+ end
+
+ local result
+ if first.expressions[1] == self.expressions[1] then
+ result = BinaryOperation(self.operation, selfrest):mergeproducts(other)
+ else
+ result = self:mergeproducts(BinaryOperation(other.operation, otherrest))
+ end
+ table.insert(result.expressions, 1, first.expressions[1])
+ if result.operation == BinaryOperation.MUL and not result.expressions[3] and result.expressions[1] and result.expressions[2] then
+ if result.expressions[1]:isconstant() and result.expressions[2]:isconstant() then
+ return result:simplifyproductrec()
+ end
+ end
+ return result
+end \ No newline at end of file
diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/quotient.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/quotient.lua
new file mode 100644
index 0000000000..4dac489bf5
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/quotient.lua
@@ -0,0 +1,14 @@
+-- Seperates the various binary operations into their own files for readability
+
+-- Automatic simplification of quotient expressions.
+--- @return BinaryOperation
+function BinaryOperation:simplifyquotient()
+ local numerator = self.expressions[1]
+ local denominator = self.expressions[2]
+
+ if numerator:isconstant() and denominator:isconstant() then
+ return self:evaluate()
+ end
+
+ return BinaryOperation(BinaryOperation.MUL, {numerator, BinaryOperation(BinaryOperation.POW, {denominator, Integer(-1)}):autosimplify()}):autosimplify()
+end \ No newline at end of file
diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/sum.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/sum.lua
new file mode 100644
index 0000000000..5bb204066f
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/sum.lua
@@ -0,0 +1,226 @@
+-- Seperates the various binary operations into their own files for readability
+
+-- Automatic simplification of addition expressions.
+--- @return BinaryOperation
+function BinaryOperation:simplifysum()
+ if not self.expressions[1] then
+ error("Execution error: Attempted to simplify empty sum")
+ end
+
+ if not self.expressions[2] then
+ return self.expressions[1]
+ end
+
+ local result = self:simplifysumrec()
+
+ -- We don't really know what ring we are working in here, so just assume the integer ring
+ if not result.expressions[1] then
+ return Integer.zero()
+ end
+
+ -- Simplifies single sums to their operands
+ if not result.expressions[2] then
+ return result.expressions[1]
+ end
+
+ return result
+end
+
+function BinaryOperation:simplifysumrec()
+ local term1 = self.expressions[1]
+ local term2 = self.expressions[2]
+
+ if self.expressions[1] and self.expressions[2] and not self.expressions[3] then
+ if (term1:isconstant() or not (term1.operation == BinaryOperation.ADD)) and
+ (term2:isconstant() or not (term2.operation == BinaryOperation.ADD)) then
+
+ if term1:isconstant() and term2:isconstant() then
+ return BinaryOperation(BinaryOperation.ADD, {self:evaluate()})
+ end
+
+ -- Uses the property that x + 0 = x
+ if term1:isconstant() and term1 == term1:zero() then
+ return BinaryOperation(BinaryOperation.ADD, {term2})
+ end
+
+ if term2:isconstant() and term2 == term2:zero() then
+ return BinaryOperation(BinaryOperation.ADD, {term1})
+ end
+
+ local revertterm1 = false
+ local revertterm2 = false
+ -- Uses the property that a*x+b*x= (a+b)*x
+ -- This is only done if a and b are constant, since otherwise this could be counterproductive
+ -- We SHOULD be okay to only check left distributivity, since constants always come first when ordered
+ if term1.operation == BinaryOperation.MUL and term2.operation == BinaryOperation.MUL then
+ local findex = 2
+ local sindex = 2
+ if not term1.expressions[1]:isconstant() then
+ revertterm1 = true
+ findex = 1
+ end
+ if not term2.expressions[1]:isconstant() then
+ revertterm2 = true
+ sindex = 1
+ end
+ if FancyArrayEqual(term1.expressions,term2.expressions,findex,sindex) then
+ local result
+ if not revertterm1 and not revertterm2 then
+ result = BinaryOperation(
+ BinaryOperation.ADD,
+ {term1.expressions[1],term2.expressions[1]}
+ )
+ end
+ if revertterm1 and not revertterm2 then
+ result = BinaryOperation(
+ BinaryOperation.ADD,
+ {Integer.one(),term2.expressions[1]}
+ )
+ end
+ if not revertterm1 and revertterm2 then
+ result = BinaryOperation(
+ BinaryOperation.ADD,
+ {term1.expressions[1],Integer.one()}
+ )
+ end
+ if revertterm1 and revertterm2 then
+ result = Integer(2)
+ end
+ result = result:autosimplify()
+ for i=findex,#term1.expressions do
+ result = BinaryOperation(
+ BinaryOperation.MUL,
+ {result,term1.expressions[i]}
+ )
+ end
+ result = result:autosimplify()
+ if result:isconstant() and result == result:zero() then
+ return BinaryOperation(BinaryOperation.ADD, {})
+ end
+ return BinaryOperation(BinaryOperation.ADD, {result})
+ end
+ end
+
+ if term1.operation ~= BinaryOperation.MUL or not term1.expressions[1]:isconstant() then
+ term1 = BinaryOperation(BinaryOperation.MUL,{Integer.one(), term1})
+ revertterm1 = true
+ end
+ if term2.operation ~= BinaryOperation.MUL or not term2.expressions[1]:isconstant() then
+ term2 = BinaryOperation(BinaryOperation.MUL, {Integer.one(), term2})
+ revertterm2 = true
+ end
+ if ArrayEqual(term1.expressions, term2.expressions, 2) then
+ local result = BinaryOperation(
+ BinaryOperation.ADD,
+ {term1.expressions[1],term2.expressions[1]}
+ )
+ result = result:autosimplify()
+ for i=2,#term1.expressions do
+ result = BinaryOperation(
+ BinaryOperation.MUL,
+ {result,term1.expressions[i]}
+ )
+ end
+ result = result:autosimplify()
+ --local result = BinaryOperation(BinaryOperation.MUL,
+ -- {BinaryOperation(BinaryOperation.ADD,
+ -- {term1.expressions[1],
+ -- term2.expressions[1]}):autosimplify(),
+ -- term1.expressions[2]}):autosimplify()
+ if result:isconstant() and result == result:zero() then
+ return BinaryOperation(BinaryOperation.ADD, {})
+ end
+ return BinaryOperation(BinaryOperation.ADD, {result})
+ end
+
+ if revertterm1 then
+ term1 = term1.expressions[2]
+ end
+ if revertterm2 then
+ term2 = term2.expressions[2]
+ end
+
+ if term2:order(term1) then
+ return BinaryOperation(BinaryOperation.ADD, {term2, term1})
+ end
+
+ return self
+ end
+
+ if term1.operation == BinaryOperation.ADD and not (term2.operation == BinaryOperation.ADD) then
+ return term1:mergesums(BinaryOperation(BinaryOperation.ADD, {term2}))
+ end
+
+ if not (term1.operation == BinaryOperation.ADD) and term2.operation == BinaryOperation.ADD then
+ return BinaryOperation(BinaryOperation.ADD, {term1}):mergesums(term2)
+ end
+
+ return term1:mergesums(term2)
+ end
+
+ local rest = {}
+ for index, expression in ipairs(self.expressions) do
+ if index > 1 then
+ rest[index - 1] = expression
+ end
+ end
+
+ local result = BinaryOperation(BinaryOperation.ADD, rest):simplifysumrec()
+
+ if term1.operation ~= BinaryOperation.ADD then
+ term1 = BinaryOperation(BinaryOperation.ADD, {term1})
+ end
+ if result.operation ~= BinaryOperation.ADD then
+ result = BinaryOperation(BinaryOperation.ADD, {result})
+ end
+ return term1:mergesums(result)
+end
+
+-- Merges two lists of sums
+function BinaryOperation:mergesums(other)
+ if not self.expressions[1] then
+ return other
+ end
+
+ if not other.expressions[1] then
+ return self
+ end
+
+ local first = BinaryOperation(BinaryOperation.ADD, {self.expressions[1], other.expressions[1]}):simplifysumrec()
+
+ local selfrest = {}
+ for index, expression in ipairs(self.expressions) do
+ if index > 1 then
+ selfrest[index - 1] = expression
+ end
+ end
+
+ local otherrest = {}
+ for index, expression in ipairs(other.expressions) do
+ if index > 1 then
+ otherrest[index - 1] = expression
+ end
+ end
+
+ if first.operation ~= BinaryOperation.ADD or not first.expressions[2] then
+ local result = BinaryOperation(self.operation, selfrest):mergesums(BinaryOperation(other.operation, otherrest))
+ if not first.expressions[1] then
+ return result
+ end
+ if first.expressions[1] ~= Integer.zero(0) then
+ table.insert(result.expressions, 1, first.expressions[1])
+ end
+ return result
+ end
+
+ local result
+ if first.expressions[1] == self.expressions[1] then
+ result = BinaryOperation(self.operation, selfrest):mergesums(other)
+ else
+ result = self:mergesums(BinaryOperation(other.operation, otherrest))
+ end
+
+ table.insert(result.expressions, 1, first.expressions[1])
+
+ return result
+end
diff --git a/macros/luatex/latex/luacas/tex/core/compoundexpression.lua b/macros/luatex/latex/luacas/tex/core/compoundexpression.lua
new file mode 100644
index 0000000000..792c36cfc2
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/core/compoundexpression.lua
@@ -0,0 +1,52 @@
+--- @class CompoundExpression
+--- Interface for an expression consisting of one or more subexpressions.
+CompoundExpression = {}
+__CompoundExpression = {}
+
+----------------------
+-- Instance methods --
+----------------------
+
+--- @param symbol SymbolExpression
+--- @return boolean
+function CompoundExpression:freeof(symbol)
+ for _, expression in ipairs(self:subexpressions()) do
+ if not expression:freeof(symbol) then
+ return false
+ end
+ end
+ return true
+ end
+
+--- @param map table<Expression, Expression>
+--- @return Expression
+function CompoundExpression:substitute(map)
+ for expression, replacement in pairs(map) do
+ if self == expression then
+ return replacement
+ end
+ end
+
+ local results = {}
+ for index, expression in ipairs(self:subexpressions()) do
+ results[index] = expression:substitute(map)
+ end
+ return self:setsubexpressions(results)
+end
+
+--- @return boolean
+function CompoundExpression:isatomic()
+ return false
+end
+
+--- @return boolean
+function CompoundExpression:isconstant()
+ return false
+end
+
+-----------------
+-- Inheritance --
+-----------------
+
+__CompoundExpression.__index = Expression
+CompoundExpression = setmetatable(CompoundExpression, __CompoundExpression) \ No newline at end of file
diff --git a/macros/luatex/latex/luacas/tex/core/constantexpression.lua b/macros/luatex/latex/luacas/tex/core/constantexpression.lua
new file mode 100644
index 0000000000..0f91a037a9
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/core/constantexpression.lua
@@ -0,0 +1,62 @@
+--- @class ConstantExpression
+--- @alias Constant ConstantExpression
+--- Interface for a mathematical expression without any symbols.
+--- ConstantExpressions are AtomicExpressions by default, but individual classes may overwrite that inheritance.
+ConstantExpression = {}
+__ConstantExpression = {}
+
+----------------------
+-- Instance methods --
+----------------------
+
+
+--- @param symbol SymbolExpression
+--- @return boolean
+function ConstantExpression:freeof(symbol)
+ return true
+end
+
+--- @return boolean
+function ConstantExpression:isconstant()
+ return true
+end
+
+--- @param other Expression
+--- @return boolean
+function ConstantExpression:order(other)
+
+ -- Constants come before non-constants.
+ if not other:isconstant() then
+ return true
+ end
+
+ if self ~= E and self ~= PI and self ~= I then
+ if other ~= E and other ~= PI and other ~= I then
+ -- If both self and other are ring elements, we use the total order on the ring to sort.
+ return self < other
+ end
+ -- Special constants come after ring elements.
+ return true
+ end
+
+ -- Special constants come after ring elements.
+ if other ~= E and other ~= PI and other ~= I then
+ return false
+ end
+
+ -- Ensures E < PI < I.
+
+ if self == E then return true end
+
+ if self == I then return false end
+
+ return other == I
+end
+
+-----------------
+-- Inheritance --
+-----------------
+
+__ConstantExpression.__index = AtomicExpression
+ConstantExpression = setmetatable(ConstantExpression, __ConstantExpression)
+
diff --git a/macros/luatex/latex/luacas/tex/core/expression.lua b/macros/luatex/latex/luacas/tex/core/expression.lua
new file mode 100644
index 0000000000..8ff4659f79
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/core/expression.lua
@@ -0,0 +1,280 @@
+--- @class Expression
+--- Interface for an arbitrary mathematical expression.
+Expression = {}
+
+----------------------
+-- Required methods --
+----------------------
+
+--- Evaluates the current expression recursively by evaluating each sub-expression.
+--- @return Expression
+function Expression:evaluate()
+ error("Called unimplemented method : evaluate()")
+end
+
+--- Performs automatic simplification of an expression. Called on every expression before being output from the CAS.
+--- @return Expression
+function Expression:autosimplify()
+ error("Called unimplemented method : autosimplify()")
+end
+
+--- Performs more rigorous simplification of an expression. Checks different equivalent forms and determines the 'smallest' expresion.
+--- @return Expression
+function Expression:simplify()
+ local me = self:unlock():autosimplify()
+ local results = {}
+ for index, expression in ipairs(me:subexpressions()) do
+ results[index] = expression:simplify()
+ end
+ me = me:setsubexpressions(results)
+
+ local out = me
+ local minsize = self:size()
+
+ local test = me:expand()
+ if test:size() < minsize then
+ out = test
+ minsize = test:size()
+ end
+
+ test = me:factor()
+ if test:size() < minsize then
+ out = test
+ minsize = test:size()
+ end
+
+ return out
+end
+
+--- Changes the autosimplify behavior of an expression depending on its parameters.
+--- THIS METHOD MUTATES THE OBJECT IT ACTS ON.
+--- @param mode number
+--- @param permanent boolean
+--- @param recursive boolean
+--- @return Expression
+function Expression:lock(mode, permanent, recursive)
+ function self:autosimplify()
+ if not permanent then
+ self.autosimplify = nil
+ end
+
+ if mode == Expression.NIL then
+ return self
+ elseif mode == Expression.SUBS then
+ local results = {}
+ for index, expression in ipairs(self:subexpressions()) do
+ results[index] = expression:autosimplify()
+ end
+ self = self:setsubexpressions(results, true) -- TODO: Add mutate setsubexpressions
+ return self
+ end
+ end
+ if recursive then
+ for _, expression in ipairs(self:subexpressions()) do
+ expression:lock(mode, permanent, recursive)
+ end
+ end
+ return self
+end
+
+--- Frees any locks on expressions.
+--- THIS METHOD MUTATES THE OBJECT IT ACTS ON.
+--- @param recursive boolean
+--- @return Expression
+function Expression:unlock(recursive)
+ self.autosimplify = nil
+ if recursive then
+ for _, expression in ipairs(self:subexpressions()) do
+ expression:unlock(recursive)
+ end
+ end
+ return self
+end
+
+--- Returns a list of all subexpressions of an expression.
+--- @return table<number, Expression>
+function Expression:subexpressions()
+ error("Called unimplemented method : subexpressions()")
+end
+
+--- Returns the total number of atomic and compound expressions that make up an expression, or the number of nodes in the expression tree.
+--- @return Integer
+function Expression:size()
+ local out = Integer.one()
+ for _, expression in ipairs(self:subexpressions()) do
+ out = out + expression:size()
+ end
+ return out
+end
+
+--- Returns a copy of the original expression with each subexpression substituted with a new one, or a mutated version if mutate is true.
+--- @param subexpressions table<number, Expression>
+--- @param mutate boolean
+--- @return Expression
+function Expression:setsubexpressions(subexpressions, mutate)
+ error("Called unimplemented method : setsubexpressions()")
+end
+
+--- Determines whether or not an expression contains a particular symbol.
+--- @param symbol SymbolExpression
+--- @return boolean
+function Expression:freeof(symbol)
+ error("Called unimplemented method : freeof()")
+end
+
+--- Substitutes occurances of specified sub-expressions with other sub-expressions.
+--- @param map table<Expression, Expression>
+--- @return Expression
+function Expression:substitute(map)
+ error("Called unimplemented method : substitute()")
+end
+
+--- Algebraically expands an expression by turning products of sums into sums of products and expanding powers.
+--- @return Expression
+function Expression:expand()
+ return self
+end
+
+--- Attempts to factor an expression by turning sums of products into products of sums, and identical terms multiplied together into factors.
+--- @return Expression
+function Expression:factor()
+ return self
+end
+
+--- Attempts to combine an expression by collapsing sums of expressions together into a single factor, e.g. common denominator
+--- @return Expression
+function Expression:combine()
+ return self
+end
+
+--- Attempts to collect all occurances of an expression in this expression.
+--- @param collect Expression
+--- @return Expression
+function Expression:collect(collect)
+ return self
+end
+
+--- Returns all non-constant subexpressions of this expression - helper method for factor.
+--- @return table<number, Expression>
+function Expression:getsubexpressionsrec()
+ local result = {}
+
+ for _, expression in ipairs(self:subexpressions()) do
+ if not expression:isconstant() then
+ result[#result+1] = expression
+ end
+ result = JoinArrays(result, expression:getsubexpressionsrec())
+ end
+
+ return result
+end
+
+--- Determines whether an expression is atomic.
+--- Atomic expressions are not necessarily constant, since polynomial rings, for instance, are atomic parts that contain symbols.
+--- @return boolean
+function Expression:isatomic()
+ error("Called unimplemented method: isatomic()")
+end
+
+--- Determines whether an expression is a constant, i.e., an atomic expression that is not a varaible and cannot be converted into an equivalent compound expression.
+--- @return boolean
+function Expression:isconstant()
+ error("Called unimplemented method: isconstant()")
+end
+
+--- Determines whether an expression is a 'proper' real constant, i.e., is free of every varaible.
+function Expression:isrealconstant()
+ if self:isconstant() or self == PI or self == E then
+ return true
+ end
+
+ for _, expression in ipairs(self:subexpressions()) do
+ if not expression:isrealconstant() then
+ return false
+ end
+ end
+
+ return self:type() ~= SymbolExpression
+end
+
+--- Determines whether an expression is a 'proper' complex constant, i.e., is free of every varaible and is of the form a + bI for nonzero a and b.
+--- @return boolean
+function Expression:iscomplexconstant()
+ return self:isrealconstant() or (self.operation == BinaryOperation.ADD and #self.expressions == 2 and self.expressions[1]:isrealconstant()
+ and ((self.expressions[2].operation == BinaryOperation.MUL and #self.expressions[2].expressions == 2 and self.expressions[2].expressions[1]:isrealconstant() and self.expressions[2].expressions[2] == I)
+ or self.expressions[2] == I)) or (self.operation == BinaryOperation.MUL and #self.expressions == 2 and self.expressions[1]:isrealconstant() and self.expressions[2] == I)
+end
+
+--- A total order on autosimplified expressions. Returns true if self < other.
+--- @param other Expression
+--- @return boolean
+function Expression:order(other)
+ error("Called unimplemented method: order()")
+end
+
+--- Returns an autosimplified expression as a single-variable polynomial in a ring, if it can be converted. Returns itself otherwise.
+--- @return PolynomialRing, boolean
+function Expression:topolynomial()
+ return self, false
+end
+
+--- Converts this expression to LaTeX code.
+--- @return string
+function Expression:tolatex()
+ error("Called Unimplemented method: tolatex()")
+end
+
+----------------------
+-- Instance methods --
+----------------------
+
+--- Returns the type of the expression, i.e., the table used to create objects of that type.
+--- @return table
+function Expression:type()
+ return getmetatable(self).__index
+end
+
+--------------------------
+-- Instance metamethods --
+--------------------------
+
+__ExpressionOperations = {}
+
+__ExpressionOperations.__unm = function(a)
+ return BinaryOperation.SUBEXP({a})
+end
+
+__ExpressionOperations.__add = function(a, b)
+ return BinaryOperation.ADDEXP({a, b})
+end
+
+__ExpressionOperations.__sub = function(a, b)
+ return BinaryOperation.SUBEXP({a, b})
+end
+
+__ExpressionOperations.__mul = function(a, b)
+ return BinaryOperation.MULEXP({a, b})
+end
+
+__ExpressionOperations.__div = function(a, b)
+ return BinaryOperation.DIVEXP({a, b})
+end
+
+__ExpressionOperations.__pow = function(a, b)
+ return BinaryOperation.POWEXP({a, b})
+end
+
+-- For iterating over the subexpressions of a easily.
+__ExpressionOperations.__call = function(a, ...)
+ if a:type() == SymbolExpression then
+ return FunctionExpression(a, table.pack(...))
+ end
+ return BinaryOperation.MULEXP({a, table.pack(...)[1]})
+end
+
+----------------------
+-- Static constants --
+----------------------
+
+Expression.NIL = 0
+Expression.SUBS = 1 \ No newline at end of file
diff --git a/macros/luatex/latex/luacas/tex/core/functionexpression.lua b/macros/luatex/latex/luacas/tex/core/functionexpression.lua
new file mode 100644
index 0000000000..dc94790b81
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/core/functionexpression.lua
@@ -0,0 +1,295 @@
+--- @class FunctionExpression
+--- Represents a generic function that takes zero or more expressions as inputs.
+--- @field name SymbolExpression
+--- @field expressions table<number, Expression>
+--- @field orders table<number, Integer>
+--- @field variables table<number,SymbolExpression>
+--- @alias Function FunctionExpression
+FunctionExpression = {}
+__FunctionExpression = {}
+
+----------------------------
+-- Instance functionality --
+----------------------------
+
+--- Creates a new function expression with the given operation.
+--- @param name string|SymbolExpression
+--- @param expressions table<number, Expression>
+--- @param derivatives table<number,Integer>
+--- @return FunctionExpression
+function FunctionExpression:new(name, expressions, derivatives)
+ local o = {}
+ local __o = Copy(__ExpressionOperations)
+
+ if type(name) == "table" and name:type() == SymbolExpression then
+ name = name.symbol
+ end
+
+ if TrigExpression.NAMES[name] and #expressions == 1 then
+ return TrigExpression(name, expressions[1])
+ end
+
+ -- TODO: Symbol Checking For Constructing derivatives like this
+ --if string.sub(name, #name, #name) == "'" and #expressions == 1 then
+ -- return DerivativeExpression(FunctionExpression(string.sub(name, 1, #name - 1), expressions), SymbolExpression("x"), true)
+ --end
+
+ o.name = name
+ o.expressions = Copy(expressions)
+ o.variables = Copy(expressions)
+ for _,expression in ipairs(o.variables) do
+ if not expression:isatomic() then
+ o.variables = {}
+ if #o.expressions < 4 then
+ local defaultvars = {SymbolExpression('x'),SymbolExpression('y'),SymbolExpression('z')}
+ for i=1,#o.expressions do
+ o.variables[i] = defaultvars[i]
+ end
+ else
+ for i=1,#o.expressions do
+ o.variables[i] = SymbolExpression('x_'..tostring(i))
+ end
+ end
+ end
+ end
+ if derivatives then
+ o.derivatives = Copy(derivatives)
+ else
+ o.derivatives = {}
+ for i=1,#o.variables do
+ o.derivatives[i] = Integer.zero()
+ end
+ end
+
+ __o.__index = FunctionExpression
+ __o.__tostring = function(a)
+ local total = Integer.zero()
+ for _,integer in ipairs(a.derivatives) do
+ total = total + integer
+ end
+ if total == Integer.zero() then
+ local out = a.name .. '('
+ for index, expression in ipairs(a.expressions) do
+ out = out .. tostring(expression)
+ if a.expressions[index + 1] then
+ out = out .. ', '
+ end
+ end
+ return out .. ')'
+ else
+ local out = 'd'
+ if total > Integer.one() then
+ out = out ..'^' .. tostring(total)
+ end
+ out = out .. a.name .. '/'
+ for index,integer in ipairs(a.derivatives) do
+ if integer > Integer.zero() then
+ out = out .. 'd' .. tostring(a.variables[index])
+ if integer > Integer.one() then
+ out = out .. '^' .. tostring(integer)
+ end
+ end
+ end
+ out = out .. '('
+ for index, expression in ipairs(a.expressions) do
+ out = out .. tostring(expression)
+ if a.expressions[index + 1] then
+ out = out .. ', '
+ end
+ end
+ return out .. ')'
+ end
+ end
+ __o.__eq = function(a, b)
+ -- if b:type() == TrigExpression then
+ -- return a == b:tofunction()
+ -- end
+ if b:type() ~= FunctionExpression then
+ return false
+ end
+ if #a.expressions ~= #b.expressions then
+ return false
+ end
+ for index, _ in ipairs(a.expressions) do
+ if a.expressions[index] ~= b.expressions[index] then
+ return false
+ end
+ end
+ for index,_ in ipairs(a.derivatives) do
+ if a.derivatives[index] ~= b.derivatives[index] then
+ return false
+ end
+ end
+ return a.name == b.name
+ end
+
+ o = setmetatable(o, __o)
+
+ return o
+end
+
+--- @return FunctionExpression
+function FunctionExpression:evaluate()
+ local results = {}
+ for index, expression in ipairs(self:subexpressions()) do
+ results[index] = expression:evaluate()
+ end
+ local result = FunctionExpression(self.name, results, self.derivatives)
+ result.variables = self.variables
+ return result
+end
+
+--- @return FunctionExpression
+function FunctionExpression:autosimplify()
+ -- Since the function is completely generic, we can't really do anything execpt autosimplify subexpressions.
+ local results = {}
+ for index, expression in ipairs(self:subexpressions()) do
+ results[index] = expression:autosimplify()
+ end
+ local result = FunctionExpression(self.name, results, self.derivatives)
+ result.variables = self.variables
+ return result
+end
+
+--- @return table<number, Expression>
+function FunctionExpression:subexpressions()
+ return self.expressions
+end
+
+--- @param subexpressions table<number, Expression>
+--- @return FunctionExpression
+function FunctionExpression:setsubexpressions(subexpressions)
+ local result = FunctionExpression(self.name, subexpressions, self.derivatives)
+ result.variables = self.variables
+ return result
+end
+
+--- @param other Expression
+--- @return boolean
+function FunctionExpression:order(other)
+ if other:isatomic() then
+ return false
+ end
+
+ -- CASC Autosimplfication has some symbols appearing before functions, but that looks bad to me, so all symbols appear before products now.
+ -- if other:type() == SymbolExpression then
+ -- return SymbolExpression(self.name):order(other)
+ -- end
+
+ if other:type() == BinaryOperation then
+ if other.operation == BinaryOperation.ADD or other.operation == BinaryOperation.MUL then
+ return BinaryOperation(other.operation, {self}):order(other)
+ end
+
+ if other.operation == BinaryOperation.POW then
+ return (self^Integer.one()):order(other)
+ end
+ end
+
+ if other:type() == SqrtExpression then
+ return self:order(other:topower())
+ end
+
+ -- TODO: Make Logarithm and AbsExpression inherit from function expression to reduce code duplication
+ if other:type() == Logarithm then
+ return self:order(FunctionExpression("log", {other.base, other.expression}))
+ end
+
+ if other:type() ~= FunctionExpression and other:type() ~= TrigExpression then
+ return true
+ end
+
+ if self.name ~= other.name then
+ return SymbolExpression(self.name):order(SymbolExpression(other.name))
+ end
+
+ local k = 1
+ while self:subexpressions()[k] and other:subexpressions()[k] do
+ if self:subexpressions()[k] ~= other:subexpressions()[k] then
+ return self:subexpressions()[k]:order(other:subexpressions()[k])
+ end
+ k = k + 1
+ end
+ return #self.expressions < #other.expressions
+end
+
+--- @return string
+function FunctionExpression:tolatex()
+ local out = tostring(self.name)
+ if self:type() == TrigExpression then
+ out = "\\" .. out
+ end
+ if self:type() ~= TrigExpression and #self.name>1 then
+ --if out:sub(2,2) ~= "'" then
+ --local fp = out:find("'")
+ --if fp then
+ -- out = '\\operatorname{' .. out:sub(1,fp-1) .. '}' .. out:sub(fp,-1)
+ --else
+ out = '\\operatorname{' .. out .. '}'
+ --end
+ --end
+ end
+ local total = Integer.zero()
+ for _,integer in ipairs(self.derivatives) do
+ total = total + integer
+ end
+ if #self.expressions == 1 then
+ if total == Integer.zero() then
+ goto continue
+ else
+ if total < Integer(5) then
+ while total > Integer.zero() do
+ out = out .. "'"
+ total = total - Integer.one()
+ end
+ else
+ out = out .. '^{(' .. total:tolatex() .. ')}'
+ end
+ end
+ end
+ if #self.expressions > 1 then
+ if total == Integer.zero() then
+ goto continue
+ else
+ if total < Integer(4) then
+ out = out .. '_{'
+ for index,integer in ipairs(self.derivatives) do
+ local i = integer:asnumber()
+ while i > 0 do
+ out = out .. self.variables[index]:tolatex()
+ i = i - 1
+ end
+ end
+ out = out .. '}'
+ else
+ out = '\\frac{\\partial^{' .. total:tolatex() .. '}' .. out .. '}{'
+ for index, integer in ipairs(self.derivatives) do
+ if integer > Integer.zero() then
+ out = out .. '\\partial ' .. self.variables[index]:tolatex()
+ if integer ~= Integer.one() then
+ out = out .. '^{' .. integer:tolatex() .. '}'
+ end
+ end
+ end
+ out = out .. '}'
+ end
+ end
+ end
+ ::continue::
+ out = out ..'\\mathopen{}' .. '\\left('
+ for index, expression in ipairs(self:subexpressions()) do
+ out = out .. expression:tolatex()
+ if self:subexpressions()[index + 1] then
+ out = out .. ', '
+ end
+ end
+ return out .. '\\right)'
+end
+
+-----------------
+-- Inheritance --
+-----------------
+
+__FunctionExpression.__index = CompoundExpression
+__FunctionExpression.__call = FunctionExpression.new
+FunctionExpression = setmetatable(FunctionExpression, __FunctionExpression) \ No newline at end of file
diff --git a/macros/luatex/latex/luacas/tex/core/symbolexpression.lua b/macros/luatex/latex/luacas/tex/core/symbolexpression.lua
new file mode 100644
index 0000000000..0d9192c084
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/core/symbolexpression.lua
@@ -0,0 +1,132 @@
+--- @class SymbolExpression
+--- An atomic expression corresponding to a symbol representing an arbitary value.
+--- @field symbol string
+--- @alias Symbol SymbolExpression
+SymbolExpression = {}
+__SymbolExpression = {}
+
+----------------------
+-- Instance methods --
+----------------------
+
+--- Given the name of the symbol as a string, creates a new symbol.
+--- @param symbol string
+--- @return SymbolExpression
+function SymbolExpression:new(symbol)
+ local o = {}
+ local __o = Copy(__ExpressionOperations)
+ __o.__index = SymbolExpression
+ __o.__tostring = function(a)
+ return a.symbol
+ end
+ __o.__eq = function(a, b)
+ return a.symbol == b.symbol
+ end
+
+ if type(symbol) ~= "string" then
+ error("Sent parameter of wrong type: symbol must be a string")
+ end
+
+ o.symbol = symbol
+ o = setmetatable(o, __o)
+
+ return o
+end
+
+--- @return boolean
+function SymbolExpression:freeof(symbol)
+ return symbol~=self
+end
+
+--- @return boolean
+function SymbolExpression:isconstant()
+ return false
+end
+
+--- @param other Expression
+--- @return boolean
+function SymbolExpression:order(other)
+
+ -- Symbol Expressions come after constant expressions.
+ if other:isconstant() then
+ return false
+ end
+
+ -- Lexographic order on symbols.
+ if other:type() == SymbolExpression then
+ for i = 1, math.min(#self.symbol, #other.symbol) do
+ if string.byte(self.symbol, i) ~= string.byte(other.symbol, i) then
+ return string.byte(self.symbol, i) < string.byte(other.symbol, i)
+ end
+ end
+
+ return #self.symbol < #other.symbol
+ end
+
+ if other.operation == BinaryOperation.POW then
+ return BinaryOperation(BinaryOperation.POW, {self, Integer.one()}):order(other)
+ end
+
+ if other.operation == BinaryOperation.MUL then
+ return BinaryOperation(BinaryOperation.MUL, {self}):order(other)
+ end
+
+ if other.operation == BinaryOperation.ADD then
+ return BinaryOperation(BinaryOperation.ADD, {self}):order(other)
+ end
+
+ -- CASC Autosimplfication has some symbols appearing before functions, but that looks bad to me, so all symbols appear before products now.
+ if other:type() == FunctionExpression or other:type() == TrigExpression or other:type() == Logarithm then
+ return true
+ end
+
+ return false
+end
+
+--- Converts this symbol to an element of a polynomial ring.
+--- @return PolynomialRing, boolean
+function SymbolExpression:topolynomial()
+ return PolynomialRing({Integer.zero(), Integer.one()}, self.symbol), true
+end
+
+-----------------
+-- Inheritance --
+-----------------
+
+__SymbolExpression.__index = AtomicExpression
+__SymbolExpression.__call = SymbolExpression.new
+SymbolExpression = setmetatable(SymbolExpression, __SymbolExpression)
+
+
+----------------------
+-- Static Constants --
+----------------------
+
+-- The constant pi.
+PI = SymbolExpression("pi")
+function PI:tolatex()
+ return "\\pi "
+end
+
+-- Approximates pi as a rational number. Uses continued fraction expansion.
+function PI:approximate()
+ return Integer(313383936) / Integer(99753205)
+end
+
+--function PI:isconstant()
+-- return true
+--end
+
+-- The constant e.
+E = SymbolExpression("e")
+
+-- Approximates pi as a rational number. Uses continued fraction expansion.
+function E:approximate()
+ return Integer(517656) / Integer(190435)
+end
+
+-- The imaginary constant i.
+I = SymbolExpression("i")
+function I:tolatex()
+ return "i"
+end \ No newline at end of file