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.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/CITATION.cff +17 -0
  3. data/DESIGN.md +783 -0
  4. data/LICENSE +21 -0
  5. data/MANUAL.md +6265 -0
  6. data/README.md +267 -0
  7. data/bin/rcas +9 -0
  8. data/bin/rcas-app +9 -0
  9. data/bin/rcas-chat +9 -0
  10. data/lib/rcas/algebraic.rb +481 -0
  11. data/lib/rcas/analysis.rb +966 -0
  12. data/lib/rcas/app/launcher.rb +203 -0
  13. data/lib/rcas/app/public/app.css +402 -0
  14. data/lib/rcas/app/public/app.js +449 -0
  15. data/lib/rcas/app/public/index.html +46 -0
  16. data/lib/rcas/app/server.rb +220 -0
  17. data/lib/rcas/app/window.rb +94 -0
  18. data/lib/rcas/app/worksheet.rb +290 -0
  19. data/lib/rcas/app.rb +168 -0
  20. data/lib/rcas/background.rb +758 -0
  21. data/lib/rcas/chat/assistant.rb +199 -0
  22. data/lib/rcas/chat/picker.rb +164 -0
  23. data/lib/rcas/chat/repl.rb +583 -0
  24. data/lib/rcas/chat/session.rb +137 -0
  25. data/lib/rcas/chat/settings.rb +71 -0
  26. data/lib/rcas/chat/style.rb +30 -0
  27. data/lib/rcas/chat/tool.rb +53 -0
  28. data/lib/rcas/chat/ui.rb +316 -0
  29. data/lib/rcas/chat/usage.rb +62 -0
  30. data/lib/rcas/chat/workspace.rb +132 -0
  31. data/lib/rcas/chat.rb +54 -0
  32. data/lib/rcas/coefficients.rb +170 -0
  33. data/lib/rcas/combinatorics.rb +274 -0
  34. data/lib/rcas/complex_parts.rb +160 -0
  35. data/lib/rcas/constants.rb +129 -0
  36. data/lib/rcas/core_ext.rb +35 -0
  37. data/lib/rcas/decide.rb +501 -0
  38. data/lib/rcas/decompositions.rb +241 -0
  39. data/lib/rcas/differentiate.rb +144 -0
  40. data/lib/rcas/discussion.rb +558 -0
  41. data/lib/rcas/distributions.rb +980 -0
  42. data/lib/rcas/dixon.rb +95 -0
  43. data/lib/rcas/docs.rb +321 -0
  44. data/lib/rcas/domains.rb +728 -0
  45. data/lib/rcas/expand.rb +174 -0
  46. data/lib/rcas/expression.rb +613 -0
  47. data/lib/rcas/factor.rb +605 -0
  48. data/lib/rcas/finite_field.rb +577 -0
  49. data/lib/rcas/fourier.rb +118 -0
  50. data/lib/rcas/fps.rb +678 -0
  51. data/lib/rcas/fraction.rb +126 -0
  52. data/lib/rcas/functions.rb +1136 -0
  53. data/lib/rcas/gcd.rb +112 -0
  54. data/lib/rcas/geometry.rb +266 -0
  55. data/lib/rcas/groebner.rb +162 -0
  56. data/lib/rcas/hold.rb +277 -0
  57. data/lib/rcas/hypothesis.rb +364 -0
  58. data/lib/rcas/inequalities.rb +689 -0
  59. data/lib/rcas/integral_functions.rb +260 -0
  60. data/lib/rcas/integrate.rb +1589 -0
  61. data/lib/rcas/integrate_substitutions.rb +434 -0
  62. data/lib/rcas/interpolate.rb +40 -0
  63. data/lib/rcas/irb.rb +146 -0
  64. data/lib/rcas/laplace.rb +159 -0
  65. data/lib/rcas/latex.rb +556 -0
  66. data/lib/rcas/lattice.rb +172 -0
  67. data/lib/rcas/linear_algebra.rb +117 -0
  68. data/lib/rcas/linear_program.rb +416 -0
  69. data/lib/rcas/lint.rb +79 -0
  70. data/lib/rcas/matrix.rb +531 -0
  71. data/lib/rcas/matrix_multiply.rb +202 -0
  72. data/lib/rcas/multimodular.rb +286 -0
  73. data/lib/rcas/named_polynomials.rb +274 -0
  74. data/lib/rcas/number_theory.rb +443 -0
  75. data/lib/rcas/numerics.rb +825 -0
  76. data/lib/rcas/ode.rb +488 -0
  77. data/lib/rcas/openmath/objects.rb +364 -0
  78. data/lib/rcas/openmath/phrasebook.rb +551 -0
  79. data/lib/rcas/openmath/popcorn.rb +518 -0
  80. data/lib/rcas/openmath/xml.rb +309 -0
  81. data/lib/rcas/openmath.rb +49 -0
  82. data/lib/rcas/petkovsek.rb +165 -0
  83. data/lib/rcas/piecewise.rb +488 -0
  84. data/lib/rcas/plot.rb +763 -0
  85. data/lib/rcas/plot3d.rb +419 -0
  86. data/lib/rcas/poly_matrix.rb +318 -0
  87. data/lib/rcas/poly_recurrence.rb +117 -0
  88. data/lib/rcas/polynomial.rb +466 -0
  89. data/lib/rcas/precision.rb +925 -0
  90. data/lib/rcas/printer.rb +150 -0
  91. data/lib/rcas/product.rb +155 -0
  92. data/lib/rcas/q_difference.rb +296 -0
  93. data/lib/rcas/q_functions.rb +158 -0
  94. data/lib/rcas/q_summation.rb +308 -0
  95. data/lib/rcas/q_zeilberger.rb +199 -0
  96. data/lib/rcas/random.rb +506 -0
  97. data/lib/rcas/rational_function.rb +186 -0
  98. data/lib/rcas/recurrence.rb +323 -0
  99. data/lib/rcas/render.rb +431 -0
  100. data/lib/rcas/results.rb +192 -0
  101. data/lib/rcas/scalar.rb +219 -0
  102. data/lib/rcas/series.rb +726 -0
  103. data/lib/rcas/simplify.rb +649 -0
  104. data/lib/rcas/solve.rb +2002 -0
  105. data/lib/rcas/special.rb +163 -0
  106. data/lib/rcas/statistics.rb +175 -0
  107. data/lib/rcas/steps.rb +835 -0
  108. data/lib/rcas/summation.rb +532 -0
  109. data/lib/rcas/trig.rb +264 -0
  110. data/lib/rcas/van_hoeij.rb +241 -0
  111. data/lib/rcas/vector.rb +175 -0
  112. data/lib/rcas/vector_calculus.rb +411 -0
  113. data/lib/rcas/version.rb +5 -0
  114. data/lib/rcas/zeilberger.rb +358 -0
  115. data/lib/rcas.rb +91 -0
  116. data/package.json +8 -0
  117. metadata +206 -0
