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