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,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RCAS
4
+ # re, im, conj, arg.
5
+ #
6
+ # re(3 + 2*I) # => 3
7
+ # im((1 + I)**2) # => 2
8
+ # conj(x + I*y) # => x - i*y after assume(x: RR, y: RR)
9
+ # arg(-1), arg(1 + I) # => pi, pi/4
10
+ #
11
+ # The expression is expanded and each term split by its complex
12
+ # coefficient. A factor is taken as real only when its inferred domain says
13
+ # so (numbers, pi, variables assumed real); anything else stays inside an
14
+ # unevaluated re(...) or im(...), so re(x) is re(x) until x is declared real.
15
+ module ComplexParts
16
+ module_function
17
+
18
+ def re(e) = parts(Expression.lift(e)).first
19
+ def im(e) = parts(Expression.lift(e)).last
20
+
21
+ def conj(e)
22
+ real, imaginary = parts(Expression.lift(e))
23
+ (real - I * imaginary).simplify
24
+ end
25
+
26
+ def arg(e)
27
+ e = Expression.lift(e)
28
+ real, imaginary = parts(e)
29
+ if constant?(real) && constant?(imaginary)
30
+ # arg(0) has no value (A7)
31
+ return UNDEFINED if Decide.zero?(real) && Decide.zero?(imaginary)
32
+ exact = exact_angle(real, imaginary)
33
+ return exact if exact
34
+ # an exact number keeps its angle as a node rather than turning into
35
+ # a Float; a Float in, a Float out - from parts taken to enough
36
+ # digits: exp(pi*sqrt(163)) - 640320**3 - 744 + i is a hair left of
37
+ # the imaginary axis, and the Float real part said -480 (Q1)
38
+ return Fn.new(:arg, [e]) unless e.each_node.any? { |n| n.is_a?(Num) && (n.value.is_a?(Float) || (n.value.is_a?(Complex) && n.value.real.is_a?(Float))) }
39
+ x = precise_float(real)
40
+ y = precise_float(imaginary)
41
+ return Num.new(Math.atan2(y, x)) if x && y
42
+ end
43
+ Fn.new(:arg, [e])
44
+ end
45
+
46
+ def precise_float(e)
47
+ v = Precision.evalf(e, 20).to_f
48
+ v.finite? ? v : nil
49
+ rescue StandardError, NotImplementedError, RCAS::Unsupported => rescued
50
+ RCAS.guard!(rescued, refused: true)
51
+ v = e.evalf
52
+ v.is_a?(Numeric) && !v.is_a?(Complex) ? v.to_f : nil
53
+ end
54
+
55
+ # => [re, im]
56
+ def parts(e)
57
+ constant, table = Expand.table(e)
58
+ real = Num.new(real_of(constant))
59
+ imaginary = Num.new(imaginary_of(constant))
60
+ table.each do |factors, coeff|
61
+ term = Simplify.rebuild_product(1, factors)
62
+ a = Num.new(real_of(coeff))
63
+ b = Num.new(imaginary_of(coeff))
64
+ if real_valued?(term)
65
+ real += a * term
66
+ imaginary += b * term
67
+ elsif (known = known_parts(term))
68
+ tr, ti = known
69
+ real += a * tr - b * ti
70
+ imaginary += b * tr + a * ti
71
+ else
72
+ tr = Fn.new(:re, [term])
73
+ ti = Fn.new(:im, [term])
74
+ real += a * tr - b * ti
75
+ imaginary += b * tr + a * ti
76
+ end
77
+ end
78
+ [real.simplify, imaginary.simplify]
79
+ end
80
+
81
+ # The parts of a term that is real factors times one logarithm of a
82
+ # constant: log(u) = log|u| + i*arg(u), so im(log(-1)) is pi and
83
+ # -log(-1) is not a real point (third review, S14). nil otherwise.
84
+ def known_parts(term)
85
+ coeff, factors = Simplify.factorize(term)
86
+ real, other = factors.partition { |base, exp| real_valued?(Simplify.power_node(base, exp)) }
87
+ return nil unless other.size == 1
88
+ base, exp = other.first
89
+ return reciprocal_parts(coeff, real, base, exp) if reciprocal?(base, exp)
90
+ return nil unless exp == 1 && base.is_a?(Fn) && base.name == :log && base.args.size == 1 && constant?(base.args.first)
91
+ u = base.args.first
92
+ angle = arg(u)
93
+ return nil if angle.is_a?(Fn)
94
+ scale = Simplify.rebuild_product(coeff, real.to_h)
95
+ [(scale * Fn.new(:log, [Fn.new(:abs, [u]).simplify])).simplify, (scale * angle).simplify]
96
+ rescue StandardError => rescued
97
+ RCAS.guard!(rescued)
98
+ nil
99
+ end
100
+
101
+ # 1/w for a w whose own parts are known: conj(w)**n/|w|**(2*n). This is
102
+ # what splits the step of the family (-2)**x = 4 answers,
103
+ # 2*pi*i/(i*pi + log(2)), which domain: RR could not measure (the sixth
104
+ # review's preflight).
105
+ def reciprocal?(base, exp)
106
+ exp = exp.value if exp.is_a?(Num)
107
+ exp.is_a?(Integer) && exp.negative? && (base.is_a?(Add) || base.is_a?(Sub))
108
+ end
109
+
110
+ def reciprocal_parts(coeff, real, base, exp)
111
+ wr, wi = parts(base)
112
+ return nil unless [wr, wi].all? { |p| p.each_node.none? { |n| n.is_a?(Fn) && %i[re im].include?(n.name) } }
113
+ n = -(exp.is_a?(Num) ? exp.value : exp)
114
+ scale = Simplify.rebuild_product(coeff, real.to_h) / (wr**2 + wi**2)**n
115
+ cr, ci = parts(Expand.expand((wr - I * wi)**n))
116
+ [(scale * cr).simplify, (scale * ci).simplify]
117
+ end
118
+
119
+ def real_of(v) = v.is_a?(Complex) ? v.real : v
120
+ def imaginary_of(v) = v.is_a?(Complex) ? v.imaginary : 0
121
+
122
+ def real_valued?(term)
123
+ return false if term.each_node.any? { |n| n.is_a?(Num) && n.value.is_a?(Complex) }
124
+ d = Infer.domain(term)
125
+ !d.nil? && d <= RR
126
+ end
127
+
128
+ def constant?(e) = e.variables.empty? && e.each_node.none? { |n| n.is_a?(Fn) && %i[re im arg].include?(n.name) }
129
+
130
+ # 0, pi, +-pi/2 and the atan table, by quadrant; nil when not exact.
131
+ def exact_angle(real, imaginary)
132
+ if Scalar.zero?(imaginary)
133
+ return Num.new(0) if positive?(real)
134
+ return PI if negative?(real)
135
+ return nil
136
+ end
137
+ if Scalar.zero?(real)
138
+ return positive?(imaginary) ? (PI / 2).simplify : (-PI / 2).simplify
139
+ end
140
+ ratio = (imaginary / real).simplify
141
+ t = Functions.exact_value(:atan, ratio)
142
+ # atan is odd: atan(-sqrt(3)) = -atan(sqrt(3)), which the table knows
143
+ if t.nil? && negative?(ratio)
144
+ u = Functions.exact_value(:atan, Simplify.negate(ratio).simplify)
145
+ t = Simplify.negate(u).simplify if u
146
+ end
147
+ # no table value: atan of the ratio is still the exact angle, moved to
148
+ # the right half-plane's other side when the real part is negative
149
+ t ||= Fn.new(:atan, [ratio]) if positive?(real) || negative?(real)
150
+ return nil unless t
151
+ return t if positive?(real)
152
+ (positive?(imaginary) ? t + PI : t - PI).simplify
153
+ end
154
+
155
+ # The signs are decided (Decide), not read off a Float: a real part of
156
+ # -7.5e-13 computed from terms of size 10**17 has no Float sign (Q1).
157
+ def positive?(e) = Decide.sign(e) == :positive
158
+ def negative?(e) = Decide.sign(e) == :negative
159
+ end
160
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RCAS
4
+ # A named mathematical constant such as pi. `e` is exp(1) and `i` is the
5
+ # number Complex(0, 1), so both reuse existing machinery.
6
+ class Const < Expression
7
+ attr_reader :name, :value
8
+
9
+ def initialize(name, value)
10
+ @name = name.to_sym
11
+ @value = value
12
+ freeze
13
+ end
14
+
15
+ def ==(other) = other.is_a?(Const) && other.name == name
16
+ alias eql? ==
17
+ def hash = [Const, name].hash
18
+ def to_sexp = name
19
+ end
20
+
21
+ PI = Const.new(:pi, Math::PI)
22
+
23
+ # What 0*oo, oo - oo and oo/oo are. Infinity is an ordinary atom to the
24
+ # term tables, so without this it would cancel like any other factor and a
25
+ # divergent calculation would come back with a number. Anything that
26
+ # reaches UNDEFINED stays undefined.
27
+ UNDEFINED = Const.new(:undefined, Float::NAN)
28
+ E = Fn.new(:exp, [Num.new(1)])
29
+ I = Num.new(Complex(0, 1))
30
+
31
+ # `include RCAS::Constants` brings PI, E and I into scope (bin/rcas does).
32
+ module Constants
33
+ PI = RCAS::PI
34
+ E = RCAS::E
35
+ I = RCAS::I
36
+ UNDEFINED = RCAS::UNDEFINED
37
+ end
38
+
39
+ # OO (infinity) is defined in series.rb and added here once loaded.
40
+ def self.register_infinity = Constants.const_set(:OO, OO) unless Constants.const_defined?(:OO)
41
+
42
+ # Exact values of the trigonometric functions at rational multiples of pi.
43
+ module Trig
44
+ module_function
45
+
46
+ # arg == r * pi for a rational r? => r or nil
47
+ def pi_multiple(arg)
48
+ coeff, factors = Simplify.factorize(arg)
49
+ return nil unless factors == { PI => 1 }
50
+ coeff.is_a?(Integer) || coeff.is_a?(Rational) ? Rational(coeff) : nil
51
+ end
52
+
53
+ # arg == n*pi with n an integer-valued expression (an indeterminate
54
+ # assumed in ZZ, for instance)? => n or nil. This is what makes
55
+ # sin(k*pi) zero and cos(k*pi) (-1)**k, the two identities every
56
+ # Fourier coefficient needs.
57
+ def integer_pi_multiple(arg)
58
+ coeff, factors = Simplify.factorize(arg)
59
+ return nil unless factors[PI] == 1 && coeff.is_a?(Integer)
60
+ rest = factors.reject { |base, _| base == PI }
61
+ return nil if rest.empty?
62
+ n = Simplify.rebuild_product(coeff, rest)
63
+ domain = Infer.domain(n)
64
+ domain && domain <= ZZ ? n : nil
65
+ end
66
+
67
+ # arg == r * i * pi? => r or nil
68
+ def imaginary_pi_multiple(arg)
69
+ coeff, factors = Simplify.factorize(arg)
70
+ return nil unless factors == { PI => 1 } && coeff.is_a?(Complex) && coeff.real.zero?
71
+ im = coeff.imaginary
72
+ im.is_a?(Integer) || im.is_a?(Rational) ? Rational(im) : nil
73
+ end
74
+
75
+ # sin(r*pi) as an exact expression, or nil when not in the table.
76
+ def sin_pi(r)
77
+ r %= 2
78
+ return Simplify.negate(sin_pi(r - 1)) if r > 1 && (sin_pi(r - 1))
79
+ return nil if r > 1
80
+ r = 1 - r if r > Rational(1, 2)
81
+ case r
82
+ when 0 then Num.new(0)
83
+ when Rational(1, 6) then Num.new(Rational(1, 2))
84
+ when Rational(1, 4) then (RCAS.sqrt(2) / 2).simplify
85
+ when Rational(1, 3) then (RCAS.sqrt(3) / 2).simplify
86
+ when Rational(1, 2) then Num.new(1)
87
+ end
88
+ end
89
+
90
+ def cos_pi(r) = sin_pi(r + Rational(1, 2))
91
+
92
+ def tan_pi(r)
93
+ s = sin_pi(r)
94
+ c = cos_pi(r)
95
+ return nil if s.nil? || c.nil? || Scalar.zero?(c)
96
+ (s / c).simplify
97
+ end
98
+
99
+ # exp(r*i*pi) = cos(r*pi) + i*sin(r*pi)
100
+ def exp_i_pi(r)
101
+ c = cos_pi(r)
102
+ s = sin_pi(r)
103
+ return nil if c.nil? || s.nil?
104
+ (c + I * s).simplify
105
+ end
106
+
107
+ # Inverse table: value => asin(value) as a multiple of pi.
108
+ def asin_exact(arg)
109
+ table = {
110
+ Num.new(0) => Num.new(0),
111
+ Num.new(Rational(1, 2)) => PI / 6,
112
+ (RCAS.sqrt(2) / 2).simplify => PI / 4,
113
+ (RCAS.sqrt(3) / 2).simplify => PI / 3,
114
+ Num.new(1) => PI / 2
115
+ }
116
+ table[arg]
117
+ end
118
+
119
+ def atan_exact(arg)
120
+ table = {
121
+ Num.new(0) => Num.new(0),
122
+ Num.new(1) => PI / 4,
123
+ RCAS.sqrt(3).simplify => PI / 3,
124
+ (RCAS.sqrt(3) / 3).simplify => PI / 6
125
+ }
126
+ table[arg]
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Symbols double as indeterminates: `:x + 1` builds an expression.
4
+ class Symbol
5
+ def +(other) = RCAS::Var.new(self) + other
6
+ def -(other) = RCAS::Var.new(self) - other
7
+ def *(other) = RCAS::Var.new(self) * other
8
+ def /(other) = RCAS::Var.new(self) / other
9
+ def **(other) = RCAS::Var.new(self)**other
10
+ def -@ = -RCAS::Var.new(self)
11
+ def +@ = RCAS::Var.new(self)
12
+
13
+ # Lets Numeric operators accept a Symbol on the right: `1 - :x`.
14
+ def coerce(other) = [RCAS::Expression.lift(other), RCAS::Var.new(self)]
15
+
16
+ def to_expr = RCAS::Var.new(self)
17
+
18
+ # `x.in(ZZ)` declares the variable's domain; `x.in?(ZZ)` and `x.domain` query it.
19
+ def eq(other) = to_expr.eq(other)
20
+
21
+ # :x < 2 builds an inequality; symbol-to-symbol comparison keeps Ruby's meaning.
22
+ %i[< <= > >=].each do |op|
23
+ original = instance_method(op)
24
+ define_method(op) do |other|
25
+ other.is_a?(Numeric) || other.is_a?(RCAS::Expression) ? RCAS::Inequality.new(to_expr, op, other) : original.bind_call(self, other)
26
+ end
27
+ end
28
+ def in(domain) = to_expr.in(domain)
29
+ def in?(domain) = to_expr.in?(domain)
30
+ def domain = to_expr.domain
31
+ end
32
+
33
+ class Numeric
34
+ def to_expr = RCAS::Num.new(self)
35
+ end