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,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
|