@@ -0,0 +1,613 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module RCAS
6
+ # The broad rescues in the mathematical code turn a failure into "not
7
+ # decided here", which is right for the mathematics and wrong for a bug:
8
+ # the third review's fault injection had 31 NoMethodErrors quietly turned
9
+ # into unevaluated answers, and a caller's Timeout::Error (a
10
+ # StandardError) swallowed. Every such rescue calls guard! first. A
11
+ # timeout always goes through; with RCAS_STRICT=1 (the test task sets it)
12
+ # so do NoMethodError and NameError, which are never mathematics.
13
+ def self.strict? = ENV["RCAS_STRICT"] == "1"
14
+
15
+ # What rcas raises when it cannot do something: "cannot" is an answer of
16
+ # its own, never "none". A StandardError, so that a caller's `rescue => e`
17
+ # catches it (it was a NotImplementedError, a ScriptError, until the
18
+ # fourth review asked a second time); and since a broad rescue would now
19
+ # see it, guard! passes it on, so it reaches the caller exactly as the
20
+ # ScriptError did. A rescue that means to take a refusal names it, and
21
+ # says so to guard! with `refused: true`.
22
+ class Unsupported < StandardError; end
23
+
24
+ # A refusal of either kind: Ruby's own NotImplementedError too.
25
+ def self.refusal?(error) = error.is_a?(Unsupported) || error.is_a?(NotImplementedError)
26
+
27
+ def self.guard!(error, refused: false)
28
+ raise error if !refused && error.is_a?(Unsupported)
29
+ raise error if defined?(::Timeout::Error) && error.is_a?(::Timeout::Error)
30
+ # NoMethodError is a NameError; 1 + nil is a TypeError (fourth review)
31
+ raise error if strict? && (error.is_a?(NameError) || error.is_a?(TypeError))
32
+ end
33
+
34
+ # The value of an expression or number as a real Float, or nil when it
35
+ # has none (not constant, complex, undefined; infinite unless finite:
36
+ # false). Six private copies of this read "Float or nil" slightly
37
+ # differently (third review, section 5); they all delegate here now.
38
+ def self.real_float(value, finite: true)
39
+ return nil unless value.is_a?(Numeric) || value.is_a?(Expression) || value.is_a?(Symbol) # a family, a set
40
+ v = value.is_a?(Numeric) ? value : Expression.lift(value).evalf
41
+ v = v.value if v.is_a?(Num)
42
+ return nil unless v.is_a?(Numeric) && v.real?
43
+ f = v.to_f
44
+ return nil if f.nan? || (finite && f.infinite?)
45
+ f
46
+ rescue StandardError, Math::DomainError => rescued
47
+ guard!(rescued) if rescued.is_a?(StandardError)
48
+ nil
49
+ end
50
+
51
+ # Base class of every node in an expression tree.
52
+ #
53
+ # Trees are immutable and built faithfully from the Ruby expression that
54
+ # created them: `(:x + 1) * (1 - :x)` is Mul(Add(x, 1), Sub(1, x)). Nothing
55
+ # is rewritten until you ask for it via #simplify, #expand or #diff.
56
+ class Expression
57
+ include Comparable
58
+
59
+ # Turn a Symbol, Numeric or Expression into an Expression.
60
+ def self.lift(obj)
61
+ case obj
62
+ when Expression then obj
63
+ when Symbol then Var.new(obj)
64
+ when Numeric then Num.new(obj)
65
+ when Mod, GFElement then Num.new(obj)
66
+ else raise TypeError, "can't convert #{obj.class} into RCAS::Expression"
67
+ end
68
+ end
69
+
70
+ # ---- arithmetic -------------------------------------------------------
71
+
72
+ def +(other) = binary(Add, :+, other)
73
+ def -(other) = binary(Sub, :-, other)
74
+ def *(other) = binary(Mul, :*, other)
75
+ def /(other) = binary(Div, :/, other)
76
+ def **(other) = binary(Pow, :**, other)
77
+ def -@ = Neg.new(self)
78
+ def +@ = self
79
+
80
+ # x < 2 builds an Inequality (see Inequalities); <=> still orders canonically.
81
+ def <(other) = Inequality.new(self, :<, other)
82
+ def <=(other) = Inequality.new(self, :<=, other)
83
+ def >(other) = Inequality.new(self, :>, other)
84
+ def >=(other) = Inequality.new(self, :>=, other)
85
+
86
+ # Called by Numeric operators when the left operand is a plain number:
87
+ # `1 - :x` ends up here (via Symbol#coerce) as Num(1) - Var(x).
88
+ def coerce(other) = [Expression.lift(other), self]
89
+
90
+ def to_expr = self
91
+
92
+ private
93
+
94
+ # Polynomials, vectors and matrices on the right take over the operation.
95
+ def binary(klass, op, other)
96
+ return other.rop(op, self) if other.is_a?(Algebraic)
97
+ klass.new(self, Expression.lift(other))
98
+ end
99
+
100
+ public
101
+
102
+ # ---- tree protocol ----------------------------------------------------
103
+
104
+ def children = []
105
+
106
+ # Build a node of the same kind with new children.
107
+ def rebuild(*_children) = self
108
+
109
+ # Depth-first structural map.
110
+ def map_children(&block)
111
+ return self if children.empty?
112
+ rebuild(*children.map(&block))
113
+ end
114
+
115
+ def leaf? = children.empty?
116
+
117
+ def ==(other)
118
+ return other == self if other.is_a?(Algebraic)
119
+ other.is_a?(Expression) && other.class == self.class && other.children == children
120
+ end
121
+
122
+ # eql? compares the children with eql?, so that eql? implies equal
123
+ # hashes: x + 1 and x + 1.0 are == (1 == 1.0) but not eql?, and they
124
+ # hash differently (third review, C12).
125
+ def eql?(other) = other.is_a?(Expression) && other.class == self.class && other.children.eql?(children)
126
+
127
+ # Nodes are immutable, so the hash is computed once, in the constructor,
128
+ # before the node is frozen: recomputing it walked the whole subtree on
129
+ # every lookup, and the term tables are hashes of expressions. The
130
+ # combination is by hand because building the array cost more than the
131
+ # walk saved - a node is constructed far more often than it is hashed.
132
+ # A node class that does not set it (the formal ones) falls back.
133
+ #
134
+ # FIXNUM is what keeps the combination from growing: multiplying a
135
+ # child's hash by 31 at every level made the hash of a 20000-term sum a
136
+ # 99000-bit integer and the sum itself 390 MB.
137
+ FIXNUM = 0x3fff_ffff_ffff_ffff
138
+
139
+ def hash = @hash || [self.class, *children].hash
140
+
141
+ # Total order used for canonical sorting; see Simplify.sort_key.
142
+ # A plain number is lifted, so that -oo..0 is a Range (Range.new asks
143
+ # <=> and gave up on nil: third review, P-14).
144
+ def <=>(other)
145
+ other = Expression.lift(other) if other.is_a?(Numeric)
146
+ return nil unless other.is_a?(Expression)
147
+ Simplify.sort_key(self) <=> Simplify.sort_key(other)
148
+ end
149
+
150
+ # ---- queries ----------------------------------------------------------
151
+
152
+ # Sorted array of the symbols appearing *free* in the expression: the x
153
+ # of integral(f(x), x, 0, 1) is bound and is not one of them.
154
+ def variables
155
+ result = Set.new
156
+ each_free_variable { |name| result << name }
157
+ result.to_a.sort
158
+ end
159
+
160
+ # The variable this node binds in its first child, or nil. A definite
161
+ # integral, a sum, a product and a limit bind theirs: the value of
162
+ # integral(f(x), x, 0, 1) does not depend on x, and renaming the x
163
+ # changes nothing. The bounds are outside the binding.
164
+ def bound_variable = nil
165
+
166
+ # Yields the name of every free occurrence of a variable (with
167
+ # repetitions). Iterative, like each_node, so deep trees are safe.
168
+ def each_free_variable
169
+ stack = [[self, nil]]
170
+ until stack.empty?
171
+ node, bound = stack.pop
172
+ if node.is_a?(Var)
173
+ yield node.name unless bound&.include?(node.name)
174
+ elsif (b = node.bound_variable)
175
+ body, _var, *rest = node.children
176
+ stack << [body, bound ? bound + [b.name] : [b.name]]
177
+ rest.each { |c| stack << [c, bound] }
178
+ else
179
+ node.children.each { |c| stack << [c, bound] }
180
+ end
181
+ end
182
+ self
183
+ end
184
+
185
+ def each_node(&block)
186
+ return enum_for(:each_node) unless block
187
+ stack = [self]
188
+ until stack.empty?
189
+ node = stack.pop
190
+ yield node
191
+ stack.concat(node.children.reverse)
192
+ end
193
+ self
194
+ end
195
+
196
+ # No Var anywhere. Asking `variables.empty?` built a Set and sorted it
197
+ # to throw both away; this stops at the first one.
198
+ def constant?
199
+ each_free_variable { return false }
200
+ true
201
+ end
202
+
203
+ # Nested-array view of the structure, handy for debugging in irb.
204
+ def to_sexp = [self.class.name.split("::").last.downcase.to_sym, *children.map(&:to_sexp)]
205
+
206
+ # ---- rewriting --------------------------------------------------------
207
+
208
+ # Replace sub-expressions. Accepts a hash or a (pattern, replacement) pair.
209
+ # Keys may be symbols, numbers or whole expressions; matching is structural.
210
+ #
211
+ # (:x + :y).subs(x: 2) # => 2 + y
212
+ # (:x**2 + :x).subs(:x**2 => :z) # => z + x
213
+ def subs(pattern, replacement = nil)
214
+ table = pattern.is_a?(Hash) ? pattern : { pattern => replacement }
215
+ table = table.to_h { |k, v| [Expression.lift(k), Expression.lift(v)] }
216
+ replace_with(table)
217
+ end
218
+
219
+ def simplify = Simplify.simplify(self)
220
+ def expand = Expand.expand(self)
221
+
222
+ # View as an element of a polynomial ring. Without a ring, undeclared
223
+ # variables become ring variables and the coefficient domain is inferred.
224
+ def to_poly(ring = nil)
225
+ ring ||= begin
226
+ # A declaration says which values x takes; x is still an
227
+ # indeterminate. With x in ZZ, factor(x**2 - 1) found "no free
228
+ # variables", and with a in ZZ, x**2 - a**2 had a**2 for a
229
+ # coefficient the factorizer could not take (third review, A2).
230
+ free = variables
231
+ raise DomainError, "#{self} has no free variables to build a ring from" if free.empty?
232
+ constant, table = Expand.table(self)
233
+ base = NumberSet.of(constant)
234
+ table.each do |factors, coeff|
235
+ base = base.join(NumberSet.of(coeff))
236
+ factors.each_key do |b|
237
+ next if b.is_a?(Var) && free.include?(b.name)
238
+ d = Infer.domain(b)
239
+ base = base.join(d) if d
240
+ end
241
+ end
242
+ PolynomialRing.new(base == NN ? ZZ : base, free)
243
+ end
244
+ Polynomial.from_expr(ring, self)
245
+ end
246
+
247
+ # Factor over ZZ or QQ and return the product as an expression.
248
+ #
249
+ # (:x**2 - 1).factor # => (-1 + x)*(1 + x)
250
+ def factor(extension: nil, recombination: nil) = Factor.with_recombination(recombination) { to_poly.factor(extension: extension).to_expr }
251
+
252
+ # Minimal polynomial over QQ of a constant algebraic expression.
253
+ def minpoly(var = :x) = Algebraic.minpoly_of(self, var).to_expr
254
+
255
+ # Derivative with respect to +var+, taken +n+ times.
256
+ def diff(var, n = 1)
257
+ var = Expression.lift(var)
258
+ raise ArgumentError, "can only differentiate with respect to a variable" unless var.is_a?(Var)
259
+ n.times.reduce(self) { |e, _| Differentiate.diff(e, var) }.simplify
260
+ end
261
+
262
+ def trigsimp = Trigonometry.trigsimp(self)
263
+ def expand_trig = Trigonometry.expand_trig(self)
264
+ def expand_log = Trigonometry.expand_log(self)
265
+ def logcombine = Trigonometry.logcombine(self)
266
+
267
+ # One fraction with the polynomial gcd cancelled: ((x**2 - 1)/(x - 1)).cancel => 1 + x
268
+ def cancel = Fraction.cancel(self)
269
+ alias together cancel
270
+
271
+ # Square roots out of denominators: (1/(1 + sqrt(2))).rationalize => -1 + 2**(1/2)
272
+ def rationalize = Fraction.rationalize(self)
273
+
274
+ # Evaluate the formal nodes a hold block left behind (MuPAD's eval):
275
+ # integral(...) is integrated, D(f, x) differentiated (unknown functions
276
+ # stay), sum(...) and limit(...) computed. Bottom-up, so nested nodes work.
277
+ def evaluate
278
+ e = map_children(&:evaluate)
279
+ case e
280
+ when Integral then e.definite? ? Integrate.definite(e.integrand, e.var, e.from, e.to) : Integrate.integrate(e.integrand, e.var)
281
+ when Derivative then e.expr.is_a?(Var) ? e : e.expr.diff(e.var, e.order)
282
+ when Sum then Summation.sum(e.term, e.var, e.from, e.to)
283
+ when Product then Products.product(e.term, e.var, e.from, e.to)
284
+ when Limit then Limits.limit(e.expr, e.var, e.point)
285
+ # hold keeps discuss(f, x) as a call; doit answers it with the report
286
+ # (not an Expression - a discussion is an answer, not a value).
287
+ when Fn then e.name == :discuss ? Discussion.discuss(*e.args) : e
288
+ else e
289
+ end
290
+ end
291
+ alias unhold evaluate
292
+ alias doit evaluate
293
+
294
+ # x.eq(4) is the equation x = 4; solve(var) solves self = 0.
295
+ def eq(other) = Equation.new(self, other)
296
+ def solve(var = nil, all: true, principal: false, domain: nil) = Solve.solve(self, var, all: all, principal: principal, domain: domain)
297
+
298
+ # re, im, conj, arg: the complex parts (see ComplexParts)
299
+ def re = ComplexParts.re(self)
300
+ def im = ComplexParts.im(self)
301
+ def conj = ComplexParts.conj(self)
302
+ def arg = ComplexParts.arg(self)
303
+
304
+ def plot(var = nil, from = nil, to = nil, **opts) = Plotting.plot(self, var, from, to, **opts)
305
+ def series(x, a = 0, n = 6) = Limits.series(self, x, a, n)
306
+ def taylor(x, a = 0, n = 6) = Limits.taylor(self, x, a, n)
307
+ def limit(x, a, dir = nil) = Limits.limit(self, x, a, dir)
308
+
309
+ # Antiderivative with respect to +var+ (no integration constant). Pieces
310
+ # that could not be integrated stay as unevaluated Integral nodes.
311
+ #
312
+ # (:x * RCAS.exp(:x)).integrate(:x) # => -exp(x) + x*exp(x)
313
+ def integrate(var = nil, from = nil, to = nil, generic: false, **range)
314
+ var, from, to = Functions.range_arguments(var, from, to, range, "integrate", discrete: false) if var.nil? || from
315
+ from.nil? ? Integrate.with_special_cases(self, var, generic: generic) : Integrate.definite_with_special_cases(self, var, from, to, generic: generic)
316
+ end
317
+
318
+ # Numeric evaluation: every number becomes a Float so roots and function
319
+ # values fold, then the bindings are applied.
320
+ def evalf(digits = nil, **bindings)
321
+ digits ||= bindings.delete(:digits)
322
+ # a Sum or Product with numeric bounds is evaluated before it is
323
+ # floated (its bounds as Floats were no bounds, and it stayed formal)
324
+ if each_node.any? { |n| n.is_a?(Sum) || n.is_a?(Product) }
325
+ target = bindings.empty? ? self : subs(bindings.to_h { |k, v| [k, Expression.lift(v)] })
326
+ done = target.evaluate
327
+ return done.evalf(digits) unless done == target
328
+ end
329
+ return Precision.evalf(self, digits, bindings) if digits
330
+ value = Expression.floatify_tree(self).call(**bindings.transform_values { |v| Expression.floatify(v) })
331
+ folded = value.is_a?(Expression) && value.constant? ? refloat(value) : nil
332
+ value = folded if folded
333
+ value = Numerics.resolve(value) if value.is_a?(Expression) && value.each_node.any? { |n| n.is_a?(Integral) }
334
+ value = value.value if value.is_a?(Num)
335
+ return wide(bindings) || value if overflowed?(value)
336
+ return value unless cancelled?(value, bindings)
337
+ wide(bindings) || (digitless?(value) ? self : value)
338
+ end
339
+
340
+ # Not one digit survived: the error bound is larger than the value, and
341
+ # the Float is noise with a sign of its own. gamma(-1 + 10**-15) +
342
+ # 10**15 + 5*10**11 came out as -3e11 against a true 5e11; with no
343
+ # arbitrary precision for gamma there, the expression is the answer.
344
+ def digitless?(value)
345
+ _, error = Decide.float_with_error(self)
346
+ !error.nil? && error >= value.abs
347
+ end
348
+
349
+ # A Float that lost most of its digits to cancellation: the running
350
+ # error bound (Decide) says so for a constant. 1 - cdf(30) of
351
+ # Poisson(2) is exact as written and came out as 0 or noise in Floats
352
+ # (fourth review, P-11); the value is taken again in arbitrary
353
+ # precision, which is certified.
354
+ def cancelled?(value, bindings)
355
+ return false unless value.is_a?(Float) && value.finite? && bindings.empty? && variables.empty?
356
+ return false unless each_node.any? { |n| n.is_a?(Add) || n.is_a?(Sub) }
357
+ found, error = Decide.float_with_error(self)
358
+ !found.nil? && error > value.abs * 1e-9
359
+ end
360
+
361
+ # Every leaf a Float first is fast and fails on large parts: 200**200
362
+ # and 200! are Infinity, so 200!/199! came out NaN and the Poisson(200)
363
+ # pmf at 200 Infinity (third review, P-4). When that happens the value
364
+ # is taken again in arbitrary precision, which carries the exponents.
365
+ def overflowed?(value)
366
+ value.is_a?(Float) ? !value.finite? : (value.is_a?(Complex) && !(value.real.to_f.finite? && value.imaginary.to_f.finite?))
367
+ end
368
+
369
+ def wide(bindings)
370
+ return nil unless (variables - bindings.keys.map(&:to_sym)).empty?
371
+ precise = Precision.evalf(self, Precision::FLOAT_DIGITS, bindings)
372
+ v = precise.to_f
373
+ v.finite? ? v : nil
374
+ rescue StandardError, NotImplementedError, RCAS::Unsupported => rescued
375
+ RCAS.guard!(rescued, refused: true)
376
+ nil
377
+ end
378
+
379
+ # Folding can put an exact constant back after the floats went in:
380
+ # exp(-1.0) is 1/e again, and the e never saw floatify. One more pass,
381
+ # kept only when it really ends in a number.
382
+ def refloat(value)
383
+ again = Expression.floatify_tree(value).call
384
+ again = again.value if again.is_a?(Num)
385
+ again.is_a?(Numeric) ? again : nil
386
+ rescue StandardError => rescued
387
+ RCAS.guard!(rescued)
388
+ nil
389
+ end
390
+
391
+ # Every number becomes a Float, except integer exponents: x**2 stays
392
+ # x**2 rather than x**2.0.
393
+ def self.floatify_tree(node)
394
+ case node
395
+ when Const then node.name == :undefined ? node : Num.new(node.value.to_f)
396
+ when RootOf then Num.new(floatify(node.value))
397
+ when Num then node.value.is_a?(Float) || node.finite_field? ? node : Num.new(floatify(node.value))
398
+ when Pow
399
+ exp = node.exponent
400
+ exp = floatify_tree(exp) unless exp.is_a?(Num) && exp.value.is_a?(Integer)
401
+ Pow.new(floatify_tree(node.base), exp)
402
+ else node.map_children { |c| floatify_tree(c) }
403
+ end
404
+ end
405
+
406
+ # Numeric conversions of constant expressions: (PI**2/6).to_f, Num#to_r, Num#to_i
407
+ def to_f
408
+ value = evalf
409
+ raise TypeError, "#{self} is not a constant expression" unless value.is_a?(Numeric)
410
+ value.is_a?(Complex) ? value : value.to_f
411
+ end
412
+
413
+ def self.floatify(value)
414
+ value.is_a?(Complex) ? Complex(float_of(value.real), float_of(value.imaginary)) : float_of(value)
415
+ end
416
+
417
+ # to_f without Ruby's "Integer out of Float range" warning: an integer
418
+ # beyond the Floats is the infinity it rounds to, which evalf's wide
419
+ # fallback then takes again in arbitrary precision.
420
+ def self.float_of(value)
421
+ return value.to_f unless value.is_a?(Integer) && value.bit_length > 1023
422
+ value.positive? ? Float::INFINITY : -Float::INFINITY
423
+ end
424
+
425
+ # Substitute and simplify. Returns a plain Ruby number when everything is
426
+ # bound, otherwise the remaining expression.
427
+ #
428
+ # (:x**2 + :y).call(x: 3, y: 1) # => 10
429
+ # (:x**2 + :y).call(x: 3) # => 9 + y
430
+ def call(*args, **bindings)
431
+ unless args.empty?
432
+ names = variables
433
+ raise ArgumentError, "#{self} has #{names.size} variables, got #{args.size} values" unless names.size == args.size
434
+ bindings = names.zip(args).to_h.merge(bindings)
435
+ end
436
+ result = subs(bindings).simplify
437
+ result.is_a?(Num) ? result.value : result
438
+ end
439
+
440
+ # Turn the expression into a lambda over its variables (sorted by name).
441
+ #
442
+ # [1, 2, 3].map(&(:x**2)) # => [1, 4, 9]
443
+ def to_proc
444
+ names = variables
445
+ ->(*args) { call(**names.zip(args).to_h) }
446
+ end
447
+
448
+ # ---- display ----------------------------------------------------------
449
+
450
+ def to_s = Printer.print(self)
451
+ def inspect = to_s
452
+
453
+ # ---- domains ----------------------------------------------------------
454
+
455
+ # Smallest of NN, ZZ, QQ, RR, CC that must contain this expression's value,
456
+ # given the declared domains of its variables; nil when unknown.
457
+ def domain = Infer.domain(self)
458
+
459
+ def in?(domain) = domain.include?(self)
460
+
461
+ # Declare a variable's domain: `x.in(ZZ)`. Returns the variable.
462
+ def in(domain)
463
+ raise TypeError, "only variables can be declared members of a domain" unless is_a?(Var)
464
+ RCAS.assume(name => domain)
465
+ self
466
+ end
467
+
468
+ protected
469
+
470
+ def replace_with(table)
471
+ return table[self] if table.key?(self)
472
+ return map_children { |c| c.replace_with(table) } unless (b = bound_variable)
473
+ replace_under_binding(table, b)
474
+ end
475
+
476
+ # Substitution under a binder: the bounds take the table as it is; the
477
+ # body does not see a pattern that mentions the bound variable (that is
478
+ # another x), and when a replacement would bring in a free variable of
479
+ # the bound name, the bound variable is renamed first so that it does
480
+ # not capture it - integral(y*f(x), x, 0, 1) with y := x is
481
+ # x*integral(f(x1), x1, 0, 1), not integral(x*f(x), x, 0, 1).
482
+ def replace_under_binding(table, b)
483
+ body, var, *rest = children
484
+ rest = rest.map { |c| c.replace_with(table) }
485
+ inner = table.reject { |k, _| k.variables.include?(b.name) }
486
+ return rebuild(body, var, *rest) if inner.empty?
487
+ if inner.each_value.any? { |v| v.variables.include?(b.name) }
488
+ taken = Set.new
489
+ body.each_node { |n| taken << n.name if n.is_a?(Var) }
490
+ inner.each { |k, v| taken.merge(k.variables).merge(v.variables) }
491
+ rest.each { |c| taken.merge(c.variables) }
492
+ fresh = Expression.fresh_variable(b.name, taken)
493
+ body = body.replace_with({ b => fresh })
494
+ var = fresh
495
+ end
496
+ rebuild(body.replace_with(inner), var, *rest)
497
+ end
498
+
499
+ # x1, x2, ... : the first name built on +name+ that is not taken.
500
+ def self.fresh_variable(name, taken)
501
+ base = name.to_s.sub(/\d+\z/, "")
502
+ base = "t" if base.empty?
503
+ (1..).lazy.map { |i| :"#{base}#{i}" }.find { |candidate| !taken.include?(candidate) }.then { |n| Var.new(n) }
504
+ end
505
+ end
506
+
507
+ # A symbolic variable, e.g. Var.new(:x). Created implicitly from Symbols.
508
+ class Var < Expression
509
+ attr_reader :name
510
+
511
+ def initialize(name)
512
+ @name = name.to_sym
513
+ @hash = @name.hash ^ Var.hash
514
+ freeze
515
+ end
516
+
517
+ def ==(other)
518
+ case other
519
+ when Var then other.name == name
520
+ when Symbol then other == name
521
+ else false
522
+ end
523
+ end
524
+
525
+ def eql?(other) = other.is_a?(Var) && other.name == name
526
+ def to_sexp = name
527
+ end
528
+
529
+ # A numeric literal (Integer, Rational, Float, ...).
530
+ class Num < Expression
531
+ attr_reader :value
532
+
533
+ def initialize(value)
534
+ @value = Simplify.normalize_number(value)
535
+ @hash = @value.hash ^ Num.hash
536
+ freeze
537
+ end
538
+
539
+ def ==(other)
540
+ case other
541
+ when Num then other.value == value
542
+ when Numeric then other == value
543
+ else false
544
+ end
545
+ end
546
+
547
+ def eql?(other) = other.is_a?(Num) && other.value.eql?(value)
548
+ def to_sexp = value
549
+ def zero? = value.zero?
550
+ def one? = value == 1
551
+ def integer? = value.is_a?(Integer)
552
+ def negative? = value.respond_to?(:negative?) && value.negative?
553
+ def to_r = value.to_r
554
+ def to_i = value.to_i
555
+ def to_c = value.to_c
556
+ end
557
+
558
+ # Unary minus.
559
+ class Neg < Expression
560
+ attr_reader :arg
561
+
562
+ def initialize(arg)
563
+ @arg = arg
564
+ @hash = arg.hash ^ Neg.hash
565
+ freeze
566
+ end
567
+
568
+ def children = [arg]
569
+ def rebuild(arg) = Neg.new(arg)
570
+ end
571
+
572
+ # Common base for the four binary operators and Pow.
573
+ class BinaryOp < Expression
574
+ attr_reader :left, :right
575
+
576
+ def initialize(left, right)
577
+ @left = left
578
+ @right = right
579
+ @hash = ((left.hash * 31 + right.hash) ^ self.class.hash) & FIXNUM
580
+ freeze
581
+ end
582
+
583
+ def children = [left, right]
584
+ def rebuild(left, right) = self.class.new(left, right)
585
+ end
586
+
587
+ class Add < BinaryOp; end
588
+ class Sub < BinaryOp; end
589
+ class Mul < BinaryOp; end
590
+ class Div < BinaryOp; end
591
+ class Pow < BinaryOp
592
+ alias base left
593
+ alias exponent right
594
+ end
595
+
596
+ # Application of a named function: Fn.new(:sin, [x]).
597
+ class Fn < Expression
598
+ attr_reader :name, :args
599
+
600
+ def initialize(name, args)
601
+ @name = name.to_sym
602
+ @args = args.map { |a| Expression.lift(a) }.freeze
603
+ @hash = @args.reduce(@name.hash ^ Fn.hash) { |h, a| (h * 31 + a.hash) & FIXNUM }
604
+ freeze
605
+ end
606
+
607
+ def children = args
608
+ def rebuild(*args) = Fn.new(name, args)
609
+ def ==(other) = other.is_a?(Fn) && other.name == name && other.args == args
610
+ def eql?(other) = other.is_a?(Fn) && other.name == name && other.args.eql?(args)
611
+ def to_sexp = [name, *args.map(&:to_sexp)]
612
+ end
613
+ end