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,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RCAS
4
+ # The incomplete gamma and beta functions, numerically. They are what the
5
+ # chi-square, Student t and F distributions need for their CDFs; everything
6
+ # here returns Floats and is labelled as numeric where it surfaces.
7
+ #
8
+ # Special.gamma_p(2.0, 1.5) # regularized lower incomplete gamma P(a, x)
9
+ # Special.beta_i(0.3, 2.0, 5) # regularized incomplete beta I_x(a, b)
10
+ #
11
+ # P(a, x) by the series for x < a + 1 and by the continued fraction for
12
+ # Q(a, x) beyond; I_x(a, b) by the continued fraction with the symmetry
13
+ # I_x(a, b) = 1 - I_(1-x)(b, a) picking the fast side. Both are evaluated
14
+ # with the modified Lentz algorithm.
15
+ #
16
+ # Sources (keys: MANUAL.md, Sources): [AS64, §6.5, §26.5]; [PTVF07, §6.2,
17
+ # §6.4]; Lentz's method [Len76].
18
+ module Special
19
+ EPSILON = 1e-16
20
+ TINY = 1e-300
21
+ MAX_ITERATIONS = 300
22
+
23
+ module_function
24
+
25
+ def float(x, name)
26
+ v = x.is_a?(Numeric) ? x : Expression.lift(x).evalf
27
+ raise ArgumentError, "#{name}: a real number is needed, got #{x}" unless v.is_a?(Numeric) && !v.is_a?(Complex)
28
+ v.to_f
29
+ end
30
+
31
+ def log_gamma(x) = Math.lgamma(x).first
32
+
33
+ # Regularized lower incomplete gamma P(a, x) = gamma(a, x) / Gamma(a).
34
+ def gamma_p(a, x)
35
+ a = float(a, "gamma_p")
36
+ x = float(x, "gamma_p")
37
+ raise ArgumentError, "gamma_p: a must be positive" unless a.positive?
38
+ return 0.0 if x <= 0
39
+ x < a + 1.0 ? gamma_series(a, x) : 1.0 - gamma_cf(a, x)
40
+ end
41
+
42
+ # Regularized upper incomplete gamma Q(a, x) = 1 - P(a, x).
43
+ def gamma_q(a, x)
44
+ a = float(a, "gamma_q")
45
+ x = float(x, "gamma_q")
46
+ return 1.0 if x <= 0
47
+ x < a + 1.0 ? 1.0 - gamma_series(a, x) : gamma_cf(a, x)
48
+ end
49
+
50
+ # The series needs about sqrt(a) terms near x = a, and stopping at a
51
+ # fixed 300 without a word gave ChiSquare(10**6 + 1).cdf(10**6 + 1) as
52
+ # 0.16 instead of 0.5 (third review, P-5): the budget grows with a, and
53
+ # running out of it is a refusal.
54
+ def budget(a) = MAX_ITERATIONS + (40 * Math.sqrt(a.abs)).ceil
55
+
56
+ def gamma_series(a, x)
57
+ ap = a
58
+ term = 1.0 / a
59
+ sum = term
60
+ converged = false
61
+ budget(a).times do
62
+ ap += 1
63
+ term *= x / ap
64
+ sum += term
65
+ if term.abs < sum.abs * EPSILON
66
+ converged = true
67
+ break
68
+ end
69
+ end
70
+ raise ArgumentError, "gamma_p: the series did not converge for a = #{a}, x = #{x}" unless converged
71
+ sum * Math.exp(log_prefactor(a, x))
72
+ end
73
+
74
+ # log(x**a * exp(-x) / Gamma(a)). For a large a the three terms are each
75
+ # of size a*log(a) and their sum is of size log(a): computed directly it
76
+ # loses nine digits at a = 10**6. Stirling's series for log Gamma(a)
77
+ # [AS64, 6.1.41] lets the large parts cancel on paper instead:
78
+ # a*(log1p(t) - t) - log(2*pi*a)/2 + ... with t = (x - a)/a.
79
+ def log_prefactor(a, x)
80
+ return -x + a * Math.log(x) - log_gamma(a) if a < 100
81
+ t = (x - a) / a
82
+ core = if t.abs < 0.1
83
+ # log1p(t) - t by its series, without the cancellation
84
+ (2..60).reduce(0.0) { |acc, k| acc + ((k.even? ? -1 : 1) * t**k / k) }
85
+ else
86
+ Math.log(1 + t) - t
87
+ end
88
+ stirling = 1.0 / (12 * a) - 1.0 / (360 * a**3) + 1.0 / (1260 * a**5)
89
+ a * core + 0.5 * Math.log(a) - 0.5 * Math.log(2 * Math::PI) - stirling
90
+ end
91
+
92
+ # Q(a, x) as a continued fraction (Lentz), for x >= a + 1.
93
+ def gamma_cf(a, x)
94
+ b = x + 1.0 - a
95
+ c = 1.0 / TINY
96
+ d = 1.0 / b
97
+ h = d
98
+ converged = false
99
+ (1..budget(a)).each do |i|
100
+ an = -i * (i - a)
101
+ b += 2.0
102
+ d = an * d + b
103
+ d = TINY if d.abs < TINY
104
+ c = b + an / c
105
+ c = TINY if c.abs < TINY
106
+ d = 1.0 / d
107
+ delta = d * c
108
+ h *= delta
109
+ if (delta - 1.0).abs < EPSILON
110
+ converged = true
111
+ break
112
+ end
113
+ end
114
+ raise ArgumentError, "gamma_q: the continued fraction did not converge for a = #{a}, x = #{x}" unless converged
115
+ Math.exp(log_prefactor(a, x)) * h
116
+ end
117
+
118
+ # Regularized incomplete beta I_x(a, b).
119
+ def beta_i(x, a, b)
120
+ x = float(x, "beta_i")
121
+ a = float(a, "beta_i")
122
+ b = float(b, "beta_i")
123
+ raise ArgumentError, "beta_i: a and b must be positive" unless a.positive? && b.positive?
124
+ return 0.0 if x <= 0
125
+ return 1.0 if x >= 1
126
+ front = Math.exp(log_gamma(a + b) - log_gamma(a) - log_gamma(b) + a * Math.log(x) + b * Math.log(1.0 - x))
127
+ x < (a + 1.0) / (a + b + 2.0) ? front * beta_cf(x, a, b) / a : 1.0 - front * beta_cf(1.0 - x, b, a) / b
128
+ end
129
+
130
+ def beta_cf(x, a, b)
131
+ qab = a + b
132
+ qap = a + 1.0
133
+ qam = a - 1.0
134
+ c = 1.0
135
+ d = 1.0 - qab * x / qap
136
+ d = TINY if d.abs < TINY
137
+ d = 1.0 / d
138
+ h = d
139
+ (1..budget([a, b].max)).each do |m|
140
+ m2 = 2 * m
141
+ an = m * (b - m) * x / ((qam + m2) * (a + m2))
142
+ d = 1.0 + an * d
143
+ d = TINY if d.abs < TINY
144
+ c = 1.0 + an / c
145
+ c = TINY if c.abs < TINY
146
+ d = 1.0 / d
147
+ h *= d * c
148
+ an = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2))
149
+ d = 1.0 + an * d
150
+ d = TINY if d.abs < TINY
151
+ c = 1.0 + an / c
152
+ c = TINY if c.abs < TINY
153
+ d = 1.0 / d
154
+ delta = d * c
155
+ h *= delta
156
+ return h if (delta - 1.0).abs < EPSILON
157
+ end
158
+ # a continued fraction that has not settled is no value: StudentT with
159
+ # 10**12 degrees of freedom printed 0.84128 for 0.84134 (fourth review)
160
+ raise ArgumentError, "beta_i: the continued fraction did not converge for a = #{a}, b = #{b}, x = #{x}"
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RCAS
4
+ # Descriptive statistics and least squares on lists of data, exact.
5
+ #
6
+ # mean([1, 2, 3, 4]) # => 5/2
7
+ # variance([2, 4, 4, 4, 5, 5, 7, 9]) # => 32/7 (sample variance, n - 1)
8
+ # quantile([1, 2, 3, 4], 1/4r) # => 7/4
9
+ # mean([a, b, c]) # => a/3 + b/3 + c/3
10
+ # linreg([1, 2, 3], [2, 4, 7], x) # => -2/3 + 5*x/2
11
+ #
12
+ # Conventions follow Maple, Mathematica and MuPAD: variance, standard
13
+ # deviation and covariance divide by n - 1 unless sample: false; skewness
14
+ # and kurtosis are the standardized third and fourth central moments
15
+ # (kurtosis 3 for a normal sample, not the excess); quantiles interpolate
16
+ # linearly between order statistics (definition 7 of [HF96], the default of
17
+ # R and Excel). Data may be symbolic wherever no ordering is needed.
18
+ #
19
+ # Sources (keys: MANUAL.md, Sources): [HF96]; least squares and the moment
20
+ # statistics are textbook [Ros14, ch. 7].
21
+ module Statistics
22
+ module_function
23
+
24
+ def data(list, name)
25
+ list = list.to_a if list.is_a?(Range)
26
+ raise ArgumentError, "#{name}: a non-empty list of values is needed, got #{list.inspect}" unless list.is_a?(Array) && !list.empty?
27
+ list.map { |v| Expression.lift(v) }
28
+ end
29
+
30
+ # Sorted ascending by numeric value; symbolic data has no order.
31
+ def sorted(list, name)
32
+ values = data(list, name)
33
+ keyed = values.map do |v|
34
+ f = v.evalf
35
+ raise ArgumentError, "#{name}: #{v} is not a real number" unless f.is_a?(Numeric) && !f.is_a?(Complex)
36
+ [f, v]
37
+ end
38
+ # exact numbers compare exactly, anything else by Inequalities.compare
39
+ # where the Floats cannot tell: 1 and 1 + 10**-20 are one Float
40
+ # (third review, P-10)
41
+ keyed.sort do |(fa, a), (fb, b)|
42
+ if a.is_a?(Num) && b.is_a?(Num) && a.value.real? && b.value.real?
43
+ a.value <=> b.value
44
+ elsif (fa - fb).abs > 1e-9 * [1.0, fa.abs, fb.abs].max
45
+ fa <=> fb
46
+ else
47
+ Inequalities.compare(a, b) || (fa <=> fb)
48
+ end
49
+ end.map(&:last)
50
+ end
51
+
52
+ def size(list) = Num.new(list.size)
53
+
54
+ def mean(list)
55
+ values = data(list, "mean")
56
+ (values.reduce(:+) / size(values)).simplify
57
+ end
58
+
59
+ def median(list)
60
+ values = sorted(list, "median")
61
+ n = values.size
62
+ return values[n / 2] if n.odd?
63
+ ((values[n / 2 - 1] + values[n / 2]) / 2).simplify
64
+ end
65
+
66
+ # The most frequent value; a list when several tie.
67
+ def mode(list)
68
+ counts = frequencies(list)
69
+ top = counts.values.max
70
+ modes = counts.select { |_, c| c == top }.keys
71
+ modes.size == 1 ? modes.first : modes
72
+ end
73
+
74
+ # { value => count }, ordered by value where the data is numeric.
75
+ def frequencies(list)
76
+ values = data(list, "frequencies").map(&:simplify)
77
+ counts = {}
78
+ values.each { |v| counts[v] = (counts[v] || 0) + 1 }
79
+ numeric = counts.keys.all? { |v| (f = v.evalf).is_a?(Numeric) && !f.is_a?(Complex) }
80
+ numeric ? counts.sort_by { |v, _| v.evalf }.to_h : counts
81
+ end
82
+
83
+ def variance(list, sample: true)
84
+ values = data(list, "variance")
85
+ raise ArgumentError, "variance: at least two values are needed for a sample variance" if sample && values.size < 2
86
+ m = mean(values)
87
+ squares = values.map { |v| ((v - m)**2).expand }.reduce(:+)
88
+ (squares / Num.new(sample ? values.size - 1 : values.size)).simplify
89
+ end
90
+
91
+ def stdev(list, sample: true) = RCAS.sqrt(variance(list, sample: sample)).simplify
92
+
93
+ # k-th central (or raw) moment, divided by n.
94
+ def moment(list, k, central: true)
95
+ values = data(list, "moment")
96
+ shift = central ? mean(values) : Num.new(0)
97
+ (values.map { |v| ((v - shift)**k).expand }.reduce(:+) / size(values)).simplify
98
+ end
99
+
100
+ def skewness(list)
101
+ m2 = moment(list, 2)
102
+ (moment(list, 3) / m2**Rational(3, 2)).simplify
103
+ end
104
+
105
+ def kurtosis(list)
106
+ m2 = moment(list, 2)
107
+ (moment(list, 4) / m2**2).simplify
108
+ end
109
+
110
+ # Definition 7 of [HF96]: h = (n - 1) p, linear interpolation between the
111
+ # order statistics x[floor h] and x[floor h + 1].
112
+ def quantile(list, p)
113
+ values = sorted(list, "quantile")
114
+ p = Expression.lift(p)
115
+ raise ArgumentError, "quantile: p must be a number in [0, 1], got #{p}" unless p.is_a?(Num) && p.value.real? && p.value.between?(0, 1)
116
+ h = (values.size - 1) * p.value
117
+ i = h.floor
118
+ return values[i] if i == h || i + 1 >= values.size
119
+ (values[i] + (values[i + 1] - values[i]) * Num.new(Simplify.normalize_number(h - i))).simplify
120
+ end
121
+
122
+ def quartiles(list) = [quantile(list, Rational(1, 4)), median(list), quantile(list, Rational(3, 4))]
123
+ def iqr(list) = (quantile(list, Rational(3, 4)) - quantile(list, Rational(1, 4))).simplify
124
+
125
+ def geometric_mean(list)
126
+ values = data(list, "geometric_mean")
127
+ (values.reduce(:*)**Rational(1, values.size)).simplify
128
+ end
129
+
130
+ def harmonic_mean(list)
131
+ values = data(list, "harmonic_mean")
132
+ (size(values) / values.map { |v| 1 / v }.reduce(:+)).simplify
133
+ end
134
+
135
+ # ---- two variables ----------------------------------------------------------
136
+
137
+ def pairs(xs, ys, name)
138
+ xs = data(xs, name)
139
+ ys = data(ys, name)
140
+ raise ArgumentError, "#{name}: the lists must have the same length (#{xs.size} and #{ys.size})" unless xs.size == ys.size
141
+ raise ArgumentError, "#{name}: at least two pairs are needed" if xs.size < 2
142
+ [xs, ys]
143
+ end
144
+
145
+ def covariance(xs, ys, sample: true)
146
+ xs, ys = pairs(xs, ys, "covariance")
147
+ mx = mean(xs)
148
+ my = mean(ys)
149
+ total = xs.zip(ys).map { |x, y| ((x - mx) * (y - my)).expand }.reduce(:+)
150
+ (total / Num.new(sample ? xs.size - 1 : xs.size)).simplify
151
+ end
152
+
153
+ def correlation(xs, ys)
154
+ xs, ys = pairs(xs, ys, "correlation")
155
+ sx = stdev(xs)
156
+ sy = stdev(ys)
157
+ if Scalar.zero?(sx) || Scalar.zero?(sy)
158
+ raise ArgumentError, "correlation: undefined when one of the two series is constant"
159
+ end
160
+ (covariance(xs, ys) / (sx * sy)).simplify
161
+ end
162
+
163
+ # Least squares line as an expression in var: a + b*var.
164
+ def linreg(xs, ys, var = :x)
165
+ xs, ys = pairs(xs, ys, "linreg")
166
+ x = Expression.lift(var)
167
+ raise ArgumentError, "linreg: the third argument names the indeterminate, got #{x}" unless x.is_a?(Var)
168
+ vx = variance(xs)
169
+ raise ArgumentError, "linreg: all x values are equal" if Scalar.zero?(vx)
170
+ b = (covariance(xs, ys) / vx).simplify
171
+ a = (mean(ys) - b * mean(xs)).simplify
172
+ (a + b * x).simplify
173
+ end
174
+ end
175
+ end