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