rcas 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CITATION.cff +17 -0
- data/DESIGN.md +783 -0
- data/LICENSE +21 -0
- data/MANUAL.md +6265 -0
- data/README.md +267 -0
- data/bin/rcas +9 -0
- data/bin/rcas-app +9 -0
- data/bin/rcas-chat +9 -0
- data/lib/rcas/algebraic.rb +481 -0
- data/lib/rcas/analysis.rb +966 -0
- data/lib/rcas/app/launcher.rb +203 -0
- data/lib/rcas/app/public/app.css +402 -0
- data/lib/rcas/app/public/app.js +449 -0
- data/lib/rcas/app/public/index.html +46 -0
- data/lib/rcas/app/server.rb +220 -0
- data/lib/rcas/app/window.rb +94 -0
- data/lib/rcas/app/worksheet.rb +290 -0
- data/lib/rcas/app.rb +168 -0
- data/lib/rcas/background.rb +758 -0
- data/lib/rcas/chat/assistant.rb +199 -0
- data/lib/rcas/chat/picker.rb +164 -0
- data/lib/rcas/chat/repl.rb +583 -0
- data/lib/rcas/chat/session.rb +137 -0
- data/lib/rcas/chat/settings.rb +71 -0
- data/lib/rcas/chat/style.rb +30 -0
- data/lib/rcas/chat/tool.rb +53 -0
- data/lib/rcas/chat/ui.rb +316 -0
- data/lib/rcas/chat/usage.rb +62 -0
- data/lib/rcas/chat/workspace.rb +132 -0
- data/lib/rcas/chat.rb +54 -0
- data/lib/rcas/coefficients.rb +170 -0
- data/lib/rcas/combinatorics.rb +274 -0
- data/lib/rcas/complex_parts.rb +160 -0
- data/lib/rcas/constants.rb +129 -0
- data/lib/rcas/core_ext.rb +35 -0
- data/lib/rcas/decide.rb +501 -0
- data/lib/rcas/decompositions.rb +241 -0
- data/lib/rcas/differentiate.rb +144 -0
- data/lib/rcas/discussion.rb +558 -0
- data/lib/rcas/distributions.rb +980 -0
- data/lib/rcas/dixon.rb +95 -0
- data/lib/rcas/docs.rb +321 -0
- data/lib/rcas/domains.rb +728 -0
- data/lib/rcas/expand.rb +174 -0
- data/lib/rcas/expression.rb +613 -0
- data/lib/rcas/factor.rb +605 -0
- data/lib/rcas/finite_field.rb +577 -0
- data/lib/rcas/fourier.rb +118 -0
- data/lib/rcas/fps.rb +678 -0
- data/lib/rcas/fraction.rb +126 -0
- data/lib/rcas/functions.rb +1136 -0
- data/lib/rcas/gcd.rb +112 -0
- data/lib/rcas/geometry.rb +266 -0
- data/lib/rcas/groebner.rb +162 -0
- data/lib/rcas/hold.rb +277 -0
- data/lib/rcas/hypothesis.rb +364 -0
- data/lib/rcas/inequalities.rb +689 -0
- data/lib/rcas/integral_functions.rb +260 -0
- data/lib/rcas/integrate.rb +1589 -0
- data/lib/rcas/integrate_substitutions.rb +434 -0
- data/lib/rcas/interpolate.rb +40 -0
- data/lib/rcas/irb.rb +146 -0
- data/lib/rcas/laplace.rb +159 -0
- data/lib/rcas/latex.rb +556 -0
- data/lib/rcas/lattice.rb +172 -0
- data/lib/rcas/linear_algebra.rb +117 -0
- data/lib/rcas/linear_program.rb +416 -0
- data/lib/rcas/lint.rb +79 -0
- data/lib/rcas/matrix.rb +531 -0
- data/lib/rcas/matrix_multiply.rb +202 -0
- data/lib/rcas/multimodular.rb +286 -0
- data/lib/rcas/named_polynomials.rb +274 -0
- data/lib/rcas/number_theory.rb +443 -0
- data/lib/rcas/numerics.rb +825 -0
- data/lib/rcas/ode.rb +488 -0
- data/lib/rcas/openmath/objects.rb +364 -0
- data/lib/rcas/openmath/phrasebook.rb +551 -0
- data/lib/rcas/openmath/popcorn.rb +518 -0
- data/lib/rcas/openmath/xml.rb +309 -0
- data/lib/rcas/openmath.rb +49 -0
- data/lib/rcas/petkovsek.rb +165 -0
- data/lib/rcas/piecewise.rb +488 -0
- data/lib/rcas/plot.rb +763 -0
- data/lib/rcas/plot3d.rb +419 -0
- data/lib/rcas/poly_matrix.rb +318 -0
- data/lib/rcas/poly_recurrence.rb +117 -0
- data/lib/rcas/polynomial.rb +466 -0
- data/lib/rcas/precision.rb +925 -0
- data/lib/rcas/printer.rb +150 -0
- data/lib/rcas/product.rb +155 -0
- data/lib/rcas/q_difference.rb +296 -0
- data/lib/rcas/q_functions.rb +158 -0
- data/lib/rcas/q_summation.rb +308 -0
- data/lib/rcas/q_zeilberger.rb +199 -0
- data/lib/rcas/random.rb +506 -0
- data/lib/rcas/rational_function.rb +186 -0
- data/lib/rcas/recurrence.rb +323 -0
- data/lib/rcas/render.rb +431 -0
- data/lib/rcas/results.rb +192 -0
- data/lib/rcas/scalar.rb +219 -0
- data/lib/rcas/series.rb +726 -0
- data/lib/rcas/simplify.rb +649 -0
- data/lib/rcas/solve.rb +2002 -0
- data/lib/rcas/special.rb +163 -0
- data/lib/rcas/statistics.rb +175 -0
- data/lib/rcas/steps.rb +835 -0
- data/lib/rcas/summation.rb +532 -0
- data/lib/rcas/trig.rb +264 -0
- data/lib/rcas/van_hoeij.rb +241 -0
- data/lib/rcas/vector.rb +175 -0
- data/lib/rcas/vector_calculus.rb +411 -0
- data/lib/rcas/version.rb +5 -0
- data/lib/rcas/zeilberger.rb +358 -0
- data/lib/rcas.rb +91 -0
- data/package.json +8 -0
- metadata +206 -0
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module RCAS
|
|
5
|
+
# The text of a term, built only if the ordering of a sum comes down to
|
|
6
|
+
# it - which is after the degree and the exponents have tied, and rarely.
|
|
7
|
+
# Printing every term of a long sum to sort it cost more than the sorting.
|
|
8
|
+
class LazyText
|
|
9
|
+
include Comparable
|
|
10
|
+
|
|
11
|
+
def initialize(&block) = @block = block
|
|
12
|
+
def text = (@text ||= @block.call)
|
|
13
|
+
def <=>(other) = text <=> other.text
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Algebraic simplification.
|
|
17
|
+
#
|
|
18
|
+
# Sums are flattened into (constant, {factors => coefficient}) and products
|
|
19
|
+
# into (coefficient, {base => exponent}); numbers fold exactly using
|
|
20
|
+
# Integer/Rational arithmetic. The result is rebuilt into a tree with a
|
|
21
|
+
# canonical ordering so structurally equal inputs print identically.
|
|
22
|
+
module Simplify
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# Sentinel base for exponential factors: exp(u) is stored as EXP**u so
|
|
26
|
+
# that exp(x)*exp(y) merges into exp(x + y) and exp(x)**2 into exp(2*x).
|
|
27
|
+
def exp_base = (@exp_base ||= Fn.new(:exp, [Num.new(1)]))
|
|
28
|
+
|
|
29
|
+
def simplify(expr)
|
|
30
|
+
case expr
|
|
31
|
+
when Num, Var then expr
|
|
32
|
+
when Add, Sub, Neg then rebuild_sum(*termize(expr, simplify: true))
|
|
33
|
+
when Mul, Div, Pow then rebuild_product(*factorize(expr, simplify: true))
|
|
34
|
+
when Fn then Functions.fold(expr.map_children { |c| simplify(c) })
|
|
35
|
+
when Piecewise then Piecewises.simplify(expr)
|
|
36
|
+
else expr
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Rational with denominator 1 becomes Integer, a Complex with zero
|
|
41
|
+
# imaginary part becomes real; everything else is kept.
|
|
42
|
+
def normalize_number(value)
|
|
43
|
+
case value
|
|
44
|
+
when Rational then value.denominator == 1 ? value.numerator : value
|
|
45
|
+
when Complex
|
|
46
|
+
re = normalize_number(value.real)
|
|
47
|
+
im = normalize_number(value.imaginary)
|
|
48
|
+
im.is_a?(Integer) && im.zero? ? re : Complex(re, im)
|
|
49
|
+
else value
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Sign test that tolerates Complex (never "negative").
|
|
54
|
+
def negative?(value)
|
|
55
|
+
value.is_a?(Numeric) && value.real? && value.negative?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def imaginary_unit?(base) = base.is_a?(Num) && base.value.is_a?(Complex)
|
|
59
|
+
|
|
60
|
+
# For choosing + or - in a sum: -i*x is written with a minus.
|
|
61
|
+
def sign_negative?(value)
|
|
62
|
+
return value.imaginary.negative? if value.is_a?(Complex) && value.real.zero? && value.imaginary.real?
|
|
63
|
+
negative?(value)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# ---- sums -------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
# => [constant, { factors_hash => coefficient }]
|
|
69
|
+
# Iterative so that long left-leaning sums don't exhaust the stack.
|
|
70
|
+
# With simplify: true the non-sum leaves are simplified on the way (a
|
|
71
|
+
# single pass over the whole sum, instead of once per nesting level).
|
|
72
|
+
def termize(expr, sign = 1, constant = 0, terms = {}, simplify: false)
|
|
73
|
+
stack = [[expr, sign, !simplify]]
|
|
74
|
+
until stack.empty?
|
|
75
|
+
e, s, done = stack.pop
|
|
76
|
+
case e
|
|
77
|
+
when Add then stack.push([e.right, s, done], [e.left, s, done])
|
|
78
|
+
when Sub then stack.push([e.right, -s, done], [e.left, s, done])
|
|
79
|
+
when Neg then stack.push([e.arg, -s, done])
|
|
80
|
+
when Num then constant += s * e.value
|
|
81
|
+
else
|
|
82
|
+
unless done
|
|
83
|
+
e = simplify(e)
|
|
84
|
+
if e.is_a?(Add) || e.is_a?(Sub) || e.is_a?(Neg) || e.is_a?(Num)
|
|
85
|
+
stack.push([e, s, true])
|
|
86
|
+
next
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
coeff, factors = factorize(e)
|
|
90
|
+
if factors.empty?
|
|
91
|
+
constant += s * coeff
|
|
92
|
+
else
|
|
93
|
+
terms[factors] = (terms[factors] || 0) + s * coeff
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
[constant, terms]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def rebuild_sum(constant, terms)
|
|
101
|
+
# oo - oo is not 0. Infinity is an atom like any other in the term
|
|
102
|
+
# table, so a cancelling infinite term - or one already undefined -
|
|
103
|
+
# has to be caught here, before the table is rebuilt into a tree.
|
|
104
|
+
return UNDEFINED if terms.any? { |factors, coeff| undefined_term?(factors, coeff) }
|
|
105
|
+
# A finite quantity added to an infinite one changes nothing: oo - 2
|
|
106
|
+
# is oo. Two different infinite terms are left alone (oo*x - oo says
|
|
107
|
+
# nothing until x does).
|
|
108
|
+
infinite = terms.reject { |_, c| c.zero? }.select { |factors, _| factors.key?(OO) }
|
|
109
|
+
return rebuild_product(infinite.values.first, infinite.keys.first) if infinite.size == 1
|
|
110
|
+
parts = terms.reject { |_, c| c.zero? }.map do |factors, coeff|
|
|
111
|
+
negative = sign_negative?(coeff)
|
|
112
|
+
[rebuild_product(negative ? -coeff : coeff, factors), negative, term_key(factors)]
|
|
113
|
+
end
|
|
114
|
+
parts.sort_by! { |_, _, key| key }
|
|
115
|
+
unless constant.zero?
|
|
116
|
+
entry = [Num.new(sign_negative?(constant) ? -constant : constant), sign_negative?(constant)]
|
|
117
|
+
index = parts.index { |_, _, key| key.first >= 0 } || parts.size
|
|
118
|
+
parts.insert(index, entry)
|
|
119
|
+
end
|
|
120
|
+
return Num.new(0) if parts.empty?
|
|
121
|
+
sum_tree(parts.map { |e, neg| [e, neg] })
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Longest left-leaning chain we build; longer sums become balanced trees
|
|
125
|
+
# of such chains so that tree depth stays logarithmic.
|
|
126
|
+
CHAIN = 32
|
|
127
|
+
|
|
128
|
+
# parts: [[expr, negative?], ...]
|
|
129
|
+
def sum_tree(parts)
|
|
130
|
+
if parts.size <= CHAIN
|
|
131
|
+
(first, negative), *rest = parts
|
|
132
|
+
acc = negative ? negate(first) : first
|
|
133
|
+
return rest.reduce(acc) { |sum, (e, neg)| neg ? Sub.new(sum, e) : Add.new(sum, e) }
|
|
134
|
+
end
|
|
135
|
+
left, right = parts.each_slice((parts.size + 1) / 2).to_a
|
|
136
|
+
if right.first[1]
|
|
137
|
+
Sub.new(sum_tree(left), sum_tree(right.map { |e, neg| [e, !neg] }))
|
|
138
|
+
else
|
|
139
|
+
Add.new(sum_tree(left), sum_tree(right))
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# ---- products ---------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
# => [coefficient, { base => exponent }]
|
|
146
|
+
# Exponents are Ruby numbers when numeric, otherwise Expressions.
|
|
147
|
+
# Iterative over Mul/Div chains for the same reason as termize.
|
|
148
|
+
def factorize(expr, power = 1, coeff = 1, factors = {}, simplify: false)
|
|
149
|
+
stack = [[expr, power, !simplify]]
|
|
150
|
+
until stack.empty?
|
|
151
|
+
e, pw, done = stack.pop
|
|
152
|
+
case e
|
|
153
|
+
when Num
|
|
154
|
+
coeff *= pow_number(e.value, pw)
|
|
155
|
+
when Neg
|
|
156
|
+
coeff *= pow_number(-1, pw)
|
|
157
|
+
stack.push([e.arg, pw, done])
|
|
158
|
+
when Mul
|
|
159
|
+
stack.push([e.right, pw, done], [e.left, pw, done])
|
|
160
|
+
when Div
|
|
161
|
+
raise ZeroDivisionError, "division by zero" if e.right.is_a?(Num) && e.right.zero?
|
|
162
|
+
stack.push([e.right, -pw, done], [e.left, pw, done])
|
|
163
|
+
when Pow
|
|
164
|
+
e = Pow.new(simplify(e.base), simplify(e.exponent)) unless done
|
|
165
|
+
exp = exponent_value(e.exponent)
|
|
166
|
+
if e.base.is_a?(Num) && e.base.value == 1
|
|
167
|
+
next # 1**anything
|
|
168
|
+
elsif e.base.is_a?(Num) && exp.is_a?(Expression) && (e.base.value.is_a?(Integer) || e.base.value.is_a?(Rational))
|
|
169
|
+
# 2**(k + 1) => 2 * 2**k so that both spellings share one canonical form
|
|
170
|
+
constant, rest = termize(exp)
|
|
171
|
+
if constant.is_a?(Integer) && !constant.zero?
|
|
172
|
+
coeff *= pow_number(e.base.value, constant * pw) if pw.is_a?(Integer)
|
|
173
|
+
exp = exponent_value(rebuild_sum(0, rest))
|
|
174
|
+
end
|
|
175
|
+
add_factor(factors, e.base, multiply_exponents(exp, pw))
|
|
176
|
+
elsif e.base.is_a?(Fn) && e.base.name == :exp && e.base.args.size == 1 &&
|
|
177
|
+
exp_power_mergeable?(e.base.args.first, multiply_exponents(exp, pw))
|
|
178
|
+
# e**x, exp(u)**v => exp(u*v)
|
|
179
|
+
add_factor(factors, exp_base, multiply_exponents(multiply_exponents(exponent_value(e.base.args.first), exp), pw))
|
|
180
|
+
elsif e.base.is_a?(Fn) && e.base.name == :exp && e.base.args.size == 1
|
|
181
|
+
# The branch would move: sqrt(exp(2*pi*i)) is 1 and exp(pi*i) is
|
|
182
|
+
# -1. The power stays as it stands (see exp_power_mergeable?).
|
|
183
|
+
add_factor(factors, power_node(e.base, exp), pw)
|
|
184
|
+
elsif exp == 0 && infinity?(e.base)
|
|
185
|
+
next # oo**0 is 1, as t**0 is for every t; oo/oo still cancels to undefined
|
|
186
|
+
elsif exp.is_a?(Integer)
|
|
187
|
+
stack.push([e.base, pw * exp, true])
|
|
188
|
+
elsif exp.is_a?(Rational) && e.base.is_a?(Pow) && e.base.exponent.is_a?(Num) &&
|
|
189
|
+
e.base.exponent.value.is_a?(Integer) && RCAS.nonnegative?(e.base.base)
|
|
190
|
+
# (x**2)**(1/2) is x when x cannot be negative
|
|
191
|
+
stack.push([e.base.base, multiply_exponents(e.base.exponent.value, multiply_exponents(exp, pw)), true])
|
|
192
|
+
elsif exp.is_a?(Rational) && e.base.is_a?(Pow) && e.base.exponent.is_a?(Num) &&
|
|
193
|
+
e.base.exponent.value.is_a?(Rational) && RCAS.nonnegative?(e.base.base)
|
|
194
|
+
# (2**(1/2))**(1/2) is 2**(1/4): (b**r)**s = b**(r*s) for real r, s
|
|
195
|
+
# when b cannot be negative - b**r is then a real power of a
|
|
196
|
+
# positive number and no branch moves
|
|
197
|
+
stack.push([Pow.new(e.base.base, Num.new(e.base.exponent.value * exp)), pw, true])
|
|
198
|
+
elsif exp.is_a?(Rational) && exp.denominator > 1 && (split = root_of_product(e.base, exp))
|
|
199
|
+
# (a**2*x**2)**(1/2) is a*(x**2)**(1/2) for a nonnegative a: a
|
|
200
|
+
# factor that cannot be negative comes out of the root, and the
|
|
201
|
+
# rest stays inside, where its sign is still nobody's business.
|
|
202
|
+
outside, inside = split
|
|
203
|
+
outside.each { |base, power| stack.push([base, multiply_exponents(power, pw), true]) }
|
|
204
|
+
stack.push([Pow.new(inside, Num.new(exp)), pw, true])
|
|
205
|
+
elsif exp.is_a?(Numeric) && e.base.is_a?(Num) && (root = exact_power(e.base.value, exp))
|
|
206
|
+
coeff *= pow_number(root, pw)
|
|
207
|
+
elsif e.base.is_a?(Num) && exp.is_a?(Numeric) && (exp.is_a?(Float) || e.base.value.is_a?(Float))
|
|
208
|
+
coeff *= pow_number(e.base.value**exp, pw)
|
|
209
|
+
elsif e.base.is_a?(Num) && e.base.value.is_a?(Integer) && e.base.value.negative? && exp.is_a?(Rational) && exp.denominator == 2
|
|
210
|
+
# sqrt(-n) = i*sqrt(n)
|
|
211
|
+
coeff *= Complex(0, 1)**(exp.numerator * pw) if pw.is_a?(Integer)
|
|
212
|
+
return factorize(expr, power, coeff, factors) unless pw.is_a?(Integer) # give up merging; rare
|
|
213
|
+
stack.push([Pow.new(Num.new(-e.base.value), Num.new(exp)), pw, true])
|
|
214
|
+
elsif e.base.is_a?(Num) && exp.is_a?(Rational) && (parts = extract_root(e.base.value, exp.denominator))
|
|
215
|
+
root, rest = parts
|
|
216
|
+
coeff *= pow_number(root, exp.numerator * pw)
|
|
217
|
+
add_factor(factors, Num.new(rest), multiply_exponents(exp, pw)) unless rest == 1
|
|
218
|
+
elsif exp.is_a?(Rational) && (e.base.is_a?(Mul) || e.base.is_a?(Div)) && (split = positive_coefficient_split(e.base))
|
|
219
|
+
# (4*a)**(1/2) => 4**(1/2) * a**(1/2): a positive number may always leave the root
|
|
220
|
+
c, rest = split
|
|
221
|
+
stack.push([Pow.new(Num.new(c), e.exponent), pw, true], [Pow.new(rest, e.exponent), pw, true])
|
|
222
|
+
elsif exp.is_a?(Rational) && (e.base.is_a?(Add) || e.base.is_a?(Sub)) && (split = sum_content_split(e.base, exp.denominator))
|
|
223
|
+
# (4 - 4*y**2)**(1/2) => 2*(1 - y**2)**(1/2)
|
|
224
|
+
c, rest = split
|
|
225
|
+
stack.push([Pow.new(Num.new(c), e.exponent), pw, true], [Pow.new(rest, e.exponent), pw, true])
|
|
226
|
+
else
|
|
227
|
+
add_factor(factors, e.base, multiply_exponents(exp, pw))
|
|
228
|
+
end
|
|
229
|
+
when Fn
|
|
230
|
+
if e.name == :exp && e.args.size == 1
|
|
231
|
+
if exp_power_mergeable?(e.args.first, pw)
|
|
232
|
+
add_factor(factors, exp_base, multiply_exponents(exponent_value(e.args.first), pw))
|
|
233
|
+
else
|
|
234
|
+
add_factor(factors, e, pw)
|
|
235
|
+
end
|
|
236
|
+
else
|
|
237
|
+
unless done
|
|
238
|
+
e = simplify(e)
|
|
239
|
+
if e.is_a?(Num) || e.is_a?(Neg) || e.is_a?(Mul) || e.is_a?(Div) || e.is_a?(Pow) || (e.is_a?(Fn) && e.name == :exp)
|
|
240
|
+
stack.push([e, pw, true])
|
|
241
|
+
next
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
add_factor(factors, e, pw)
|
|
245
|
+
end
|
|
246
|
+
else
|
|
247
|
+
unless done
|
|
248
|
+
e = simplify(e)
|
|
249
|
+
if e.is_a?(Num) || e.is_a?(Neg) || e.is_a?(Mul) || e.is_a?(Div) || e.is_a?(Pow)
|
|
250
|
+
stack.push([e, pw, true])
|
|
251
|
+
next
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
add_factor(factors, e, pw)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
Combinatorics.merge_factorials(factors) if factors.count { |b, _| b.is_a?(Fn) && b.name == :factorial } > 1
|
|
258
|
+
[coeff, factors]
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# sign(u)*|u|**e is u**e for an odd e, since |u| = u*sign(u) and
|
|
262
|
+
# sign(u)**2 = 1 wherever the product has a value: sign(x)/abs(x) is
|
|
263
|
+
# 1/x, which an antiderivative valid on both sides of a point writes.
|
|
264
|
+
def sign_times_abs(factors)
|
|
265
|
+
factors.each do |base, exp|
|
|
266
|
+
next unless base.is_a?(Fn) && base.name == :sign && exp == 1
|
|
267
|
+
modulus = Fn.new(:abs, base.args)
|
|
268
|
+
power = factors[modulus]
|
|
269
|
+
next unless power.is_a?(Integer) && power.odd?
|
|
270
|
+
rest = factors.reject { |b, _| b == base || b == modulus }
|
|
271
|
+
add_factor(rest, base.args.first, power)
|
|
272
|
+
return rest
|
|
273
|
+
end
|
|
274
|
+
factors
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def rebuild_product(coeff, factors)
|
|
278
|
+
coeff = normalize_number(coeff)
|
|
279
|
+
# 0*oo and oo/oo have no value either (the second reaches here as an
|
|
280
|
+
# infinite factor whose exponents have cancelled to zero).
|
|
281
|
+
return UNDEFINED if undefined_term?(factors, coeff)
|
|
282
|
+
return Num.new(0) if coeff.zero?
|
|
283
|
+
coeff, factors = absorb_infinity(coeff, factors) if factors.key?(OO)
|
|
284
|
+
return Num.new(0) if factors.nil?
|
|
285
|
+
return Num.new(coeff) if factors.all? { |_, exp| exp.is_a?(Numeric) && exp.zero? }
|
|
286
|
+
factors = sign_times_abs(factors) if factors.keys.any? { |b| b.is_a?(Fn) && b.name == :sign }
|
|
287
|
+
# (-1)**(k/2) is i**k: sqrt(-1/4) came out as (-1)**(1/2)/2 while
|
|
288
|
+
# sqrt(-4) was 2*i (third review, 3.3)
|
|
289
|
+
minus_one = Num.new(-1)
|
|
290
|
+
half = factors[minus_one]
|
|
291
|
+
if half.is_a?(Rational) && half.denominator == 2
|
|
292
|
+
factors = factors.reject { |base, _| base == minus_one }
|
|
293
|
+
coeff = normalize_number(coeff * Complex(0, 1)**(2 * half).to_i)
|
|
294
|
+
end
|
|
295
|
+
if coeff.is_a?(Complex) && coeff.real.zero?
|
|
296
|
+
# The unit joins the powers of i already there: i*i**(1/2) is
|
|
297
|
+
# i**(3/2), and writing i => 1 over the 1/2 would lose the root.
|
|
298
|
+
unit = Num.new(Complex(0, 1))
|
|
299
|
+
exp = (factors[unit] || 0) + 1
|
|
300
|
+
coeff = coeff.imaginary
|
|
301
|
+
factors = factors.reject { |base, _| base == unit }
|
|
302
|
+
if exp.is_a?(Integer) || (exp.is_a?(Rational) && exp.denominator == 1)
|
|
303
|
+
coeff = normalize_number(coeff * Complex(0, 1)**exp.to_i)
|
|
304
|
+
if coeff.is_a?(Complex) && coeff.real.zero?
|
|
305
|
+
factors[unit] = 1
|
|
306
|
+
coeff = coeff.imaginary
|
|
307
|
+
end
|
|
308
|
+
else
|
|
309
|
+
factors[unit] = exp
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# 2**(-1/2) => 2**(1/2)/2, 2**(3/2) => 2*2**(1/2): a positive integer base keeps a
|
|
314
|
+
# fractional exponent in (0, 1); the integer part moves into the coefficient.
|
|
315
|
+
if factors.any? { |base, exp| base.is_a?(Num) && base.value.is_a?(Rational) && base.value.positive? && exp.is_a?(Rational) }
|
|
316
|
+
split = {}
|
|
317
|
+
factors.each do |base, exp|
|
|
318
|
+
if base.is_a?(Num) && base.value.is_a?(Rational) && base.value.positive? && exp.is_a?(Rational)
|
|
319
|
+
add_factor(split, Num.new(base.value.numerator), exp) unless base.value.numerator == 1
|
|
320
|
+
add_factor(split, Num.new(base.value.denominator), -exp)
|
|
321
|
+
else
|
|
322
|
+
add_factor(split, base, exp)
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
factors = split
|
|
326
|
+
end
|
|
327
|
+
factors = factors.to_h do |base, exp|
|
|
328
|
+
next [base, exp] unless base.is_a?(Num) && base.value.is_a?(Integer) && base.value.positive? && exp.is_a?(Rational) && exp.floor != 0
|
|
329
|
+
m = exp.floor
|
|
330
|
+
coeff = normalize_number(coeff * Rational(base.value)**m)
|
|
331
|
+
[base, exp - m]
|
|
332
|
+
end
|
|
333
|
+
numerator, denominator = [], []
|
|
334
|
+
factors.sort_by { |base, _| factor_key(base) }.each do |base, exp|
|
|
335
|
+
next if exp.is_a?(Numeric) && exp.zero?
|
|
336
|
+
if exp.is_a?(Numeric) && negative?(exp)
|
|
337
|
+
node = power_node(base, -exp)
|
|
338
|
+
node.is_a?(Num) && !imaginary_unit?(base) ? coeff = normalize_number(coeff.quo(node.value)) : denominator << node
|
|
339
|
+
else
|
|
340
|
+
node = power_node(base, exp)
|
|
341
|
+
node.is_a?(Num) && !imaginary_unit?(base) ? coeff = normalize_number(coeff * node.value) : numerator << node
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
return Num.new(0) if coeff.zero?
|
|
345
|
+
# every factor folded into the number: (2**(1/2)/2)**2 is the number
|
|
346
|
+
# 1/2, not Div(1, 2)
|
|
347
|
+
return Num.new(coeff) if numerator.empty? && denominator.empty?
|
|
348
|
+
|
|
349
|
+
if numerator.size == 1 && denominator.empty? && coeff != 1 &&
|
|
350
|
+
(numerator.first.is_a?(Add) || numerator.first.is_a?(Sub))
|
|
351
|
+
constant, terms = termize(numerator.first)
|
|
352
|
+
if (constant.zero? ? 0 : 1) + terms.size >= 2
|
|
353
|
+
return rebuild_sum(constant * coeff, terms.transform_values { |c| c * coeff })
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
if coeff.is_a?(Rational)
|
|
358
|
+
denominator.unshift(Num.new(coeff.denominator))
|
|
359
|
+
coeff = coeff.numerator
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
negative = negative?(coeff)
|
|
363
|
+
coeff = negative ? -coeff : coeff
|
|
364
|
+
numerator.unshift(Num.new(coeff)) unless coeff == 1 && !numerator.empty?
|
|
365
|
+
|
|
366
|
+
result = product_node(numerator)
|
|
367
|
+
result = Div.new(result, product_node(denominator)) unless denominator.empty?
|
|
368
|
+
negative ? negate(result) : result
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# [what every term of a sum has in common, what is left], or nil:
|
|
372
|
+
# -2*log(x) + x*log(x) is log(x) times (x - 2). `factor` refuses this
|
|
373
|
+
# one, because log(x) is not a polynomial variable, but the term table
|
|
374
|
+
# has the factor in plain sight. Both `solve` and `discuss` look for a
|
|
375
|
+
# product this way before giving up on an equation.
|
|
376
|
+
def common_factor(expr)
|
|
377
|
+
constant, terms = Expand.table(expr)
|
|
378
|
+
return nil unless constant.zero?
|
|
379
|
+
return nil if terms.size < 2
|
|
380
|
+
shared = terms.keys.first.select { |base, exponent| terms.keys.all? { |factors| factors[base] == exponent } }
|
|
381
|
+
return nil if shared.empty?
|
|
382
|
+
common = shared.reduce(Num.new(1)) { |product, (base, exponent)| (product * power_node(base, exponent)).simplify }
|
|
383
|
+
[common, Expand.expand(expr / common)]
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# ---- helpers ----------------------------------------------------------
|
|
387
|
+
|
|
388
|
+
# A factor map that must not be rebuilt into a value: an infinity that
|
|
389
|
+
# cancels (against a zero coefficient in a product, against another term
|
|
390
|
+
# in a sum, or against itself in the exponents), or an undefined factor.
|
|
391
|
+
def undefined_term?(factors, coeff)
|
|
392
|
+
return true if factors.key?(UNDEFINED)
|
|
393
|
+
return false unless factors.key?(OO)
|
|
394
|
+
exp = factors[OO]
|
|
395
|
+
coeff.zero? || (exp.is_a?(Numeric) && exp.zero?)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# oo or -oo as written, before the tables merge it with anything.
|
|
399
|
+
def infinity?(e) = e == OO || (e.is_a?(Neg) && infinity?(e.arg))
|
|
400
|
+
|
|
401
|
+
# Infinity swallows what multiplies it: 2*oo and oo/2 are oo, oo**2 is
|
|
402
|
+
# oo, and a number over oo is 0 (nil for the factors says so). Only the
|
|
403
|
+
# sign of a numeric coefficient survives; a symbolic one is kept, since
|
|
404
|
+
# x*oo is not oo. The cancellations are caught before this.
|
|
405
|
+
def absorb_infinity(coeff, factors)
|
|
406
|
+
exp = factors[OO]
|
|
407
|
+
return [coeff, factors] unless exp.is_a?(Numeric) && exp.real? && coeff.is_a?(Numeric) && coeff.real?
|
|
408
|
+
return [coeff, nil] if exp.negative? && factors.size == 1
|
|
409
|
+
return [coeff, factors] if exp.negative?
|
|
410
|
+
[coeff.negative? ? -1 : 1, factors.merge(OO => 1)]
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# exp(u)**v is exp(u*v) only where the choice of branch cannot change.
|
|
414
|
+
# For an integer v the power is a repeated product, and for a real u the
|
|
415
|
+
# base exp(u) is a positive real whose principal power is the real one;
|
|
416
|
+
# in between the rule changes the value. sqrt(exp(x)) at x = 2*pi*i is
|
|
417
|
+
# sqrt(1) = 1, while exp(x/2) there is exp(pi*i) = -1 (22 Sept 2026,
|
|
418
|
+
# from a review). An undeclared indeterminate is not known to be real,
|
|
419
|
+
# so exp(x)**(1/2) keeps its shape until `assume(x: RR)` says otherwise.
|
|
420
|
+
def exp_power_mergeable?(u, v)
|
|
421
|
+
return true if v.is_a?(Integer)
|
|
422
|
+
ComplexParts.real_valued?(Expression.lift(u))
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def add_factor(factors, base, exp)
|
|
426
|
+
factors[base] = add_exponents(factors[base] || 0, exp)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def exponent_value(exp) = exp.is_a?(Num) ? exp.value : exp
|
|
430
|
+
|
|
431
|
+
def add_exponents(a, b)
|
|
432
|
+
return normalize_number(a + b) if a.is_a?(Numeric) && b.is_a?(Numeric)
|
|
433
|
+
exponent_value(simplify(Expression.lift(a) + Expression.lift(b)))
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def multiply_exponents(a, b)
|
|
437
|
+
return normalize_number(a * b) if a.is_a?(Numeric) && b.is_a?(Numeric)
|
|
438
|
+
exponent_value(simplify(Expression.lift(a) * Expression.lift(b)))
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Exact integer power, going through Rational for negative exponents.
|
|
442
|
+
# The factors of a product that may leave a root: nonnegative ones whose
|
|
443
|
+
# exponent the root divides, since sqrt(c**2*w) is c*sqrt(w) for a real
|
|
444
|
+
# c >= 0 and any w. Everything else stays inside. nil when nothing comes
|
|
445
|
+
# out, which is what stops the rule from firing on its own result.
|
|
446
|
+
def root_of_product(base, exp)
|
|
447
|
+
# sqrt(pi**2) is pi and sqrt(1/pi**2) is 1/pi, as sqrt(4*pi**2) is 2*pi
|
|
448
|
+
return nil unless base.is_a?(Mul) || base.is_a?(Div) || (base.is_a?(Pow) && base.exponent.is_a?(Num) && base.exponent.value.is_a?(Integer))
|
|
449
|
+
coeff, factors = factorize(base)
|
|
450
|
+
outside = {}
|
|
451
|
+
inside = {}
|
|
452
|
+
factors.each do |factor, power|
|
|
453
|
+
if power.is_a?(Integer) && (power * exp).denominator == 1 && !power.zero? && RCAS.nonnegative?(factor)
|
|
454
|
+
outside[factor] = power * exp
|
|
455
|
+
else
|
|
456
|
+
inside[factor] = power
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
return nil if outside.empty?
|
|
460
|
+
[outside, rebuild_product(coeff, inside)]
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def pow_number(value, power)
|
|
464
|
+
return value if power == 1
|
|
465
|
+
return normalize_number(value**power) if value.is_a?(Complex)
|
|
466
|
+
if power.is_a?(Integer) && power.negative?
|
|
467
|
+
raise ZeroDivisionError, "division by zero" if value.zero?
|
|
468
|
+
return normalize_number(Rational(value)**power) if value.is_a?(Integer) || value.is_a?(Rational)
|
|
469
|
+
end
|
|
470
|
+
normalize_number(value**power)
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# 4 ** (1/2) => 2, 8 ** (2/3) => 4, 2 ** (1/2) => nil (stays symbolic).
|
|
474
|
+
def exact_power(value, exp)
|
|
475
|
+
return nil unless exp.is_a?(Rational) && value.is_a?(Integer) && value >= 0
|
|
476
|
+
root = integer_root(value, exp.denominator)
|
|
477
|
+
return nil unless root**exp.denominator == value
|
|
478
|
+
pow_number(root, exp.numerator)
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# The integer n-th root of a non-negative integer, by bisection on the
|
|
482
|
+
# bit length. value**(1.0/n) overflows to Infinity above 10**308, so
|
|
483
|
+
# root(10**400, 3) used to raise FloatDomainError.
|
|
484
|
+
def integer_root(value, n)
|
|
485
|
+
return Integer.sqrt(value) if n == 2
|
|
486
|
+
return value if value < 2
|
|
487
|
+
lo = 1
|
|
488
|
+
hi = 1 << ((value.bit_length + n - 1) / n) # 2**ceil(bits/n) is past the root
|
|
489
|
+
while lo < hi
|
|
490
|
+
mid = (lo + hi + 1) / 2
|
|
491
|
+
mid**n <= value ? lo = mid : hi = mid - 1
|
|
492
|
+
end
|
|
493
|
+
lo
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
def power_node(base, exp)
|
|
497
|
+
return Functions.fold(Fn.new(:exp, [Expression.lift(exp)])) if base == exp_base
|
|
498
|
+
return base if exp == 1
|
|
499
|
+
Pow.new(base, Expression.lift(exp))
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
# 32 = 4**2 * 2 => [4, 2] for q = 2; nil when nothing can be extracted.
|
|
503
|
+
def extract_root(value, q)
|
|
504
|
+
if value.is_a?(Rational)
|
|
505
|
+
num = extract_root(value.numerator, q) || [1, value.numerator]
|
|
506
|
+
den = extract_root(value.denominator, q) || [1, value.denominator]
|
|
507
|
+
return nil if num.first == 1 && den.first == 1
|
|
508
|
+
return [Rational(num.first, den.first), normalize_number(Rational(num.last, den.last))]
|
|
509
|
+
end
|
|
510
|
+
return nil unless value.is_a?(Integer) && value > 1
|
|
511
|
+
division = NumberTheory.prime_division(value, hard: false) or return nil
|
|
512
|
+
root = 1
|
|
513
|
+
rest = 1
|
|
514
|
+
division.each do |prime, e|
|
|
515
|
+
root *= prime**(e / q)
|
|
516
|
+
rest *= prime**(e % q)
|
|
517
|
+
end
|
|
518
|
+
root == 1 ? nil : [root, rest]
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# [c, rest] for a product c*rest with a positive rational c != 1, else nil.
|
|
522
|
+
def positive_coefficient_split(expr)
|
|
523
|
+
c, factors = factorize(expr)
|
|
524
|
+
return nil unless (c.is_a?(Integer) || c.is_a?(Rational)) && !c.zero? && c.abs != 1 && !factors.empty?
|
|
525
|
+
[c.abs, rebuild_product(c.negative? ? -1 : 1, factors)] # sqrt(-4*a) = 2*sqrt(-a)
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
# [content, sum / content] when the rational content of a sum with
|
|
529
|
+
# rational coefficients has a q-th root to extract, else nil.
|
|
530
|
+
def sum_content_split(expr, q)
|
|
531
|
+
constant, terms = termize(expr)
|
|
532
|
+
values = terms.values + (constant.zero? ? [] : [constant])
|
|
533
|
+
return nil unless values.all? { |v| v.is_a?(Integer) || v.is_a?(Rational) }
|
|
534
|
+
content = values.map(&:abs).reduce { |g, v| Polynomial.rational_gcd(g, v) }
|
|
535
|
+
return nil if content == 1 || extract_root(content, q).nil?
|
|
536
|
+
rest = rebuild_sum(normalize_number(Rational(constant) / content), terms.transform_values { |c| normalize_number(Rational(c) / content) })
|
|
537
|
+
[content, rest]
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
# A sum whose terms are all negative, like -1 - 2*x.
|
|
541
|
+
def negative_sum?(expr)
|
|
542
|
+
return false unless expr.is_a?(Add) || expr.is_a?(Sub)
|
|
543
|
+
constant, terms = termize(expr)
|
|
544
|
+
!negative?(-constant) && terms.values.all? { |c| negative?(c) } && !(constant.zero? && terms.empty?)
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def product_node(list)
|
|
548
|
+
return Num.new(1) if list.empty?
|
|
549
|
+
return list.reduce { |acc, f| Mul.new(acc, f) } if list.size <= CHAIN
|
|
550
|
+
left, right = list.each_slice((list.size + 1) / 2).to_a
|
|
551
|
+
Mul.new(product_node(left), product_node(right))
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
# Negate a product by flipping the sign of its leading numeric factor when
|
|
555
|
+
# there is one, so "-2*x" rather than "-(2*x)".
|
|
556
|
+
def negate(expr)
|
|
557
|
+
case expr
|
|
558
|
+
when Num then Num.new(-expr.value)
|
|
559
|
+
when Div then Div.new(negate(expr.left), expr.right)
|
|
560
|
+
when Mul
|
|
561
|
+
leading = negate(expr.left)
|
|
562
|
+
leading.is_a?(Neg) ? Neg.new(expr) : Mul.new(leading, expr.right)
|
|
563
|
+
else Neg.new(expr)
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
# Ordering of terms in a sum: ascending total degree ("1 + 2*x + x**2"),
|
|
568
|
+
# then graded lexicographic on variables ("x**3 + 3*x**2*y + ..."), then
|
|
569
|
+
# by text as a tie-break.
|
|
570
|
+
def sort_key(expr)
|
|
571
|
+
[degree(expr), exponent_vector(expr), expr.to_s]
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
# The same key for a term of the table, read off its factor map instead
|
|
575
|
+
# of building rebuild_product(1, factors) to ask it: that second build
|
|
576
|
+
# was a quarter of the time of a 20 000-term simplify. A numeric base
|
|
577
|
+
# keeps the long way round, because rebuilding moves the integer part of
|
|
578
|
+
# its exponent into the coefficient and the map here has not.
|
|
579
|
+
def term_key(factors)
|
|
580
|
+
unless factors.keys.all? { |base| keyable?(base) }
|
|
581
|
+
node = rebuild_product(1, factors)
|
|
582
|
+
return [degree(node), exponent_vector(node), LazyText.new { node.to_s }]
|
|
583
|
+
end
|
|
584
|
+
[factor_degree(factors), factor_exponents(factors), LazyText.new { rebuild_product(1, factors).to_s }]
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
# A base the rebuild hands back unchanged, so that reading the map is
|
|
588
|
+
# the same as re-factorizing the node. A number moves its exponent's
|
|
589
|
+
# integer part into the coefficient, and a power or a quotient is
|
|
590
|
+
# flattened into the map, so those take the long way round.
|
|
591
|
+
def keyable?(base)
|
|
592
|
+
case base
|
|
593
|
+
when Fn then base == exp_base || base.name != :exp # exp(u) is stored as EXP**u
|
|
594
|
+
when Var, Const, Add, Sub then true
|
|
595
|
+
else false
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def factor_degree(factors)
|
|
600
|
+
factors.sum do |base, exp|
|
|
601
|
+
next 0 if exp.is_a?(Numeric) && exp.zero?
|
|
602
|
+
next degree(base) * exp if exp.is_a?(Integer)
|
|
603
|
+
exp.is_a?(Numeric) && negative?(exp) ? -1 : 1
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def factor_exponents(factors)
|
|
608
|
+
factors.filter_map do |base, exp|
|
|
609
|
+
next nil if exp.is_a?(Numeric) && exp.zero?
|
|
610
|
+
[base.to_s, exp.is_a?(Numeric) && exp.real? ? -exp : 0]
|
|
611
|
+
end.sort
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
def exponent_vector(expr)
|
|
615
|
+
_, factors = factorize(expr)
|
|
616
|
+
factors.map { |base, exp| [base.to_s, exp.is_a?(Numeric) && exp.real? ? -exp : 0] }.sort
|
|
617
|
+
rescue ZeroDivisionError
|
|
618
|
+
[]
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
# Ordering of factors in a product: variables first, then functions, then
|
|
622
|
+
# parenthesized sums.
|
|
623
|
+
def factor_key(base)
|
|
624
|
+
rank =
|
|
625
|
+
case base
|
|
626
|
+
when Num then base.value.is_a?(Complex) ? -1 : 0
|
|
627
|
+
when Const then 1
|
|
628
|
+
when Var then 2
|
|
629
|
+
when Fn then 3
|
|
630
|
+
else 4
|
|
631
|
+
end
|
|
632
|
+
[rank, base.to_s]
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
def degree(expr)
|
|
636
|
+
case expr
|
|
637
|
+
when Num, Const then 0
|
|
638
|
+
when Var then 1
|
|
639
|
+
when Neg then degree(expr.arg)
|
|
640
|
+
when Mul then degree(expr.left) + degree(expr.right)
|
|
641
|
+
when Div then degree(expr.left) - degree(expr.right)
|
|
642
|
+
when Pow then expr.exponent.is_a?(Num) && expr.exponent.value.is_a?(Integer) ? degree(expr.base) * expr.exponent.value : 1
|
|
643
|
+
when Add, Sub then [degree(expr.left), degree(expr.right)].max
|
|
644
|
+
when Fn then expr.name == :O ? 10**9 : 1
|
|
645
|
+
else 1
|
|
646
|
+
end
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
end
|