summaryrefslogtreecommitdiff
path: root/macros/luatex/latex/luacas/tex/algebra/luacas-trigexpression.lua
diff options
context:
space:
mode:
Diffstat (limited to 'macros/luatex/latex/luacas/tex/algebra/luacas-trigexpression.lua')
-rw-r--r--macros/luatex/latex/luacas/tex/algebra/luacas-trigexpression.lua355
1 files changed, 355 insertions, 0 deletions
diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-trigexpression.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-trigexpression.lua
new file mode 100644
index 0000000000..307bfe1737
--- /dev/null
+++ b/macros/luatex/latex/luacas/tex/algebra/luacas-trigexpression.lua
@@ -0,0 +1,355 @@
+--- @class TrigExpression
+--- Represents a trigonometric function from one expression to another.
+--- @field name string
+--- @field expression Expression
+TrigExpression = {}
+__TrigExpression = {}
+
+----------------------------
+-- Instance functionality --
+----------------------------
+
+--- Creates a new trig expression with the given name and expression.
+--- @param name string|SymbolExpression
+--- @param expression Expression
+--- @return TrigExpression
+function TrigExpression:new(name, expression)
+ local o = {}
+ local __o = Copy(__ExpressionOperations)
+
+ if not TrigExpression.NAMES[name] then
+ error("Argument error: " .. name .. " is not the name of a trigonometric function.")
+ end
+
+ o.name = name
+ o.expression = expression
+ o.expressions = {expression}
+ if expression:isatomic() then
+ o.variables = {expression}
+ else
+ o.variables = {SymbolExpression('x')}
+ end
+ o.derivatives = {Integer.zero()}
+
+ __o.__index = TrigExpression
+ __o.__tostring = function(a)
+ return tostring(a.name) .. '(' .. tostring(a.expression) .. ')'
+ end
+ __o.__eq = function(a, b)
+ -- if b:type() == FunctionExpression then
+ -- return a:tofunction() == b
+ -- end
+ -- 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() == TrigExpression then
+ return false
+ end
+ return a.name == b.name and a.expression == b.expression
+ end
+
+ o = setmetatable(o, __o)
+ return o
+end
+
+--- @return TrigExpression
+function TrigExpression:evaluate()
+ local expression = self.expression:autosimplify()
+
+ if expression == Integer.zero() then
+ if self.name == "cos" or self.name == "sec" then
+ return Integer.one()
+ end
+ if self.name == "sin" or self.name == "tan" then
+ return Integer.zero()
+ end
+ if self.name == "arctan" or self.name == "arcsin" then
+ return Integer.zero()
+ end
+ if self.name == "arccos" or self.name == "arccot" then
+ return PI / Integer(2)
+ end
+ end
+
+ if expression == PI then
+ if self.name == "cos" or self.name == "sec" then
+ return Integer(-1)
+ end
+ if self.name == "sin" or self.name == "tan" then
+ return Integer.zero()
+ end
+ end
+
+ if expression:ismulratlPI() then
+ local coeff = expression.expressions[1]
+ if TrigExpression.COSVALUES[tostring(coeff)] ~= nil then
+ if self.name == "cos" then
+ return TrigExpression.COSVALUES[tostring(coeff)]:autosimplify()
+ end
+ if self.name == "sin" then
+ local sign = Integer.one()
+ if coeff > Integer.one() then
+ sign = Integer(-1)
+ end
+ return (sign*sqrt(Integer.one()-cos(expression)^Integer(2))):autosimplify()
+ end
+ if self.name == "tan" then
+ return (sin(expression) / cos(expression)):autosimplify()
+ end
+ if self.name == "sec" then
+ return (Integer.one() / cos(expression)):autosimplify()
+ end
+ if self.name == "csc" then
+ return (Integer.one() / sin(expression)):autosimplify()
+ end
+ if self.name == "cot" then
+ return (cos(expression) / sin(expression)):autosimplify()
+ end
+ end
+ end
+
+ if TrigExpression.ACOSVALUES[tostring(expression)] ~= nil then
+ if self.name == "arccos" then
+ return TrigExpression.ACOSVALUES[tostring(expression)]:autosimplify()
+ end
+ if self.name == "arcsin" then
+ if expression == Integer(-1) then
+ return TrigExpression.ACOSVALUES["-1"]:autosimplify()
+ elseif expression.expressions and expression.expressions[1] == Integer(-1) then
+ local expr = (Integer(-1)*sqrt(Integer.one() - expression ^ Integer(2))):autosimplify()
+ return TrigExpression.ACOSVALUES[tostring(expr)]:autosimplify()
+ else
+ local expr = (sqrt(Integer.one() - expression ^ Integer(2))):autosimplify()
+ return TrigExpression.ACOSVALUES[tostring(expr)]:autosimplify()
+ end
+ end
+ end
+
+ if self.name == "arctan" and TrigExpression.ATANVALUES[tostring(expression)] ~= nil then
+ return TrigExpression.ATANVALUES[tostring(expression)]:autosimplify()
+ end
+
+ return self
+end
+
+--- checks if expression is a rational multiple of pi
+--- @return boolean
+function Expression:ismulratlPI()
+ if self.operation == BinaryOperation.MUL and #self.expressions == 2 and (self.expressions[1]:type() == Integer or self.expressions[1]:type() == Rational) and self.expressions[2] == PI then
+ return true
+ end
+
+ return false
+end
+
+--- @return TrigExpression
+function TrigExpression:autosimplify()
+ local expression = self.expression:autosimplify()
+
+ -- even and odd properties of trig functions
+ if (self.name == "sin" or self.name == "tan" or self.name == "csc" or self.name == "cot") and
+ expression.operation == BinaryOperation.MUL and expression.expressions[1]:isconstant() and expression.expressions[1] < Integer(0) then
+ return (-Integer.one() * TrigExpression(self.name, -expression)):autosimplify()
+ end
+
+ if (self.name == "cos" or self.name == "sec") and
+ expression.operation == BinaryOperation.MUL and expression.expressions[1]:isconstant() and expression.expressions[1] < Integer(0) then
+ expression = (-expression):autosimplify()
+ end
+
+ -- uses periodicity of sin and cos and friends
+ if self.name == "sin" or self.name == "cos" or self.name == "csc" or self.name == "sec" then
+ if expression == Integer.zero() or expression == PI then
+ goto skip
+ end
+ if expression.operation ~= BinaryOperation.ADD then
+ expression = BinaryOperation(BinaryOperation.ADD,{expression})
+ end
+ for index,component in ipairs(expression.expressions) do
+ if component:ismulratlPI() then
+ local coeff = component.expressions[1]
+ if coeff:type() == Integer then
+ coeff = coeff % Integer(2)
+ coeff = coeff:autosimplify()
+ end
+ if coeff:type() == Rational then
+ local n = coeff.numerator
+ local d = coeff.denominator
+ local m = {n:divremainder(d)}
+ coeff = (m[1] % Integer(2)) + m[2]/d
+ coeff = coeff:autosimplify()
+ end
+ expression.expressions[index].expressions[1] = coeff
+ end
+ expression = expression:autosimplify()
+ end
+ ::skip::
+ end
+
+ -- uses periodicity of tan and cot
+ if self.name == "tan" or self.name == "cot" then
+ if expression == Integer.zero() or expression == PI then
+ goto skip
+ end
+ if expression.operation ~= BinaryOperation.ADD then
+ expression = BinaryOperation(BinaryOperation.ADD,{expression})
+ end
+ for index,component in ipairs(expression.expressions) do
+ if component:ismulratlPI() then
+ local coeff = component.expressions[1]
+ if coeff:type() == Integer then
+ coeff = Integer.zero()
+ end
+ if coeff:type() == Rational then
+ local n = coeff.numerator
+ local d = coeff.denominator
+ local m = {n:divremainder(d)}
+ coeff = m[2]/d
+ coeff = coeff:autosimplify()
+ end
+ expression.expressions[index].expressions[1] = coeff
+ end
+ if component == PI then
+ expression.expressions[index] = Integer.zero()
+ end
+ end
+ expression = expression:autosimplify()
+ ::skip::
+ end
+
+ return TrigExpression(self.name, expression):evaluate()
+end
+
+--- @return table<number, Expression>
+function TrigExpression:subexpressions()
+ return {self.expression}
+end
+
+--- @param subexpressions table<number, Expression>
+--- @return TrigExpression
+function TrigExpression:setsubexpressions(subexpressions)
+ return TrigExpression(self.name, subexpressions[1])
+end
+
+-- function TrigExpression:freeof(symbol)
+-- return self.expression:freeof(symbol)
+-- end
+
+-- function TrigExpression:substitute(map)
+-- for expression, replacement in pairs(map) do
+-- if self == expression then
+-- return replacement
+-- end
+-- end
+-- return TrigExpression(self.name, self.expression:substitute(map))
+-- end
+
+-- function TrigExpression:order(other)
+-- return self:tofunction():order(other)
+-- end
+
+-- function TrigExpression:tofunction()
+-- return FunctionExpression(self.name, {self.expression}, true)
+-- end
+
+-----------------
+-- Inheritance --
+-----------------
+
+__TrigExpression.__index = FunctionExpression
+__TrigExpression.__call = TrigExpression.new
+TrigExpression = setmetatable(TrigExpression, __TrigExpression)
+
+----------------------
+-- Static constants --
+----------------------
+TrigExpression.NAMES = {sin=1, cos=2, tan=3, csc=4, sec=5, cot=6,
+ arcsin=7, arccos=8, arctan=9, arccsc=10, arcsec=11, arccot=12}
+
+TrigExpression.INVERSES = {sin="arcsin", cos="arccos", tan="arctan", csc="arccsc", sec="arcsec", cot="arccot",
+ arcsin="sin", arccos="cos", arctan="tan", arccsc="csc", arcsec="sec", arccot="cot"}
+
+TrigExpression.COSVALUES = {
+ ["0"] = Integer.one(),
+ ["1/6"] = sqrt(Integer(3))/Integer(2),
+ ["1/4"] = sqrt(Integer(2))/Integer(2),
+ ["1/3"] = Integer.one()/Integer(2),
+ ["1/2"] = Integer.zero(),
+ ["2/3"] = -Integer.one()/Integer(2),
+ ["3/4"] = -sqrt(Integer(2))/Integer(2),
+ ["5/6"] = -sqrt(Integer(3))/Integer(2),
+ ["1"] = -Integer.one(),
+ ["7/6"] = -sqrt(Integer(3))/Integer(2),
+ ["5/4"] = -sqrt(Integer(2))/Integer(2),
+ ["4/3"] = -Integer.one()/Integer(2),
+ ["3/2"] = Integer.zero(),
+ ["5/3"] = Integer.one()/Integer(2),
+ ["7/4"] = sqrt(Integer(2))/Integer(2),
+ ["11/6"] = sqrt(Integer(3))/Integer(2),
+}
+TrigExpression.ACOSVALUES = {
+ ["1"] = Integer.zero(),
+ ["(1/2 * sqrt(3,2))"] = PI * Integer(6) ^ Integer(-1),
+ ["(1/2 * sqrt(2,2))"] = PI * Integer(4) ^ Integer(-1),
+ ["1/2"] = PI * Integer(3) ^ Integer(-1),
+ ["0"] = PI * Integer(2) ^ Integer(-1),
+ ["-1/2"] = PI * Integer(2) * Integer(3) ^ Integer(-1),
+ ["(-1/2 * sqrt(2,2))"]= PI * Integer(3) * Integer(4) ^ Integer(-1),
+ ["(-1/2 * sqrt(3,2))"]= PI * Integer(5) * Integer(6) ^ Integer(-1),
+ ["-1"] = Integer(-1)*PI,
+}
+TrigExpression.ATANVALUES = {
+ ["(-1 * sqrt(3,2))"] = Integer(-1) * PI * Integer(3) ^ Integer(-1),
+ ["-1"] = Integer(-1) * PI * Integer(4) ^ Integer(-1),
+ ["(-1/3 * sqrt(3,2))"] = Integer(-1) * Integer(6) ^ Integer(-1),
+ ["0"] = Integer.zero(),
+ ["(1/3 * sqrt(3,2))"] = PI * Integer(6) ^ Integer(-1),
+ ["1"] = PI * Integer(4) ^ Integer(-1),
+ ["sqrt(3,2)"] = PI * Integer(3) ^ Integer(-1)
+}
+
+SIN = function (a)
+ return TrigExpression("sin", a)
+end
+
+COS = function (a)
+ return TrigExpression("cos", a)
+end
+
+TAN = function (a)
+ return TrigExpression("tan", a)
+end
+
+CSC = function (a)
+ return TrigExpression("csc", a)
+end
+
+SEC = function (a)
+ return TrigExpression("sec", a)
+end
+
+COT = function (a)
+ return TrigExpression("cot", a)
+end
+
+ARCSIN = function (a)
+ return TrigExpression("arcsin", a)
+end
+
+ARCCOS = function (a)
+ return TrigExpression("arccos", a)
+end
+
+ARCTAN = function (a)
+ return TrigExpression("arctan", a)
+end
+
+ARCCSC = function (a)
+ return TrigExpression("arccsc", a)
+end
+
+ARCSEC = function (a)
+ return TrigExpression("arcsec", a)
+end
+
+ARCCOT = function (a)
+ return TrigExpression("arccot", a)
+end \ No newline at end of file