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,260 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RCAS
4
+ # The integrals that are not elementary but have names: the exponential
5
+ # integral Ei, the sine and cosine integrals Si and Ci, and the
6
+ # logarithmic integral li.
7
+ #
8
+ # Ei(x) = integral(exp(t)/t, t, -oo, x) (principal value)
9
+ # Si(x) = integral(sin(t)/t, t, 0, x)
10
+ # Ci(x) = gamma + log(x) + integral((cos(t) - 1)/t, t, 0, x)
11
+ # li(x) = Ei(log(x))
12
+ #
13
+ # With them `integrate(sin(x)/x, x)` has an answer instead of staying an
14
+ # integral(...) node, which is the point: a student who meets sin(x)/x
15
+ # should be told that its antiderivative has a name, not that rcas gave up.
16
+ #
17
+ # The numbers are Floats. Ei by its series for moderate arguments and by
18
+ # the asymptotic expansion beyond; Si and Ci by the series below 2 and by
19
+ # a complex continued fraction (modified Lentz) above it.
20
+ #
21
+ # Sources (keys: MANUAL.md, Sources): [AS64, §5.1, §5.2] for the
22
+ # definitions and the series; [PTVF07, §6.3] for the two routines;
23
+ # Lentz's method [Len76].
24
+ module IntegralFunctions
25
+ NAMES = %i[Ei Si Ci li].freeze
26
+ EULER = 0.5772156649015328606
27
+ EPSILON = 1e-16
28
+ TINY = 1e-300
29
+ MAX_ITERATIONS = 200
30
+ SERIES_LIMIT = 36.0 # beyond this the series for Ei loses to cancellation
31
+
32
+ module_function
33
+
34
+ # ---- numerics -----------------------------------------------------------
35
+
36
+ # Ei(x), the principal value of integral(exp(t)/t, t, -oo, x).
37
+ def ei(x)
38
+ x = x.to_f
39
+ raise ArgumentError, "Ei(0) is infinite" if x.zero?
40
+ return Math.log(x.abs) + EULER if x.abs < TINY
41
+ x.abs <= SERIES_LIMIT ? ei_series(x) : ei_asymptotic(x)
42
+ end
43
+
44
+ # Ei(x) = gamma + log|x| + sum_k x**k/(k*k!)
45
+ def ei_series(x)
46
+ sum = 0.0
47
+ fact = 1.0
48
+ (1..MAX_ITERATIONS).each do |k|
49
+ fact *= x / k
50
+ term = fact / k
51
+ sum += term
52
+ break if term.abs < EPSILON * sum.abs
53
+ end
54
+ sum + Math.log(x.abs) + EULER
55
+ end
56
+
57
+ # Ei(x) ~ exp(x)/x * sum_k k!/x**k, summed while the terms shrink.
58
+ def ei_asymptotic(x)
59
+ sum = 0.0
60
+ term = 1.0
61
+ (1..MAX_ITERATIONS).each do |k|
62
+ previous = term
63
+ term *= k / x
64
+ break if term.abs < EPSILON
65
+ if term.abs < previous.abs
66
+ sum += term
67
+ else
68
+ sum -= previous
69
+ break
70
+ end
71
+ end
72
+ Math.exp(x) * (1.0 + sum) / x
73
+ end
74
+
75
+ def si(x) = cisi(x).last
76
+ # On the negative axis Ci is on its cut: the principal value is
77
+ # Ci(|x|) + i*pi, from the log(x) in its definition. A real Float there
78
+ # was a wrong answer on either side of the cut (third review, D12).
79
+ def ci(x)
80
+ value = cisi(x).first
81
+ x.to_f.negative? ? Complex(value, Math::PI) : value
82
+ end
83
+
84
+ # [Ci(x), Si(x)] for a real x; Ci is real only for x > 0.
85
+ def cisi(x)
86
+ x = x.to_f
87
+ return [-Float::INFINITY, 0.0] if x.zero?
88
+ return cisi(-x).then { |c, s| [c, -s] } if x.negative? # Si is odd, Ci is not real
89
+ return [Math.log(x) + EULER, x] if x < Math.sqrt(TINY)
90
+ x <= 2.0 ? cisi_series(x) : cisi_fraction(x)
91
+ end
92
+
93
+ def cisi_series(x)
94
+ sum = 0.0
95
+ sums = 0.0
96
+ sumc = 0.0
97
+ sign = 1.0
98
+ fact = 1.0
99
+ odd = true
100
+ (1..MAX_ITERATIONS).each do |k|
101
+ fact *= x / k
102
+ term = fact / k
103
+ sum += sign * term
104
+ error = term / sum.abs
105
+ if odd
106
+ sign = -sign
107
+ sums = sum
108
+ sum = sumc
109
+ else
110
+ sumc = sum
111
+ sum = sums
112
+ end
113
+ break if error < EPSILON
114
+ odd = !odd
115
+ end
116
+ [sumc + Math.log(x) + EULER, sums]
117
+ end
118
+
119
+ # The continued fraction for exp(i*x)*(Ci + i*(Si - pi/2)) [PTVF07, §6.3].
120
+ def cisi_fraction(x)
121
+ b = Complex(1.0, x)
122
+ c = Complex(1.0 / TINY, 0.0)
123
+ d = 1.0 / b
124
+ h = d
125
+ (2..MAX_ITERATIONS).each do |i|
126
+ a = -((i - 1)**2).to_f
127
+ b += 2.0
128
+ d = 1.0 / (a * d + b)
129
+ c = b + a / c
130
+ delta = c * d
131
+ h *= delta
132
+ break if (delta.real - 1.0).abs + delta.imaginary.abs < EPSILON
133
+ end
134
+ h = Complex(Math.cos(x), -Math.sin(x)) * h
135
+ [-h.real, Math::PI / 2 + h.imaginary]
136
+ end
137
+
138
+ # li(x) = Ei(log(x)), the count of primes' companion.
139
+ def li(x)
140
+ x = x.to_f
141
+ return 0.0 if x.zero?
142
+ raise ArgumentError, "li: a positive argument is needed, got #{x}" unless x.positive?
143
+ raise ArgumentError, "li(1) is infinite" if x == 1.0
144
+ ei(Math.log(x))
145
+ end
146
+
147
+ def evaluate(name, value)
148
+ case name
149
+ when :Ei then ei(value)
150
+ when :Si then si(value)
151
+ when :Ci then ci(value)
152
+ when :li then li(value)
153
+ end
154
+ end
155
+
156
+ # ---- exact values -------------------------------------------------------
157
+
158
+ # The values a table lists, and Floats; nil when there is nothing to say.
159
+ def value(name, arg)
160
+ return Num.new(evaluate(name, arg.value)) if arg.is_a?(Num) && arg.value.is_a?(Float)
161
+ return infinite_value(name, arg) if arg == OO || arg == Neg.new(OO).simplify
162
+ return nil unless arg.is_a?(Num) && arg.value.is_a?(Numeric) && arg.value.real?
163
+ zero = arg.value.zero?
164
+ case name
165
+ when :Si then zero ? Num.new(0) : nil
166
+ when :Ci, :Ei then zero ? Neg.new(OO).simplify : nil
167
+ when :li
168
+ return Num.new(0) if zero
169
+ arg.value == 1 ? Neg.new(OO).simplify : nil
170
+ end
171
+ end
172
+
173
+ def infinite_value(name, arg)
174
+ positive = arg == OO
175
+ case name
176
+ when :Si then positive ? (PI / 2).simplify : Neg.new(PI / 2).simplify
177
+ when :Ci then positive ? Num.new(0) : nil
178
+ when :Ei then positive ? OO : Num.new(0)
179
+ when :li then positive ? OO : nil
180
+ end
181
+ end
182
+
183
+ # ---- calculus -----------------------------------------------------------
184
+
185
+ # The derivatives: the integrands these functions were named for.
186
+ def derivative(name, u)
187
+ case name
188
+ when :Ei then Div.new(Fn.new(:exp, [u]), u)
189
+ when :Si then Div.new(Fn.new(:sin, [u]), u)
190
+ when :Ci then Div.new(Fn.new(:cos, [u]), u)
191
+ when :li then Div.new(Num.new(1), Fn.new(:log, [u]))
192
+ end
193
+ end
194
+
195
+ # exp(u)/u, sin(u)/u, cos(u)/u and 1/log(u) with u = a*x + b.
196
+ # Everything else (exp(x)/(x + 1), say) reaches this through the linear
197
+ # substitution Integrate.shift makes.
198
+ INTEGRANDS = { exp: :Ei, sin: :Si, cos: :Ci }.freeze
199
+
200
+ def antiderivative(f, x)
201
+ coeff, factors = Simplify.factorize(f)
202
+ return nil unless coeff == 1
203
+ log_case = logarithmic(factors, x)
204
+ return log_case if log_case
205
+ return nil unless factors.size == 2
206
+ name, u = numerator_of(factors)
207
+ return nil if name.nil?
208
+ denominator = factors.find { |_, exp| exp == -1 }&.first
209
+ return nil if denominator.nil?
210
+ a, = Integrate.linear(u, x)
211
+ return nil if a.nil?
212
+ return Div.new(Fn.new(name, [u]), a) if denominator == u || Scalar.zero?(denominator - u)
213
+ shifted_exponential(name, u, denominator, x)
214
+ end
215
+
216
+ # integral(exp(a*x + b)/(c*x + d)) = exp(b - a*d/c)/c * Ei(a*x + a*d/c):
217
+ # the substitution t = c*x + d leaves a constant factor behind. Only the
218
+ # exponential has this form; sin and cos would need both Si and Ci.
219
+ def shifted_exponential(name, u, denominator, x)
220
+ return nil unless name == :Ei
221
+ a, b = Integrate.linear(u, x)
222
+ c, d = Integrate.linear(denominator, x)
223
+ return nil if a.nil? || c.nil?
224
+ factor = Fn.new(:exp, [(b - a * d / c).simplify])
225
+ argument = (a * x + a * d / c).simplify
226
+ (factor * Fn.new(:Ei, [argument]) / c).simplify
227
+ end
228
+
229
+ # [the function to name the integral, its argument]. An exponential is
230
+ # kept as exp_base**u in a factor table (simplify.rb), not as Fn(:exp).
231
+ def numerator_of(factors)
232
+ factors.each do |base, exp|
233
+ return [:Ei, Expression.lift(exp)] if base == Simplify.exp_base
234
+ next unless exp == 1 && base.is_a?(Fn) && INTEGRANDS.key?(base.name) && base.args.size == 1
235
+ return [INTEGRANDS[base.name], base.args.first]
236
+ end
237
+ nil
238
+ end
239
+
240
+ # 1/log(a*x + b) = li(a*x + b)/a
241
+ def logarithmic(factors, x)
242
+ return nil unless factors.size == 1
243
+ base, exp = factors.first
244
+ return nil unless exp == -1 && base.is_a?(Fn) && base.name == :log && base.args.size == 1
245
+ u = base.args.first
246
+ a, = Integrate.linear(u, x)
247
+ a.nil? ? nil : Div.new(Fn.new(:li, [u]), a)
248
+ end
249
+
250
+ # The limits at the ends of the real line, which is what a definite
251
+ # integral over an infinite range needs. nil when this is not one of ours.
252
+ def limit(f, x, point)
253
+ return nil unless f.is_a?(Fn) && NAMES.include?(f.name) && f.args.size == 1
254
+ return nil unless Limits.infinite?(point)
255
+ inner = Limits.limit(f.args.first, x, point)
256
+ return nil unless inner == OO || inner == Neg.new(OO).simplify
257
+ value(f.name, inner)
258
+ end
259
+ end
260
+ end