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,551 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCAS
|
|
4
|
+
module OpenMath
|
|
5
|
+
# The phrasebook: the one place where rcas and OpenMath are told about
|
|
6
|
+
# each other. Nothing outside this file knows an OpenMath symbol, and no
|
|
7
|
+
# expression class carries a to_openmath of its own - the table below is
|
|
8
|
+
# the whole vocabulary, in both directions.
|
|
9
|
+
#
|
|
10
|
+
# One declaration, two indexes. Most rows are symmetric and say so in a
|
|
11
|
+
# line; the ones where the two systems disagree carry a pair of
|
|
12
|
+
# procedures instead. They disagree in three ways:
|
|
13
|
+
#
|
|
14
|
+
# - decoding is many to one. arith1.root(x, 2) and arith1.power(x, 1/2)
|
|
15
|
+
# are both Pow, transc1.ln and transc1.log both reach rcas's log.
|
|
16
|
+
# - OpenMath's operators are n-ary, rcas's are binary. Decoding folds
|
|
17
|
+
# left, encoding writes the tree it was given: construction never
|
|
18
|
+
# rewrites, so a + b + c stays the tree that was typed.
|
|
19
|
+
# - the interesting nodes are bindings, not applications. An integral,
|
|
20
|
+
# a sum, a derivative and a limit all carry fns1.lambda in OpenMath
|
|
21
|
+
# and a bound variable in rcas.
|
|
22
|
+
#
|
|
23
|
+
# Nothing is dropped in silence. A symbol with no row decodes to a held
|
|
24
|
+
# Fn named "cd.name" (an unknown function, exactly as u(n + 1) is), and
|
|
25
|
+
# an unknown function encodes back to the symbol it came from, so a
|
|
26
|
+
# document rcas does not understand survives the round trip. What rcas
|
|
27
|
+
# cannot express at all - a one-sided limit, a bare lambda - stays as
|
|
28
|
+
# such an Fn rather than being bent into the nearest node.
|
|
29
|
+
#
|
|
30
|
+
# Sources: the official content dictionaries [OM19]; arith1, nums1,
|
|
31
|
+
# transc1, relation1, logic1, fns1, calculus1, limit1, integer1,
|
|
32
|
+
# combinat1, complex1, rounding1, setname1, interval1, piece1, linalg2.
|
|
33
|
+
# The names rcas has and OpenMath has not (gamma, zeta, erf, Ei, Si, Ci,
|
|
34
|
+
# li, harmonic, sign, RootOf) are in rcas's own CD, RCAS_CD.
|
|
35
|
+
module Phrasebook
|
|
36
|
+
DECODE_APPLY = {} # ["cd", "name"] => ->(args, node) { rcas object or nil }
|
|
37
|
+
DECODE_CONST = {} # ["cd", "name"] => rcas object, for a symbol on its own
|
|
38
|
+
ENCODE_CLASS = {} # Expression subclass => ->(e) { OpenMath node }
|
|
39
|
+
ENCODE_FN = {} # rcas function name => ["cd", "name"]
|
|
40
|
+
ENCODE_CONST = {} # Const name => ["cd", "name"]
|
|
41
|
+
|
|
42
|
+
module_function
|
|
43
|
+
|
|
44
|
+
# ---- how a row is declared ------------------------------------------
|
|
45
|
+
|
|
46
|
+
# An n-ary OpenMath operator and the binary rcas node it becomes.
|
|
47
|
+
def operator(klass, cd, name)
|
|
48
|
+
DECODE_APPLY[[cd, name]] = ->(args, _) { args.map { |a| decode(a) }.reduce { |l, r| klass.new(l, r) } }
|
|
49
|
+
ENCODE_CLASS[klass] = ->(e) { Application.new(OpenMath.sym(cd, name), encode(e.left), encode(e.right)) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def unary(klass, cd, name)
|
|
53
|
+
DECODE_APPLY[[cd, name]] = ->(args, _) { klass.new(decode(args.first)) }
|
|
54
|
+
ENCODE_CLASS[klass] = ->(e) { Application.new(OpenMath.sym(cd, name), encode(e.arg)) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# A named function on both sides: sin, gamma, binomial, ...
|
|
58
|
+
def function(fn, cd, name)
|
|
59
|
+
DECODE_APPLY[[cd, name]] = ->(args, _) { Fn.new(fn, args.map { |a| decode(a) }) }
|
|
60
|
+
ENCODE_FN[fn] = [cd, name]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The same, in rcas's own content dictionary.
|
|
64
|
+
def rcas_function(fn, name = fn.to_s) = function(fn, RCAS_CD, name)
|
|
65
|
+
|
|
66
|
+
# A symbol standing on its own: nums1.pi, setname1.Z.
|
|
67
|
+
def constant(value, cd, name, encode_as: nil)
|
|
68
|
+
DECODE_CONST[[cd, name]] = value
|
|
69
|
+
ENCODE_CONST[encode_as] = [cd, name] if encode_as
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# A row the two systems do not see the same way.
|
|
73
|
+
def special(cd, name, decode: nil, encode: nil, klass: nil)
|
|
74
|
+
DECODE_APPLY[[cd, name]] = decode if decode
|
|
75
|
+
ENCODE_CLASS[klass] = encode if klass && encode
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# ---- the table -------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
operator Add, "arith1", "plus"
|
|
81
|
+
operator Sub, "arith1", "minus"
|
|
82
|
+
operator Mul, "arith1", "times"
|
|
83
|
+
operator Div, "arith1", "divide"
|
|
84
|
+
operator Pow, "arith1", "power"
|
|
85
|
+
unary Neg, "arith1", "unary_minus"
|
|
86
|
+
|
|
87
|
+
function :abs, "arith1", "abs"
|
|
88
|
+
function :gcd, "arith1", "gcd"
|
|
89
|
+
function :lcm, "arith1", "lcm"
|
|
90
|
+
|
|
91
|
+
function :exp, "transc1", "exp"
|
|
92
|
+
function :log, "transc1", "ln"
|
|
93
|
+
function :sin, "transc1", "sin"
|
|
94
|
+
function :cos, "transc1", "cos"
|
|
95
|
+
function :tan, "transc1", "tan"
|
|
96
|
+
function :sinh, "transc1", "sinh"
|
|
97
|
+
function :cosh, "transc1", "cosh"
|
|
98
|
+
function :tanh, "transc1", "tanh"
|
|
99
|
+
function :asin, "transc1", "arcsin"
|
|
100
|
+
function :acos, "transc1", "arccos"
|
|
101
|
+
function :atan, "transc1", "arctan"
|
|
102
|
+
|
|
103
|
+
function :factorial, "integer1", "factorial"
|
|
104
|
+
function :quo, "integer1", "quotient"
|
|
105
|
+
function :rem, "integer1", "remainder"
|
|
106
|
+
function :binomial, "combinat1", "binomial"
|
|
107
|
+
function :fibonacci, "combinat1", "Fibonacci"
|
|
108
|
+
|
|
109
|
+
function :floor, "rounding1", "floor"
|
|
110
|
+
function :ceil, "rounding1", "ceiling"
|
|
111
|
+
|
|
112
|
+
function :re, "complex1", "real"
|
|
113
|
+
function :im, "complex1", "imaginary"
|
|
114
|
+
function :conj, "complex1", "conjugate"
|
|
115
|
+
function :arg, "complex1", "argument"
|
|
116
|
+
|
|
117
|
+
# No official CD names these, so they are ours.
|
|
118
|
+
rcas_function :gamma
|
|
119
|
+
rcas_function :zeta
|
|
120
|
+
rcas_function :erf
|
|
121
|
+
rcas_function :erfc
|
|
122
|
+
rcas_function :sign
|
|
123
|
+
rcas_function :harmonic
|
|
124
|
+
rcas_function :Ei
|
|
125
|
+
rcas_function :Si
|
|
126
|
+
rcas_function :Ci
|
|
127
|
+
rcas_function :li
|
|
128
|
+
|
|
129
|
+
constant PI, "nums1", "pi", encode_as: :pi
|
|
130
|
+
constant OO, "nums1", "infinity", encode_as: :oo
|
|
131
|
+
constant E, "nums1", "e"
|
|
132
|
+
constant I, "nums1", "i"
|
|
133
|
+
# nums1.NaN is what an undefined value is called there; it decodes to
|
|
134
|
+
# rcas's own UNDEFINED, which is the node the arithmetic produces.
|
|
135
|
+
constant UNDEFINED, "nums1", "NaN", encode_as: :undefined
|
|
136
|
+
|
|
137
|
+
constant NN, "setname1", "N"
|
|
138
|
+
constant ZZ, "setname1", "Z"
|
|
139
|
+
constant QQ, "setname1", "Q"
|
|
140
|
+
constant RR, "setname1", "R"
|
|
141
|
+
constant CC, "setname1", "C"
|
|
142
|
+
|
|
143
|
+
# nums1.rational and complex1.complex_cartesian build a number rather
|
|
144
|
+
# than an operation; a Num goes back through #number, which picks the
|
|
145
|
+
# form the value calls for.
|
|
146
|
+
special "nums1", "rational", decode: lambda { |args, _|
|
|
147
|
+
p, q = args
|
|
148
|
+
return nil unless p.is_a?(Int) && q.is_a?(Int)
|
|
149
|
+
Num.new(Rational(p.value, q.value))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
special "complex1", "complex_cartesian", decode: lambda { |args, _|
|
|
153
|
+
re, im = args.map { |a| decode(a) }
|
|
154
|
+
return Num.new(Complex(re.value, im.value)) if re.is_a?(Num) && im.is_a?(Num)
|
|
155
|
+
Add.new(re, Mul.new(im, I))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# transc1.log is the logarithm to a base, which rcas writes as a
|
|
159
|
+
# quotient of natural logarithms.
|
|
160
|
+
special "transc1", "log", decode: lambda { |args, _|
|
|
161
|
+
base, x = args.map { |a| decode(a) }
|
|
162
|
+
Div.new(Fn.new(:log, [x]), Fn.new(:log, [base]))
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# arith1.root(x, n) is x**(1/n).
|
|
166
|
+
special "arith1", "root", decode: lambda { |args, _|
|
|
167
|
+
x, n = args
|
|
168
|
+
exponent = n.is_a?(Int) ? Num.new(Rational(1, n.value)) : Div.new(Num.new(1), decode(n))
|
|
169
|
+
Pow.new(decode(x), exponent)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# sum and product: a range and a lambda in OpenMath, a bound variable
|
|
173
|
+
# in rcas.
|
|
174
|
+
special "arith1", "sum", klass: Sum,
|
|
175
|
+
decode: ->(args, _) { range_binding(args) { |term, var, from, to| Sum.new(term, var, from, to) } },
|
|
176
|
+
encode: ->(e) { range_application("sum", e.from, e.to, e.var, e.term) }
|
|
177
|
+
|
|
178
|
+
special "arith1", "product", klass: Product,
|
|
179
|
+
decode: ->(args, _) { range_binding(args) { |term, var, from, to| Product.new(term, var, from, to) } },
|
|
180
|
+
encode: ->(e) { range_application("product", e.from, e.to, e.var, e.term) }
|
|
181
|
+
|
|
182
|
+
# calculus1: int and defint are one symbol in rcas, whose Integral
|
|
183
|
+
# node carries the bounds when it has them.
|
|
184
|
+
special "calculus1", "int", klass: Integral,
|
|
185
|
+
decode: lambda { |args, _|
|
|
186
|
+
pair = lambda_parts(args.first)
|
|
187
|
+
return nil unless pair
|
|
188
|
+
var, body = pair
|
|
189
|
+
Integral.new(body, var)
|
|
190
|
+
},
|
|
191
|
+
encode: lambda { |e|
|
|
192
|
+
return Application.new(OpenMath.sym("calculus1", "int"), lambda_of(e.var, e.integrand)) unless e.definite?
|
|
193
|
+
Application.new(OpenMath.sym("calculus1", "defint"),
|
|
194
|
+
Application.new(OpenMath.sym("interval1", "interval"), encode(e.from), encode(e.to)),
|
|
195
|
+
lambda_of(e.var, e.integrand))
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
special "calculus1", "defint", decode: lambda { |args, _|
|
|
199
|
+
range, fn = args
|
|
200
|
+
pair = interval_bounds(range)
|
|
201
|
+
return nil unless pair
|
|
202
|
+
from, to = pair
|
|
203
|
+
pair = lambda_parts(fn)
|
|
204
|
+
return nil unless pair
|
|
205
|
+
var, body = pair
|
|
206
|
+
Integral.new(body, var, from, to)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
special "calculus1", "diff", klass: Derivative,
|
|
210
|
+
decode: lambda { |args, _|
|
|
211
|
+
pair = lambda_parts(args.first)
|
|
212
|
+
return nil unless pair
|
|
213
|
+
var, body = pair
|
|
214
|
+
Derivative.new(body, var)
|
|
215
|
+
},
|
|
216
|
+
encode: lambda { |e|
|
|
217
|
+
return Application.new(OpenMath.sym("calculus1", "diff"), lambda_of(e.var, e.expr)) if e.order == 1
|
|
218
|
+
Application.new(OpenMath.sym("calculus1", "nthdiff"), Int.new(e.order), lambda_of(e.var, e.expr))
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
special "calculus1", "nthdiff", decode: lambda { |args, _|
|
|
222
|
+
order, fn = args
|
|
223
|
+
return nil unless order.is_a?(Int)
|
|
224
|
+
pair = lambda_parts(fn)
|
|
225
|
+
return nil unless pair
|
|
226
|
+
var, body = pair
|
|
227
|
+
Derivative.new(body, var, order.value)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# limit1.limit(x0, direction, lambda). rcas's Limit node has no
|
|
231
|
+
# direction, so only the two-sided limits become one; above and below
|
|
232
|
+
# stay as they came, held.
|
|
233
|
+
special "limit1", "limit", klass: Limit,
|
|
234
|
+
decode: lambda { |args, _|
|
|
235
|
+
point, direction, fn = args
|
|
236
|
+
return nil unless direction.is_a?(ContentSymbol) && %w[both_sides null].include?(direction.name)
|
|
237
|
+
pair = lambda_parts(fn)
|
|
238
|
+
return nil unless pair
|
|
239
|
+
var, body = pair
|
|
240
|
+
Limit.new(body, var, decode(point))
|
|
241
|
+
},
|
|
242
|
+
encode: lambda { |e|
|
|
243
|
+
Application.new(OpenMath.sym("limit1", "limit"), encode(e.point),
|
|
244
|
+
OpenMath.sym("limit1", "both_sides"), lambda_of(e.var, e.expr))
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# piece1.piece is (value, condition) - that way round.
|
|
248
|
+
special "piece1", "piecewise", klass: Piecewise,
|
|
249
|
+
decode: lambda { |args, _|
|
|
250
|
+
branches = args.map { |a| piece_branch(a) }
|
|
251
|
+
return nil if branches.any?(&:nil?)
|
|
252
|
+
Piecewise.new(branches)
|
|
253
|
+
},
|
|
254
|
+
encode: lambda { |e|
|
|
255
|
+
pieces = e.branches.map do |condition, value|
|
|
256
|
+
if condition == Piecewise::OTHERWISE
|
|
257
|
+
Application.new(OpenMath.sym("piece1", "otherwise"), encode(value))
|
|
258
|
+
else
|
|
259
|
+
Application.new(OpenMath.sym("piece1", "piece"), encode(value), encode(condition))
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
Application.new(OpenMath.sym("piece1", "piecewise"), pieces)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
# RootOf: the algebraic number that is the index-th root of a
|
|
266
|
+
# polynomial. No official CD names it.
|
|
267
|
+
special RCAS_CD, "root_of", klass: RootOf,
|
|
268
|
+
decode: lambda { |args, _|
|
|
269
|
+
poly, index = args
|
|
270
|
+
return nil unless index.is_a?(Int)
|
|
271
|
+
begin
|
|
272
|
+
RootOf.new(decode(poly).to_poly, index.value)
|
|
273
|
+
rescue StandardError => rescued
|
|
274
|
+
RCAS.guard!(rescued)
|
|
275
|
+
nil
|
|
276
|
+
end
|
|
277
|
+
},
|
|
278
|
+
encode: ->(e) { Application.new(OpenMath.rcas_sym("root_of"), encode(e.poly.to_expr), Int.new(e.index)) }
|
|
279
|
+
|
|
280
|
+
# The intervals, on their own rather than as the range of a sum.
|
|
281
|
+
{ "interval_oo" => [true, true], "interval_cc" => [false, false],
|
|
282
|
+
"interval_oc" => [true, false], "interval_co" => [false, true],
|
|
283
|
+
"interval" => [false, false], "integer_interval" => [false, false] }.each do |name, (left, right)|
|
|
284
|
+
DECODE_APPLY[["interval1", name]] = lambda { |args, _|
|
|
285
|
+
low, high = args.map { |a| decode(a) }
|
|
286
|
+
Interval.new(low, high, left_open: left, right_open: right)
|
|
287
|
+
}
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# The leaves and the two nodes that are not operations.
|
|
291
|
+
ENCODE_CLASS[Num] = ->(e) { number(e.value) }
|
|
292
|
+
ENCODE_CLASS[Var] = ->(e) { name_head(e.name) }
|
|
293
|
+
ENCODE_CLASS[Const] = lambda { |e|
|
|
294
|
+
cd, name = ENCODE_CONST[e.name]
|
|
295
|
+
raise EncodeError, "no OpenMath symbol for the constant #{e}" unless cd
|
|
296
|
+
OpenMath.sym(cd, name)
|
|
297
|
+
}
|
|
298
|
+
ENCODE_CLASS[Fn] = lambda { |e|
|
|
299
|
+
return Error.new(encode(e.args.first), e.args[1..].map { |a| encode(a) }) if e.name == :openmath_error
|
|
300
|
+
return lambda_of(e.args.first, e.args.last) if e.name == :lambda && e.args.size == 2 && e.args.first.is_a?(Var)
|
|
301
|
+
cd, name = ENCODE_FN[e.name]
|
|
302
|
+
head = cd ? OpenMath.sym(cd, name, cdbase: (RCAS_CDBASE if cd == RCAS_CD)) : name_head(e.name)
|
|
303
|
+
Application.new(head, e.args.map { |a| encode(a) })
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
# ---- encoding --------------------------------------------------------
|
|
307
|
+
|
|
308
|
+
# The OpenMath object for an rcas object, wrapped in its OMOBJ.
|
|
309
|
+
def to_openmath(obj) = Root.new(encode(obj))
|
|
310
|
+
|
|
311
|
+
def encode(obj)
|
|
312
|
+
case obj
|
|
313
|
+
when Node then obj
|
|
314
|
+
when Expression then encode_expression(obj)
|
|
315
|
+
when Equation then Application.new(OpenMath.sym("relation1", "eq"), encode(obj.lhs), encode(obj.rhs))
|
|
316
|
+
when Membership then Application.new(OpenMath.sym("set1", "in"), encode(obj.value), encode(obj.domain))
|
|
317
|
+
when ImageSet then encode_image_set(obj)
|
|
318
|
+
when Inequality then encode_inequality(obj)
|
|
319
|
+
when Interval then encode_interval(obj)
|
|
320
|
+
when NumberSet then encode_number_set(obj)
|
|
321
|
+
when Matrix then encode_matrix(obj)
|
|
322
|
+
when Vector then Application.new(OpenMath.sym("linalg2", "vector"), obj.entries.map { |v| encode(v) })
|
|
323
|
+
when Numeric then number(obj)
|
|
324
|
+
when Symbol then name_head(obj)
|
|
325
|
+
when Array then Application.new(OpenMath.sym("list1", "list"), obj.map { |v| encode(v) })
|
|
326
|
+
else raise EncodeError, "no OpenMath encoding for #{obj.class}"
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def encode_expression(e)
|
|
331
|
+
rule = ENCODE_CLASS[e.class]
|
|
332
|
+
raise EncodeError, "no OpenMath symbol for #{e.class} (#{e})" unless rule
|
|
333
|
+
rule.call(e)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
RELATIONS = { :< => "lt", :<= => "leq", :> => "gt", :>= => "geq", :!= => "neq" }.freeze
|
|
337
|
+
|
|
338
|
+
def encode_inequality(ineq)
|
|
339
|
+
name = RELATIONS.fetch(ineq.op) { raise EncodeError, "no OpenMath symbol for #{ineq.op}" }
|
|
340
|
+
Application.new(OpenMath.sym("relation1", name), encode(ineq.lhs), encode(ineq.rhs))
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def encode_interval(interval)
|
|
344
|
+
name = "interval_#{interval.left_open ? 'o' : 'c'}#{interval.right_open ? 'o' : 'c'}"
|
|
345
|
+
Application.new(OpenMath.sym("interval1", name), encode(interval.low), encode(interval.high))
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
SET_NAMES = { NN: "N", ZZ: "Z", QQ: "Q", RR: "R", CC: "C" }.freeze
|
|
349
|
+
|
|
350
|
+
def encode_number_set(set)
|
|
351
|
+
name = SET_NAMES.fetch(set.name.to_sym) { raise EncodeError, "no OpenMath symbol for #{set}" }
|
|
352
|
+
OpenMath.sym("setname1", name)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def encode_matrix(matrix)
|
|
356
|
+
rows = matrix.entries.map do |row|
|
|
357
|
+
Application.new(OpenMath.sym("linalg2", "matrixrow"), row.map { |v| encode(v) })
|
|
358
|
+
end
|
|
359
|
+
Application.new(OpenMath.sym("linalg2", "matrix"), rows)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# An indeterminate is an OMV; a name carrying a content dictionary
|
|
363
|
+
# ("arith3.foo", the way an unmapped symbol decodes) goes back to the
|
|
364
|
+
# symbol it came from.
|
|
365
|
+
def name_head(name)
|
|
366
|
+
text = name.to_s
|
|
367
|
+
return Variable.new(text) unless text.include?(".")
|
|
368
|
+
cd, symbol = text.split(".", 2)
|
|
369
|
+
OpenMath.sym(cd, symbol, cdbase: (RCAS_CDBASE if cd == RCAS_CD))
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Ruby numbers. Integers and floats are objects of their own in
|
|
373
|
+
# OpenMath; a rational is an application, and so is a complex number
|
|
374
|
+
# unless it is exactly i.
|
|
375
|
+
def number(value)
|
|
376
|
+
case value
|
|
377
|
+
when Integer then Int.new(value)
|
|
378
|
+
when Rational then Application.new(OpenMath.sym("nums1", "rational"), Int.new(value.numerator), Int.new(value.denominator))
|
|
379
|
+
when Float then Double.new(value)
|
|
380
|
+
when Complex then complex_number(value)
|
|
381
|
+
else
|
|
382
|
+
raise EncodeError, "no OpenMath encoding for #{value.class}" unless value.respond_to?(:to_r)
|
|
383
|
+
number(value.to_r)
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def complex_number(value)
|
|
388
|
+
return OpenMath.sym("nums1", "i") if value.real.zero? && value.imaginary == 1
|
|
389
|
+
Application.new(OpenMath.sym("complex1", "complex_cartesian"), number(value.real), number(value.imaginary))
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def range_application(name, from, to, var, term)
|
|
393
|
+
Application.new(OpenMath.sym("arith1", name),
|
|
394
|
+
Application.new(OpenMath.sym("interval1", "integer_interval"), encode(from), encode(to)),
|
|
395
|
+
lambda_of(var, term))
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def lambda_of(var, body)
|
|
399
|
+
Bind.new(OpenMath.sym("fns1", "lambda"), BVar.new(Variable.new(var.name)), encode(body))
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# ---- decoding --------------------------------------------------------
|
|
403
|
+
|
|
404
|
+
# The rcas object an OpenMath object stands for. Not always an
|
|
405
|
+
# Expression: relation1.eq is an Equation, interval1.interval_cc an
|
|
406
|
+
# Interval, setname1.Z a number set.
|
|
407
|
+
def to_expression(node)
|
|
408
|
+
node = OpenMath.dereference(node)
|
|
409
|
+
decode(node.is_a?(Root) ? node.object : node)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def decode(node)
|
|
413
|
+
case node
|
|
414
|
+
when Root then decode(node.object)
|
|
415
|
+
when Int, Double then Num.new(node.value)
|
|
416
|
+
when Variable then Var.new(node.name.to_sym)
|
|
417
|
+
when ContentSymbol then DECODE_CONST.fetch(node.key) { Var.new(:"#{node.cd}.#{node.name}") }
|
|
418
|
+
when Application then decode_application(node)
|
|
419
|
+
when Bind then decode_binding(node)
|
|
420
|
+
when Attribution then decode(node.object)
|
|
421
|
+
when Error then Fn.new(:openmath_error, [decode(node.symbol), *node.args.map { |a| decode(a) }])
|
|
422
|
+
when Reference then raise ParseError, "reference #{node.href} points nowhere"
|
|
423
|
+
when Text then raise ParseError, "no rcas expression for the string #{node.value.inspect}"
|
|
424
|
+
when Bytes then raise ParseError, "no rcas expression for a byte array"
|
|
425
|
+
else raise ParseError, "can't decode #{node.class}"
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def decode_application(node)
|
|
430
|
+
head = node.head
|
|
431
|
+
if head.is_a?(ContentSymbol) && (rule = DECODE_APPLY[head.key])
|
|
432
|
+
result = rule.call(node.args, node)
|
|
433
|
+
return result if result
|
|
434
|
+
end
|
|
435
|
+
Fn.new(unmapped_name(head), node.args.map { |a| decode(a) })
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# A binding rcas has no node for stays a held call, so re-encoding
|
|
439
|
+
# gives back what arrived.
|
|
440
|
+
def decode_binding(node)
|
|
441
|
+
var, body = lambda_parts(node)
|
|
442
|
+
return Fn.new(:lambda, [var, body]) if var
|
|
443
|
+
Fn.new(unmapped_name(node.binder), [*node.vars.map { |v| decode(v) }, decode(node.body)])
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def unmapped_name(head)
|
|
447
|
+
case head
|
|
448
|
+
when ContentSymbol then :"#{head.cd}.#{head.name}"
|
|
449
|
+
when Variable then head.name.to_sym
|
|
450
|
+
else raise ParseError, "can't apply #{head}"
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# fns1.lambda[x -> body] as [Var, expression]; nil for anything else.
|
|
455
|
+
def lambda_parts(node)
|
|
456
|
+
return nil unless node.is_a?(Bind) && node.binder.is_a?(ContentSymbol) && node.binder.key == %w[fns1 lambda]
|
|
457
|
+
return nil unless node.vars.size == 1 && node.vars.first.is_a?(Variable)
|
|
458
|
+
[Var.new(node.vars.first.name.to_sym), decode(node.body)]
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def interval_bounds(node)
|
|
462
|
+
return nil unless node.is_a?(Application) && node.head.is_a?(ContentSymbol)
|
|
463
|
+
return nil unless node.head.cd == "interval1" && node.args.size == 2
|
|
464
|
+
node.args.map { |a| decode(a) }
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# The shape shared by arith1.sum and arith1.product.
|
|
468
|
+
def range_binding(args)
|
|
469
|
+
range, fn = args
|
|
470
|
+
pair = interval_bounds(range)
|
|
471
|
+
return nil unless pair
|
|
472
|
+
from, to = pair
|
|
473
|
+
pair = lambda_parts(fn)
|
|
474
|
+
return nil unless pair
|
|
475
|
+
var, term = pair
|
|
476
|
+
yield term, var, from, to
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def piece_branch(node)
|
|
480
|
+
return nil unless node.is_a?(Application) && node.head.is_a?(ContentSymbol)
|
|
481
|
+
case node.head.key
|
|
482
|
+
when %w[piece1 piece]
|
|
483
|
+
value, condition = node.args
|
|
484
|
+
return nil unless value && condition
|
|
485
|
+
decoded = piece_condition(condition)
|
|
486
|
+
decoded ? [decoded, decode(value)] : nil
|
|
487
|
+
when %w[piece1 otherwise]
|
|
488
|
+
[Piecewise::OTHERWISE, decode(node.args.first)]
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def piece_condition(node)
|
|
493
|
+
return Piecewise::OTHERWISE if node.is_a?(ContentSymbol) && node.key == %w[logic1 true]
|
|
494
|
+
condition = decode_relation(node)
|
|
495
|
+
condition if condition.is_a?(Inequality) || condition.is_a?(Equation)
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
DECODE_RELATIONS = { "lt" => :<, "leq" => :<=, "gt" => :>, "geq" => :>=, "neq" => :!= }.freeze
|
|
499
|
+
|
|
500
|
+
def decode_relation(node)
|
|
501
|
+
return nil unless node.is_a?(Application) && node.head.is_a?(ContentSymbol) && node.head.cd == "relation1"
|
|
502
|
+
lhs, rhs = node.args.map { |a| decode(a) }
|
|
503
|
+
return Equation.new(lhs, rhs) if node.head.name == "eq"
|
|
504
|
+
op = DECODE_RELATIONS[node.head.name] or return nil
|
|
505
|
+
Inequality.new(lhs, op, rhs)
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
# relation1 builds an Equation or an Inequality, neither of which is
|
|
509
|
+
# an Expression, so it cannot go through the ordinary table.
|
|
510
|
+
%w[eq lt leq gt geq neq].each do |name|
|
|
511
|
+
DECODE_APPLY[["relation1", name]] = ->(args, node) { decode_relation(node) }
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# {2*pi*k | k in ZZ} is the image of a number set under a function,
|
|
515
|
+
# which is what set1.map says: map(lambda k . 2*pi*k, setname1.Z).
|
|
516
|
+
def encode_image_set(set)
|
|
517
|
+
unless set.parameters.size == 1
|
|
518
|
+
raise EncodeError, "no OpenMath encoding for a family with #{set.parameters.size} parameters"
|
|
519
|
+
end
|
|
520
|
+
Application.new(OpenMath.sym("set1", "map"), lambda_of(set.parameters.first, set.expr), encode(set.domain))
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
DECODE_APPLY[%w[set1 map]] = lambda { |args, _|
|
|
524
|
+
next nil unless args.size == 2
|
|
525
|
+
pair = lambda_parts(args.first)
|
|
526
|
+
domain = decode(args.last)
|
|
527
|
+
pair && domain.is_a?(NumberSet) ? ImageSet.new(pair.last, [pair.first], domain) : nil
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
# set1.in is a Membership, which is a statement and not an Expression
|
|
531
|
+
# either. The set comes back as the number set it names.
|
|
532
|
+
DECODE_APPLY[%w[set1 in]] = lambda { |args, _|
|
|
533
|
+
next nil unless args.size == 2
|
|
534
|
+
value, domain = args.map { |a| decode(a) }
|
|
535
|
+
domain.is_a?(Domain) ? Membership.new(value, domain) : nil
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
# linalg2: a matrix of matrixrows, and a vector.
|
|
539
|
+
DECODE_APPLY[%w[linalg2 matrix]] = lambda { |args, _|
|
|
540
|
+
rows = args.map do |row|
|
|
541
|
+
return nil unless row.is_a?(Application) && row.head.is_a?(ContentSymbol) && row.head.key == %w[linalg2 matrixrow]
|
|
542
|
+
row.args.map { |v| decode(v) }
|
|
543
|
+
end
|
|
544
|
+
RCAS.matrix(rows)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
DECODE_APPLY[%w[linalg2 vector]] = ->(args, _) { RCAS.vector(args.map { |v| decode(v) }) }
|
|
548
|
+
DECODE_APPLY[%w[list1 list]] = ->(args, _) { args.map { |v| decode(v) } }
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
end
|