summaryrefslogtreecommitdiff
path: root/macros/luatex/latex/luacas/tex/core/expression.lua
blob: 8ff4659f79c167476f2c2496ace73736bc6ec8d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
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