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,364 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RCAS
4
+ # OpenMath [OM19]: the abstract objects of the standard.
5
+ #
6
+ # An OpenMath object is not XML. XML is one of its encodings - the binary
7
+ # encoding and strict content MathML are others - so the classes below are
8
+ # the thing itself, and OpenMath::XML is one way of writing it down:
9
+ #
10
+ # om = RCAS.openmath(:x**2 + 1) # => arith1.plus(arith1.power(x, 2), 1)
11
+ # om.to_xml # => "<OMOBJ ...>...</OMOBJ>"
12
+ # RCAS.from_openmath(om.to_xml) # => x**2 + 1, unevaluated
13
+ #
14
+ # A class for each kind of object the standard defines: the six basic ones
15
+ # (Int, Double, Text, Bytes, ContentSymbol, Variable), the four compound
16
+ # ones (Application, Bind, Attribution, Error), the derived one (Foreign),
17
+ # and Reference, BVar, AttrPair and the wrapper Root, which the encodings
18
+ # need. Six of the standard's own words would shadow something inside this
19
+ # module - Integer, Float, String and Object are Ruby's, Binding is Ruby's
20
+ # too, and Symbol already means the indeterminate :x in rcas - so those are
21
+ # Int, Double, Text, Root, Bind and ContentSymbol.
22
+ #
23
+ # Note that OpenMath::Error is a *node*, not an exception: OME is the
24
+ # object you get back for a symbol the sender could not evaluate. The
25
+ # exceptions here are ParseError and EncodeError.
26
+ #
27
+ # Sources: the OpenMath 2.0 standard [OM19, ch. 2] for the objects, [OM19,
28
+ # ch. 4] for the XML encoding, the official content dictionaries for the
29
+ # phrasebook.
30
+ module OpenMath
31
+ # Where the official content dictionaries live, and a private CD of our
32
+ # own for the names rcas has and OpenMath has not (gamma, zeta, erf, the
33
+ # integral functions, RootOf). A cdbase is an identifier, not a URL that
34
+ # has to resolve.
35
+ CDBASE = "http://www.openmath.org/cd"
36
+ RCAS_CDBASE = "https://github.com/no-dashes/rcas/cd"
37
+ RCAS_CD = "rcas1"
38
+
39
+ OBJECT_VERSION = "2.0"
40
+
41
+ class ParseError < StandardError; end
42
+ class EncodeError < StandardError; end
43
+
44
+ # Common behaviour: frozen, structurally compared, walkable.
45
+ class Node
46
+ # An optional xml:id, carried so structure sharing survives a round
47
+ # trip. Never part of equality: two objects are equal when they say
48
+ # the same thing, whatever they are called.
49
+ attr_reader :id
50
+
51
+ def children = []
52
+ def leaf? = children.empty?
53
+
54
+ def each_node(&block)
55
+ return enum_for(:each_node) unless block
56
+ stack = [self]
57
+ until stack.empty?
58
+ node = stack.pop
59
+ yield node
60
+ stack.concat(node.children.reverse)
61
+ end
62
+ self
63
+ end
64
+
65
+ # A copy carrying an xml:id (the nodes themselves are frozen).
66
+ def identified(id)
67
+ copy = dup
68
+ copy.instance_variable_set(:@id, id&.to_s&.freeze)
69
+ copy.freeze
70
+ end
71
+
72
+ def ==(other) = other.class == self.class && other.state == state
73
+ alias eql? ==
74
+ def hash = [self.class, *state].hash
75
+
76
+ # An OpenMath object proper. Foreign content is a *derived* object in
77
+ # the standard's words, "not an OpenMath object", and may stand only
78
+ # as the value of an attribution or an argument of an error.
79
+ def object? = true
80
+
81
+ def to_xml(**opts) = XML.encode(self, **opts)
82
+
83
+ # The sugared notation a person would type; #to_s is the same notation
84
+ # with every symbol written out as cd.name.
85
+ def to_popcorn = Popcorn.render(self)
86
+ def to_s = Popcorn.render(self, sugar: false)
87
+
88
+ # The rcas object this stands for (see Phrasebook).
89
+ def to_expression = Phrasebook.to_expression(self)
90
+
91
+ def inspect = to_s
92
+
93
+ protected
94
+
95
+ # What equality and #hash look at; the children unless said otherwise.
96
+ def state = children
97
+ end
98
+
99
+ # OMI, OMF, OMSTR, OMB: an object that is just its value.
100
+ class Value < Node
101
+ attr_reader :value
102
+
103
+ def initialize(value)
104
+ @value = convert(value)
105
+ freeze
106
+ end
107
+
108
+ protected
109
+
110
+ def state = [value]
111
+ end
112
+
113
+ # OMI: an integer of any size.
114
+ class Int < Value
115
+ private def convert(v) = Integer(v)
116
+ end
117
+
118
+ # OMF: an IEEE 754 double.
119
+ class Double < Value
120
+ private def convert(v) = Float(v)
121
+ end
122
+
123
+ # OMSTR: a string of Unicode characters.
124
+ class Text < Value
125
+ private def convert(v) = v.to_s.dup.freeze
126
+ end
127
+
128
+ # OMB: a byte array.
129
+ class Bytes < Value
130
+ private def convert(v) = v.to_s.b.freeze
131
+ end
132
+
133
+ # OMV: a variable. rcas calls this an indeterminate.
134
+ class Variable < Node
135
+ attr_reader :name
136
+
137
+ def initialize(name)
138
+ @name = name.to_s.dup.freeze
139
+ freeze
140
+ end
141
+
142
+ protected
143
+
144
+ def state = [name]
145
+ end
146
+
147
+ # OMS: a symbol, i.e. the name of a mathematical object in a content
148
+ # dictionary. arith1.plus is the addition of the CD arith1, whatever the
149
+ # sender happens to call it.
150
+ class ContentSymbol < Node
151
+ attr_reader :cd, :name, :cdbase
152
+
153
+ def initialize(cd, name, cdbase: nil)
154
+ @cd = cd.to_s.dup.freeze
155
+ @name = name.to_s.dup.freeze
156
+ @cdbase = cdbase&.to_s&.dup&.freeze
157
+ freeze
158
+ end
159
+
160
+ def key = [cd, name]
161
+ def official? = cdbase.nil? || cdbase == CDBASE
162
+
163
+ protected
164
+
165
+ def state = [cd, name, cdbase]
166
+ end
167
+
168
+ # OMA: an application, head first.
169
+ class Application < Node
170
+ attr_reader :head, :args
171
+
172
+ def initialize(head, *args)
173
+ args = args.first if args.size == 1 && args.first.is_a?(Array)
174
+ @head = OpenMath.object!(head, "the head of an application")
175
+ @args = args.map { |a| OpenMath.object!(a, "an argument of an application") }.freeze
176
+ freeze
177
+ end
178
+
179
+ def children = [head, *args]
180
+ end
181
+
182
+ # OMBVAR: the bound variables of a binding.
183
+ class BVar < Node
184
+ attr_reader :vars
185
+
186
+ def initialize(*vars)
187
+ vars = vars.first if vars.size == 1 && vars.first.is_a?(Array)
188
+ @vars = vars.map { |v| v.is_a?(Node) ? v : Variable.new(v) }.freeze
189
+ freeze
190
+ end
191
+
192
+ def children = vars
193
+ def to_s = vars.join(", ")
194
+ end
195
+
196
+ # OMBIND: a binding - fns1.lambda[x -> x^2] and everything built on it.
197
+ class Bind < Node
198
+ attr_reader :binder, :bvar, :body
199
+
200
+ def initialize(binder, bvar, body)
201
+ @binder = OpenMath.object!(binder, "the binder of a binding")
202
+ @bvar = bvar.is_a?(BVar) ? bvar : BVar.new(bvar)
203
+ @body = OpenMath.object!(body, "the body of a binding")
204
+ freeze
205
+ end
206
+
207
+ def vars = bvar.vars
208
+ def children = [binder, bvar, body]
209
+ end
210
+
211
+ # One key/value pair of an attribution.
212
+ class AttrPair < Node
213
+ attr_reader :key, :value
214
+
215
+ def initialize(key, value)
216
+ @key = OpenMath.object!(key, "the key of an attribution")
217
+ @value = OpenMath.lift(value) # a value may be foreign
218
+ freeze
219
+ end
220
+
221
+ def children = [key, value]
222
+ def to_s = "#{key} -> #{value}"
223
+ end
224
+
225
+ # OMATTR: an object with attributes attached.
226
+ class Attribution < Node
227
+ attr_reader :pairs, :object
228
+
229
+ def initialize(pairs, object)
230
+ @pairs = pairs.map { |p| p.is_a?(AttrPair) ? p : AttrPair.new(*p) }.freeze
231
+ @object = OpenMath.lift(object)
232
+ freeze
233
+ end
234
+
235
+ def children = [*pairs, object]
236
+ end
237
+
238
+ # OME: an error object. Data, not an exception - it is what a sender
239
+ # hands back for a symbol it could not deal with.
240
+ class Error < Node
241
+ attr_reader :symbol, :args
242
+
243
+ def initialize(symbol, *args)
244
+ args = args.first if args.size == 1 && args.first.is_a?(Array)
245
+ @symbol = OpenMath.object!(symbol, "the symbol of an error")
246
+ @args = args.map { |a| OpenMath.lift(a) }.freeze # an argument may be foreign
247
+ freeze
248
+ end
249
+
250
+ def children = [symbol, *args]
251
+ end
252
+
253
+ # OMR: a reference to another object, by which a tree becomes a DAG.
254
+ class Reference < Node
255
+ attr_reader :href
256
+
257
+ def initialize(href)
258
+ @href = href.to_s.dup.freeze
259
+ freeze
260
+ end
261
+
262
+ protected
263
+
264
+ def state = [href]
265
+ end
266
+
267
+ # OMFOREIGN: data that is not OpenMath - a presentation-MathML or LaTeX
268
+ # rendering of the object it is attached to, an image, a comment. The
269
+ # content is carried exactly as it came, markup and all, and never
270
+ # interpreted. A derived object: legal as the value of an attribution
271
+ # or an argument of an error, nowhere else.
272
+ class Foreign < Node
273
+ attr_reader :content, :encoding
274
+
275
+ def initialize(content, encoding: nil)
276
+ @content = content.to_s.dup.freeze
277
+ @encoding = encoding&.to_s&.dup&.freeze
278
+ freeze
279
+ end
280
+
281
+ def object? = false
282
+
283
+ protected
284
+
285
+ def state = [content, encoding]
286
+ end
287
+
288
+ # OMOBJ: the wrapper that says "what follows is an OpenMath object".
289
+ # It carries the version and nothing mathematical, so it prints as its
290
+ # own contents.
291
+ class Root < Node
292
+ attr_reader :object, :version
293
+
294
+ def initialize(object, version: OBJECT_VERSION)
295
+ @object = OpenMath.object!(object, "the content of an OpenMath object")
296
+ @version = version.to_s.dup.freeze
297
+ freeze
298
+ end
299
+
300
+ def children = [object]
301
+ end
302
+
303
+ module_function
304
+
305
+ # Ruby values as OpenMath objects: 3, 2.5, "text", :x. Anything more
306
+ # mathematical than that goes through the Phrasebook.
307
+ def lift(obj)
308
+ case obj
309
+ when Node then obj
310
+ when Integer then Int.new(obj)
311
+ when Float then Double.new(obj)
312
+ when String then Text.new(obj)
313
+ when Symbol then Variable.new(obj)
314
+ else raise TypeError, "can't turn #{obj.class} into an OpenMath object"
315
+ end
316
+ end
317
+
318
+ # An OpenMath object where one is required; foreign content is not one.
319
+ def object!(obj, where)
320
+ node = lift(obj)
321
+ raise TypeError, "foreign content may not be #{where}" unless node.object?
322
+ node
323
+ end
324
+
325
+ # The symbol +name+ of the content dictionary +cd+: sym("arith1", "plus")
326
+ def sym(cd, name, cdbase: nil) = ContentSymbol.new(cd, name, cdbase: cdbase)
327
+
328
+ # A symbol of rcas's own content dictionary, for what OpenMath has no
329
+ # official name for.
330
+ def rcas_sym(name) = ContentSymbol.new(RCAS_CD, name, cdbase: RCAS_CDBASE)
331
+
332
+ # Replace every OMR by the object it points at, so the result is a tree.
333
+ # Unknown targets are left alone.
334
+ def dereference(node, table = nil)
335
+ table ||= node.each_node.filter_map { |n| [n.id, n] if n.id }.to_h
336
+ return node if table.empty?
337
+ expand(node, table, 0)
338
+ end
339
+
340
+ def expand(node, table, depth)
341
+ raise ParseError, "cyclic references" if depth > 64
342
+ if node.is_a?(Reference)
343
+ target = table[node.href.sub(/\A#/, "")]
344
+ return node unless target
345
+ return expand(target, table, depth + 1)
346
+ end
347
+ children = node.children.map { |c| expand(c, table, depth + 1) }
348
+ children == node.children ? node : rebuild(node, children)
349
+ end
350
+
351
+ def rebuild(node, children)
352
+ case node
353
+ when Root then Root.new(children.first, version: node.version)
354
+ when Application then Application.new(children.first, children[1..])
355
+ when BVar then BVar.new(children)
356
+ when Bind then Bind.new(children[0], children[1], children[2])
357
+ when AttrPair then AttrPair.new(children[0], children[1])
358
+ when Attribution then Attribution.new(children[0..-2], children.last)
359
+ when Error then Error.new(children.first, children[1..])
360
+ else node
361
+ end
362
+ end
363
+ end
364
+ end