summaryrefslogtreecommitdiff
path: root/macros/luatex/latex/luacas/tex/calculus/derivativeexpression.lua
diff options
context:
space:
mode:
Diffstat (limited to 'macros/luatex/latex/luacas/tex/calculus/derivativeexpression.lua')
-rw-r--r--macros/luatex/latex/luacas/tex/calculus/derivativeexpression.lua265
1 files changed, 265 insertions, 0 deletions
diff --git a/macros/luatex/latex/luacas/tex/calculus/derivativeexpression.lua b/macros/luatex/latex/luacas/tex/calculus/derivativeexpression.lua
new file mode 100644
index 0000000000..b757759c4c
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/calculus/derivativeexpression.lua
@@ -0,0 +1,265 @@
+--- @class DerivativeExpression
+--- An expression for a single-variable derivative of an expression.
+--- @field symbol SymbolExpression
+--- @field expression Expression
+DerivativeExpression = {}
+__DerivativeExpression = {}
+
+----------------------------
+-- Instance functionality --
+----------------------------
+
+-- Creates a new single-variable derivative operation with the given symbol and expression.
+--- @param expression Expression
+--- @param symbol Symbol
+--- @return DerivativeExpression
+function DerivativeExpression:new(expression, symbol)
+ local o = {}
+ local __o = Copy(__ExpressionOperations)
+
+ o.expression = Copy(expression)
+ o.symbol = symbol or SymbolExpression("x")
+
+ __o.__index = DerivativeExpression
+ __o.__tostring = function(a)
+ return '(d/d' .. tostring(a.symbol) .. " " .. tostring(a.expression) .. ')'
+ 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 b:type() == DerivativeExpression then
+ return false
+ end
+ return a.symbol == b.symbol and a.expression == b.expression
+ end
+ o = setmetatable(o, __o)
+
+ return o
+end
+
+--- @return Expression
+function DerivativeExpression:evaluate()
+ local exp = self.expression
+
+ -- The derivative of a constant is 0
+ if exp:isconstant() then
+ return Integer.zero()
+ end
+
+ -- The derivative of a symbol is either 1 or 0
+ if exp:type() == SymbolExpression then
+ if self.symbol == exp then
+ return Integer.one()
+ end
+ return Integer.zero()
+ end
+
+ -- Chain rule for arbitrary functions
+
+ if exp:type() == FunctionExpression then
+ local results = {}
+ for index,expression in ipairs(exp.expressions) do
+ local dout = FunctionExpression(exp.name,exp.expressions,exp.derivatives)
+ dout.variables = exp.variables
+ dout.derivatives[index] = dout.derivatives[index]+Integer.one()
+ local dinn = DerivativeExpression(expression,self.symbol):evaluate()
+ results[index] = dout*dinn
+ end
+ return BinaryOperation(BinaryOperation.ADD,results):autosimplify()
+ end
+
+ --if exp:type() == FunctionExpression then
+ -- local results = {}
+ -- for index,expression in ipairs(exp.expressions) do
+ -- local inn = DerivativeExpression(expression,self.symbol):autosimplify()
+ -- local out = Copy(exp)
+ -- out.orders[index] = out.orders[index] + Integer.one()
+ -- local result = inn*out
+ -- table.insert(results,result)
+ -- end
+ -- return BinaryOperation(BinaryOperation.ADD,results):autosimplify()
+ --end
+
+ --if exp:type() == FunctionExpression then
+ -- if exp.expressions[2] then
+ -- return self
+ -- end
+ -- return DerivativeExpression(exp.expressions[1], self.symbol) * FunctionExpression(exp.name, exp.expressions, exp.orders[1] + Integer.one(), exp.variables[1]):autosimplify()
+ --end
+
+ -- Chain rule for trig functions
+ if exp:type() == TrigExpression then
+ local internal = DerivativeExpression(exp.expression, self.symbol)
+
+ if exp.name == "sin" then
+ return (internal * COS(exp.expression)):autosimplify()
+ end
+ if exp.name == "cos" then
+ return (internal * -SIN(exp.expression)):autosimplify()
+ end
+ if exp.name == "tan" then
+ return (internal * SEC(exp.expression)^Integer(2)):autosimplify()
+ end
+ if exp.name == "csc" then
+ return (internal * -CSC(exp.expression)*COT(exp.expression)):autosimplify()
+ end
+ if exp.name == "sec" then
+ return (internal * -SEC(exp.expression)*TAN(exp.expression)):autosimplify()
+ end
+ if exp.name == "cot" then
+ return (internal * -CSC(exp.expression)^Integer(2)):autosimplify()
+ end
+ if exp.name == "arcsin" then
+ return (internal / (Integer(1)-exp.expression^Integer(2))^(Integer(1)/Integer(2))):autosimplify()
+ end
+ if exp.name == "arccos" then
+ return (-internal / (Integer(1)-exp.expression^Integer(2))^(Integer(1)/Integer(2))):autosimplify()
+ end
+ if exp.name == "arctan" then
+ return (internal / (Integer(1)+exp.expression^Integer(2))):autosimplify()
+ end
+ if exp.name == "arccsc" then
+ return (-internal / (ABS(exp.expression) * (Integer(1)-exp.expression^Integer(2))^(Integer(1)/Integer(2)))):autosimplify()
+ end
+ if exp.name == "arcsec" then
+ return (internal / (ABS(exp.expression) * (Integer(1)-exp.expression^Integer(2))^(Integer(1)/Integer(2)))):autosimplify()
+ end
+ if exp.name == "arccot" then
+ return (-internal / (Integer(1)+exp.expression^Integer(2))):autosimplify()
+ end
+ end
+
+ -- TODO: Piecewise functions
+ if self:type() == AbsExpression then
+ return DerivativeExpression(self.expression, self.symbol):autosimplify()
+ end
+
+ -- Uses linearity of derivatives to evaluate sum expressions
+ if exp.operation == BinaryOperation.ADD then
+ local parts = {}
+ for i, expression in pairs(exp.expressions) do
+ parts[i] = DerivativeExpression(expression, self.symbol)
+ end
+ return BinaryOperation(BinaryOperation.ADD, parts):autosimplify()
+ end
+
+ -- Uses product rule to evaluate product expressions
+ if exp.operation == BinaryOperation.MUL then
+ local sums = {}
+ for i, expression in pairs(exp.expressions) do
+ local products = {}
+ for j, innerexpression in pairs(exp.expressions) do
+ if i ~= j then
+ products[j] = innerexpression
+ else
+ products[j] = DerivativeExpression(innerexpression, self.symbol)
+ end
+ end
+ sums[i] = BinaryOperation(BinaryOperation.MUL, products)
+ end
+ return BinaryOperation(BinaryOperation.ADD, sums):autosimplify()
+ end
+
+ -- Uses the generalized power rule to evaluate power expressions
+ if exp.operation == BinaryOperation.POW then
+ local base = exp.expressions[1]
+ local exponent = exp.expressions[2]
+
+ return BinaryOperation.MULEXP({
+ BinaryOperation.POWEXP({base, exponent}),
+ BinaryOperation.ADDEXP({
+ BinaryOperation.MULEXP({
+ DD(base, self.symbol),
+ BinaryOperation.DIVEXP({exponent, base})}),
+ BinaryOperation.MULEXP({
+ DD(exponent, self.symbol),
+ LN(base)})})
+ }):autosimplify()
+ end
+
+ if exp:type() == Logarithm then
+ local base = exp.base
+ local expression = exp.expression
+
+ return BinaryOperation.SUBEXP({
+ BinaryOperation.DIVEXP({DD(expression, self.symbol),
+ BinaryOperation.MULEXP({expression, LN(base)})}),
+
+ BinaryOperation.DIVEXP({
+ BinaryOperation.MULEXP({LN(expression), DD(base, self.symbol)}),
+ BinaryOperation.MULEXP({BinaryOperation.POWEXP({LN(base), Integer(2)}), base})
+ })
+
+ }):autosimplify()
+ end
+
+ return self
+end
+
+--- @return Expression
+function DerivativeExpression:autosimplify()
+ return DerivativeExpression(self.expression:autosimplify(), self.symbol):evaluate()
+end
+
+--- @return table<number, Expression>
+function DerivativeExpression:subexpressions()
+ return {self.expression}
+end
+
+--- @param subexpressions table<number, Expression>
+--- @return DerivativeExpression
+function DerivativeExpression:setsubexpressions(subexpressions)
+ return DerivativeExpression(subexpressions[1], self.symbol)
+end
+
+-- function DerivativeExpression:freeof(symbol)
+-- return self.symbol.freeof(symbol) and self.expression:freeof(symbol)
+-- end
+
+-- Substitutes each expression for a new one.
+-- function DerivativeExpression:substitute(map)
+-- for expression, replacement in pairs(map) do
+-- if self == expression then
+-- return replacement
+-- end
+-- end
+-- -- Typically, we only perform substitution on autosimplified expressions, so this won't get called. May give strange results, i.e.,
+-- -- substituting and then evaluating the derivative may not return the same thing as evaluating the derivative and then substituting.
+-- return DerivativeExpression(self.expression:substitute(map), self.symbol)
+-- end
+
+--- @param other Expression
+--- @return boolean
+function DerivativeExpression:order(other)
+ if other:type() == IntegralExpression then
+ return true
+ end
+
+ if other:type() ~= DerivativeExpression then
+ return false
+ end
+
+ if self.symbol ~= other.symbol then
+ return self.symbol:order(other.symbol)
+ end
+
+ return self.expression:order(other.expression)
+end
+
+--- @return string
+function DerivativeExpression:tolatex()
+ return '\\frac{d}{d' .. self.symbol:tolatex() .. '}\\left(' .. self.expression:tolatex() .. '\\right)'
+end
+
+-----------------
+-- Inheritance --
+-----------------
+__DerivativeExpression.__index = CompoundExpression
+__DerivativeExpression.__call = DerivativeExpression.new
+DerivativeExpression = setmetatable(DerivativeExpression, __DerivativeExpression)
+
+----------------------
+-- Static constants --
+----------------------
+DD = function(expression, symbol)
+ return DerivativeExpression(expression, symbol)
+end \ No newline at end of file