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
data/lib/rcas/scalar.rb
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCAS
|
|
4
|
+
# Arithmetic on matrix/vector/polynomial entries. Entries are Expressions;
|
|
5
|
+
# purely numeric ones take an exact fast path (Integer/Rational stay exact).
|
|
6
|
+
module Scalar
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def lift(x) = Expression.lift(x)
|
|
10
|
+
|
|
11
|
+
def add(a, b) = nums?(a, b) ? Num.new(a.value + b.value) : (a + b).expand
|
|
12
|
+
def sub(a, b) = nums?(a, b) ? Num.new(a.value - b.value) : (a - b).expand
|
|
13
|
+
def mul(a, b) = nums?(a, b) ? Num.new(a.value * b.value) : (a * b).expand
|
|
14
|
+
def neg(a) = a.is_a?(Num) ? Num.new(-a.value) : Simplify.negate(a)
|
|
15
|
+
|
|
16
|
+
def div(a, b)
|
|
17
|
+
if nums?(a, b)
|
|
18
|
+
raise ZeroDivisionError, "division by zero" if b.value.zero?
|
|
19
|
+
Num.new(a.value.quo(b.value))
|
|
20
|
+
else
|
|
21
|
+
(a / b).simplify
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Exact for numbers; for constant expressions such as (1 + sqrt(5))/2 - phi
|
|
26
|
+
# a numeric evaluation decides (algebraic numbers have no canonical form here).
|
|
27
|
+
def zero?(a)
|
|
28
|
+
return a.value.zero? if a.is_a?(Num)
|
|
29
|
+
return identically_zero?(a) if a.is_a?(Expression) && !a.constant?
|
|
30
|
+
return false unless a.is_a?(Expression) && a.constant? && a.each_node.none? { |n| n.is_a?(Integral) || n.is_a?(Derivative) }
|
|
31
|
+
# a value clear of its own rounding is no zero, and saying so costs a
|
|
32
|
+
# walk; the exact field arithmetic below cost seconds on the series
|
|
33
|
+
# coefficients of a Weierstrass antiderivative (fourth review)
|
|
34
|
+
return false if Decide.float_nonzero?(a)
|
|
35
|
+
exact = Algebraic.exact(a)
|
|
36
|
+
return exact.zero? if exact
|
|
37
|
+
v = begin
|
|
38
|
+
a.evalf
|
|
39
|
+
rescue StandardError => rescued
|
|
40
|
+
RCAS.guard!(rescued)
|
|
41
|
+
return false
|
|
42
|
+
end
|
|
43
|
+
return false unless v.is_a?(Numeric) && v.abs < 1e-12
|
|
44
|
+
vanishes?(a)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# An expression in indeterminates is zero when it is the zero polynomial
|
|
48
|
+
# or rational function: (a**2 - 1)/(a - 1) - a - 1 is, and a symbolic
|
|
49
|
+
# pivot that vanishes identically is no pivot - rank said 2 for a
|
|
50
|
+
# singular matrix (third review, L9). This is not the generic-pivot
|
|
51
|
+
# assumption, which is about pivots that vanish only for special values.
|
|
52
|
+
def identically_zero?(a)
|
|
53
|
+
return false if a.each_node.any? { |n| n.is_a?(Integral) || n.is_a?(Derivative) || n.is_a?(Sum) || n.is_a?(Limit) }
|
|
54
|
+
# a value at one rational point is a proof of the opposite, and costs
|
|
55
|
+
# a walk where the normal forms below cost an expansion: a generic 4x4
|
|
56
|
+
# rank spent 1.5 s in them (fourth review)
|
|
57
|
+
return false if nonzero_somewhere?(a)
|
|
58
|
+
expanded = a.expand
|
|
59
|
+
return true if expanded.is_a?(Num) && expanded.value.zero?
|
|
60
|
+
if expanded.each_node.any? { |n| n.is_a?(Div) || (n.is_a?(Pow) && n.exponent.is_a?(Num) && Simplify.negative?(n.exponent.value)) }
|
|
61
|
+
cancelled = expanded.cancel
|
|
62
|
+
return true if cancelled.is_a?(Num) && cancelled.value.zero?
|
|
63
|
+
end
|
|
64
|
+
radical_zero?(expanded) || trigonometric_zero?(expanded)
|
|
65
|
+
rescue StandardError => rescued
|
|
66
|
+
RCAS.guard!(rescued)
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# A rational function of radicals t_i = B_i**(1/q_i) is zero when its
|
|
71
|
+
# numerator, as a polynomial in the t_i, reduces to 0 modulo the
|
|
72
|
+
# t_i**q_i - B_i. That is a proof - each t_i satisfies its equation -
|
|
73
|
+
# and cancel cannot see it, since a radical is an atom to it. The
|
|
74
|
+
# pivots of A - lambda*I at an eigenvalue of matrix([[a, b], [c, d]])
|
|
75
|
+
# are of this kind, and each read as non-zero, so the eigenspaces came
|
|
76
|
+
# back empty. A non-zero remainder proves nothing (t**q - B may
|
|
77
|
+
# factor) and says false. A radical inside another's base is reduced
|
|
78
|
+
# after it.
|
|
79
|
+
def radical_zero?(e)
|
|
80
|
+
radicals = e.each_node.select do |n|
|
|
81
|
+
n.is_a?(Pow) && n.exponent.is_a?(Num) && n.exponent.value.is_a?(Rational) && n.exponent.value.denominator > 1
|
|
82
|
+
end
|
|
83
|
+
return false unless radicals.any? { |n| !n.base.constant? }
|
|
84
|
+
degrees = {}
|
|
85
|
+
radicals.each { |n| degrees[n.base] = (degrees[n.base] || 1).lcm(n.exponent.value.denominator) }
|
|
86
|
+
used = e.variables
|
|
87
|
+
atoms = degrees.to_h do |b, _|
|
|
88
|
+
t = Expression.fresh_variable(:t, used)
|
|
89
|
+
used += [t.name]
|
|
90
|
+
[b, t]
|
|
91
|
+
end
|
|
92
|
+
atomized = replace_radical(e, degrees, atoms)
|
|
93
|
+
numerator = RationalFunction.numer(atomized.cancel).expand
|
|
94
|
+
# outer radicals first: reducing t**q to B brings in the radicals
|
|
95
|
+
# inside B, never the other way round
|
|
96
|
+
depth = Hash.new { |h, b| h[b] = 1 + degrees.keys.select { |o| o != b && b.each_node.any? { |n| n.is_a?(Pow) && n.base == o } }.map { |o| h[o] }.max.to_i }
|
|
97
|
+
atoms.keys.sort_by { |b| -depth[b] }.each do |b|
|
|
98
|
+
t = atoms[b]
|
|
99
|
+
q = degrees[b]
|
|
100
|
+
next unless numerator.variables.include?(t.name)
|
|
101
|
+
value = replace_radical(b, degrees, atoms)
|
|
102
|
+
numerator = numerator.coeffs(t).each_with_index.reduce(Num.new(0)) do |sum, (c, k)|
|
|
103
|
+
sum + c * value**(k / q) * t**(k % q)
|
|
104
|
+
end.expand
|
|
105
|
+
end
|
|
106
|
+
numerator.is_a?(Num) && numerator.value.is_a?(Numeric) && numerator.value.zero?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def replace_radical(e, degrees, atoms)
|
|
110
|
+
if e.is_a?(Pow) && atoms.key?(e.base) && e.exponent.is_a?(Num) && e.exponent.value.is_a?(Rational)
|
|
111
|
+
k = e.exponent.value * degrees[e.base]
|
|
112
|
+
return atoms[e.base]**k.to_i if k.denominator == 1
|
|
113
|
+
end
|
|
114
|
+
return e if e.children.empty?
|
|
115
|
+
e.map_children { |c| replace_radical(c, degrees, atoms) }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# cos(u)**2 is 1 - sin(u)**2 and cosh(u)**2 is 1 + sinh(u)**2: with every
|
|
119
|
+
# even power rewritten so, a sum of Pythagorean identities expands to 0
|
|
120
|
+
# in one linear pass - 25 of them were past the size where trigsimp
|
|
121
|
+
# runs, and a pivot that is zero stood as one (fourth review, L9).
|
|
122
|
+
def pythagorean_zero?(e)
|
|
123
|
+
rewritten = pythagorean(e)
|
|
124
|
+
return false if rewritten.equal?(e)
|
|
125
|
+
reduced = rewritten.expand
|
|
126
|
+
reduced.is_a?(Num) && reduced.value.is_a?(Numeric) && reduced.value.zero?
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def pythagorean(e)
|
|
130
|
+
if e.is_a?(Pow) && e.base.is_a?(Fn) && %i[cos cosh].include?(e.base.name) && e.exponent.is_a?(Num) &&
|
|
131
|
+
e.exponent.value.is_a?(Integer) && e.exponent.value.positive? && e.exponent.value.even?
|
|
132
|
+
other = Fn.new(e.base.name == :cos ? :sin : :sinh, e.base.args)
|
|
133
|
+
square = e.base.name == :cos ? Num.new(1) - other**2 : Num.new(1) + other**2
|
|
134
|
+
return square**(e.exponent.value / 2)
|
|
135
|
+
end
|
|
136
|
+
return e if e.children.empty?
|
|
137
|
+
mapped = e.map_children { |c| pythagorean(c) }
|
|
138
|
+
mapped == e ? e : mapped
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# True when the expression has an exact non-zero value at a rational
|
|
142
|
+
# point (a fixed one, so the answer does not depend on the run).
|
|
143
|
+
def nonzero_somewhere?(a)
|
|
144
|
+
names = a.variables
|
|
145
|
+
point = names.each_with_index.to_h { |name, i| [name, Num.new(Rational(PRIMES[i % PRIMES.size], 7 + i))] }
|
|
146
|
+
value = a.subs(point).simplify
|
|
147
|
+
return !value.value.zero? if value.is_a?(Num) && value.value.is_a?(Numeric)
|
|
148
|
+
value.variables.empty? && Decide.zero?(value) == false
|
|
149
|
+
rescue ZeroDivisionError
|
|
150
|
+
false
|
|
151
|
+
rescue StandardError => rescued
|
|
152
|
+
RCAS.guard!(rescued)
|
|
153
|
+
false
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
PRIMES = [3, 5, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61].freeze
|
|
157
|
+
|
|
158
|
+
# sin(2*x) - 2*sin(x)*cos(x) and sin(x)**2 + cos(x)**2 - 1: zero by an
|
|
159
|
+
# identity, which the addition formulas and trigsimp show. Only for
|
|
160
|
+
# small expressions - this runs inside elimination.
|
|
161
|
+
def trigonometric_zero?(e)
|
|
162
|
+
return false unless e.each_node.any? { |n| n.is_a?(Fn) && Trigonometry::SQUARES.key?(n.name) }
|
|
163
|
+
return true if pythagorean_zero?(e)
|
|
164
|
+
return false if e.each_node.count > 200 || large_multiple?(e)
|
|
165
|
+
rewritten = Trigonometry.expand_trig(e).expand
|
|
166
|
+
return false if rewritten.each_node.count > 2000
|
|
167
|
+
return true if rewritten.is_a?(Num) && rewritten.value.zero?
|
|
168
|
+
reduced = Trigonometry.trigsimp(rewritten).simplify
|
|
169
|
+
reduced.is_a?(Num) && reduced.value.zero?
|
|
170
|
+
rescue StandardError => rescued
|
|
171
|
+
RCAS.guard!(rescued)
|
|
172
|
+
false
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# expand_trig writes sin(n*u) as a polynomial of degree n in sin(u) and
|
|
176
|
+
# cos(u), and trigsimp is superlinear in it: sin(280*pi*x) took ten
|
|
177
|
+
# seconds, sin(27720*pi*x) did not come back (the sixth review's
|
|
178
|
+
# preflight). An identity that needs a multiple beyond this is not one
|
|
179
|
+
# this test will find anyway.
|
|
180
|
+
MAX_MULTIPLE = 12
|
|
181
|
+
|
|
182
|
+
def large_multiple?(e)
|
|
183
|
+
e.each_node.any? do |n|
|
|
184
|
+
next false unless n.is_a?(Fn) && Trigonometry::SQUARES.key?(n.name)
|
|
185
|
+
_, terms = Expand.table(n.args.first)
|
|
186
|
+
terms.values.any? { |c| c.is_a?(Numeric) && c.real? && c.abs > MAX_MULTIPLE }
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# 1e-12 is not zero. exp(-100) is 3.7e-44, and a determinant built from
|
|
191
|
+
# it used to come out as 0; i*exp(-40) is complex and has no
|
|
192
|
+
# arbitrary-precision value, so it went on the float verdict and was
|
|
193
|
+
# zero too. Decide takes the parts apart and asks each at two
|
|
194
|
+
# precisions, and a normal form proves the zeros. What it leaves
|
|
195
|
+
# undecided is not zero: that is the generic-pivot assumption (the
|
|
196
|
+
# fourth review: exp(-30)*gamma(1/3) was a zero determinant).
|
|
197
|
+
def vanishes?(expr) = Decide.zero?(expr) == true
|
|
198
|
+
def one?(a) = a.is_a?(Num) && a.value == 1
|
|
199
|
+
def negative?(a) = a.is_a?(Num) && Simplify.negative?(a.value)
|
|
200
|
+
def numeric?(a) = a.is_a?(Num)
|
|
201
|
+
|
|
202
|
+
def nums?(a, b) = a.is_a?(Num) && b.is_a?(Num)
|
|
203
|
+
|
|
204
|
+
# Domain of a scalar for result-space bookkeeping; nil when unknown.
|
|
205
|
+
def domain(a) = a.is_a?(Expression) ? a.domain : nil
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Returned by Vector#coerce / Matrix#coerce so `2 * v` works but `2 - v` raises.
|
|
209
|
+
class ScalarProxy
|
|
210
|
+
def initialize(value) = @value = value
|
|
211
|
+
def *(other) = other * @value
|
|
212
|
+
|
|
213
|
+
def method_missing(name, *_args)
|
|
214
|
+
raise TypeError, "can't apply #{name} to a scalar and a #{@other_class || 'vector'}"
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def respond_to_missing?(*) = false
|
|
218
|
+
end
|
|
219
|
+
end
|