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
data/DESIGN.md ADDED
@@ -0,0 +1,783 @@
1
+ # rcas design notes
2
+
3
+ This document is for readers who know computer algebra and want to know
4
+ how rcas is built and why: what its objects mean, which rules the code
5
+ keeps, how it decides questions that cannot always be decided, and where
6
+ it stands next to Sage, SymPy, MuPAD and Maxima. The user manual is
7
+ [MANUAL.md](MANUAL.md); the list of what is not implemented is at the end
8
+ of [its reference section](MANUAL.md#2-reference), and the sources of the
9
+ algorithms are in [MANUAL.md, section 4](MANUAL.md#4-sources). Keys such
10
+ as `[Zei91]` below refer to that bibliography.
11
+
12
+ Contents
13
+
14
+ 1. [rcas at a glance](#1-rcas-at-a-glance)
15
+ 2. [Vocabulary](#2-vocabulary)
16
+ 3. [Invariants](#3-invariants)
17
+ 4. [Decision policies](#4-decision-policies)
18
+ 5. [Algorithms and rule chains](#5-algorithms-and-rule-chains)
19
+ 6. [Implementation notes and traps](#6-implementation-notes-and-traps)
20
+ 7. [Contributing a feature](#7-contributing-a-feature)
21
+ 8. [Layout](#8-layout)
22
+
23
+ ## 1. rcas at a glance
24
+
25
+ rcas is a computer algebra system written in Ruby (3.3 and later,
26
+ standard library only). It extends Ruby rather than defining a language:
27
+ Ruby symbols are the indeterminates, Ruby operators build expression
28
+ trees, and irb is the read-eval-print loop. There is no parser and no
29
+ evaluator of its own. The intended audience is school, high school and
30
+ undergraduate mathematics, and two things follow from that throughout:
31
+ answers are exact wherever possible (Integer, Rational, algebraic
32
+ numbers), and an answer rcas cannot justify is returned as an
33
+ unevaluated node or refused, never replaced by a plausible guess.
34
+
35
+ ### What `==` means
36
+
37
+ `==` is structural: two expressions are equal when they are the same
38
+ tree, and `eql?`/`hash` agree with it, so expressions work as Hash keys
39
+ and as `subs` patterns.
40
+
41
+ ```
42
+ rcas> x + 1 == 1 + x
43
+ => false
44
+ rcas> (x + 1).simplify == (1 + x).simplify
45
+ => true
46
+ ```
47
+
48
+ Mathematical equality is a question, not an operator: compare canonical
49
+ forms, or ask `Scalar.zero?(a - b)`, which may answer "undecided" (see
50
+ below). Inside `hold { }` the operator changes role: `==` builds an
51
+ `Equation`, `!=` an `Inequality` and `in?` a `Membership` - statements
52
+ about expressions, which are not themselves expressions. Outside `hold`,
53
+ equations are written `a.eq(b)` or passed to `solve` as expressions equal
54
+ to zero. This keeps `==` usable as Ruby expects (in `Array#include?`,
55
+ `Hash`, tests) at the price of one extra spelling for equations; Sage and
56
+ SymPy made the opposite choice (Sage) or the same one (SymPy, with `Eq`).
57
+
58
+ ### When rcas rewrites
59
+
60
+ Construction never rewrites. `(x + 1)*(1 - x)` is stored and printed as
61
+ written; rewriting happens only when asked, through `simplify`, `expand`,
62
+ `factor`, `cancel`, `diff` and the other operations.
63
+
64
+ ```
65
+ rcas> e = (x + 1)*(1 - x)
66
+ => (x + 1)*(1 - x)
67
+ rcas> e.simplify
68
+ => (1 + x)*(1 - x)
69
+ rcas> expand(e)
70
+ => 1 - x**2
71
+ ```
72
+
73
+ There is one exception: a *function applied to a constant argument* folds
74
+ when it is built, so `sin(PI/6)` is `1/2` and `sqrt(-4)` is `2*i`. The
75
+ reason is that Ruby folds `1 + 2` before rcas sees it, and a student
76
+ expects `sin(pi/6)` to behave like a number in the same way. Operator
77
+ expressions on constants (`I**2`) still wait for `simplify`.
78
+
79
+ Ruby's own folding is the one real trap of the design: `/` between two
80
+ Integers is integer division (the quotient rounded down, as `//` in
81
+ Python), so `1/2` is the Integer 0, and `2**(1/3r)` is a Float, before
82
+ any rcas code runs; `x + 1/3` is `x + 0`. Redefining `Integer#/` would
83
+ change it for every library in the process, and rcas has no preparser
84
+ (Sage's answer), so the literal has to say what it means. Output keeps
85
+ the mathematical spelling `x**(1/3)` for the stored Rational exponent,
86
+ which reads well but does not paste back: typed at the prompt it is
87
+ `x**0`, and the input warning is what catches it. The exact spellings are `1/3r`, `Rational(1, 3)`,
88
+ `root(2, 3)` and `cbrt`, and `hold { }` keeps the literal structure of a
89
+ block by reading its syntax tree instead of running it. Since the value
90
+ cannot be recovered afterwards, the front ends read each input line's
91
+ syntax tree before running it and warn about a division of two integer
92
+ literals that is not whole (`RCAS::Lint`).
93
+
94
+ ### The canonical form
95
+
96
+ `simplify` produces one canonical form. Sums are ordered by ascending
97
+ degree with the constant first (`1 + 2*x + x**2`, as Mathematica prints
98
+ them), ties broken graded-lexicographically; factors are ordered number,
99
+ constant, indeterminate, function, sum, with `i` first. Radicals of
100
+ positive integers keep an exponent in (0, 1) and move the integer part
101
+ into the coefficient (`1/sqrt(2)` is `2**(1/2)/2`), which is what lets
102
+ exact tables such as `atan`'s match. Internally the normal form is a term
103
+ table `{ {base => exponent} => coefficient }`; every module that needs
104
+ coefficients, atoms, numerators or denominators reads it rather than
105
+ walking trees. `simplify` does not expand products and does not apply
106
+ branch-dependent rules (`log(exp(x))` stays unless `x` is known to be
107
+ real; see [branch cuts](#branch-cuts)).
108
+
109
+ ### Domains
110
+
111
+ Domains are values: `NN ZZ QQ RR CC` (also `ℕ ℤ ℚ ℝ ℂ`), polynomial rings
112
+ `ZZ[x]`, fraction fields, `GF(p**n)`, algebraic fields `QQ(alpha)`,
113
+ vector spaces `QQ**3` and matrix spaces `QQ**[2, 3]`. A *value* answers
114
+ `domain` with the smallest domain rcas knows it to lie in; a *structure*
115
+ answers `base` with the domain its entries come from (`ZZ[x].base` is
116
+ `ZZ`). For an expression the domain is inferred (`Infer.domain`) and is
117
+ `nil` when unknown. Assumptions (`assume(x: ZZ)`, `assume(x > 0)`, the
118
+ block form `assume(...) { }` that restores both tables afterwards) record
119
+ a number set and a sign per indeterminate, and are used by `simplify`,
120
+ `Infer`, `solve` and a few rewriting rules.
121
+
122
+ A matrix or vector whose entries are in undeclared indeterminates lives
123
+ over the polynomial ring they generate, the way integer entries put it
124
+ over `ZZ`: `matrix([[a, b], [c, d]])` is in `ZZ[a, b, c, d]**[2, 2]`,
125
+ and `inverse` and `rref` pass to the fraction field when they need it.
126
+ A declared name is a scalar of its own domain instead. Entries that are
127
+ not rational functions (a radical, as in a generic eigenvalue, or
128
+ `sin(a)`) read the undeclared names as complex numbers, which is what an
129
+ undeclared name is to `Infer` anyway, and the matrix is over `CC`; the
130
+ constructor runs under that same temporary reading, so its membership
131
+ check agrees with the inference.
132
+
133
+ Membership is exact where possible and one-sided otherwise:
134
+ `Infer.excluded?(value, domain)` answers true only when the value is
135
+ *demonstrably* outside - exactly for a number, by the minimal polynomial
136
+ for an algebraic constant, by transcendence for a rational multiple of
137
+ `pi` or `e`. `log(2)` survives a declared `QQ` because rcas cannot prove
138
+ it irrational. A wrong answer kept is preferred to a right answer dropped.
139
+
140
+ ### Undecidable questions
141
+
142
+ Zero-equivalence of constants is undecidable in general, and rcas does
143
+ not pretend otherwise. There is one numeric decision procedure,
144
+ `Decide.sign`/`Decide.zero?`, and every part of the system that needs a
145
+ sign or a zero test goes through it. Its rule: **numerics prove "not
146
+ zero", never "zero".** A value is non-zero when an evaluation stands
147
+ clear of its error bound (a Float with a running error bound first, then
148
+ 30, 60 and 120 digits measured against the largest magnitude met in the
149
+ walk). A zero is proved only by a root separation bound for an algebraic
150
+ constant or by a normal form (simplification, expansion, cancellation,
151
+ the Pythagorean identity, logarithms of rationals over their primes).
152
+ Anything else is `nil`, undecided, and callers must treat it as such:
153
+ matrix elimination takes an undecided pivot as the generic non-zero case
154
+ and documents that; certified `evalf` refuses.
155
+
156
+ ```
157
+ rcas> evalf(atan(1/2r) + atan(1/3r) - PI/4, 30)
158
+ NoConvergence: evalf: atan(1/2) + atan(1/3) - pi/4 could not be certified
159
+ to 30 digits; ... (it is 0 to 660 digits, and rcas cannot prove it is
160
+ exactly 0)
161
+ ```
162
+
163
+ The same attitude shapes the answers of the symbolic algorithms. An
164
+ integral without an antiderivative rcas can find stays `integral(...)`;
165
+ a sum, limit or product likewise; a polynomial root without radicals is a
166
+ `RootOf`. A question rcas cannot settle raises `RCAS::Unsupported` naming
167
+ what is missing - "cannot" is never reported as "none".
168
+
169
+ ### Comparison with other systems
170
+
171
+ rcas is small next to all of these, and its closest relatives are clear.
172
+ Structurally it is closest to Sage: a host-language variable holds an
173
+ algebraic object, and domains are parents with elements (`ZZ[x]`,
174
+ `QQ**[2, 3]` follow Sage's `ZZ['x']`, `MatrixSpace`). Unlike Sage it has
175
+ no preparser, which is why Ruby's integer division is visible. From MuPAD
176
+ it takes `hold`/`eval`, domains as first-class values, the image-set
177
+ notation `{pi*k | k in ZZ}` for infinite solution sets, and `assume`.
178
+ From Maple and Mathematica it takes `surd` (Maple's name; Mathematica's
179
+ `Surd`), `RootOf`, and the scoped assumptions of `assuming`/`Assuming`.
180
+
181
+ | | rcas | Sage | SymPy | MuPAD | Maxima |
182
+ |---|---|---|---|---|---|
183
+ | language | Ruby, no parser of its own | Python with a preparser | Python library | own language | own language (Lisp underneath) |
184
+ | indeterminates | Ruby symbols `:x` (bare names in the REPL) | `var('x')` | `Symbol('x')` | identifiers | atoms |
185
+ | `==` on expressions | structural | builds a relation | structural (`Eq` builds one) | `=` builds an equation | `=` builds an equation |
186
+ | at construction | stored as written | automatic simplification | automatic canonicalization (`evaluate=False` to prevent it) | evaluation to full depth, `hold` to prevent it | general simplifier on, quote `'` to prevent evaluation |
187
+ | exact rationals from literals | `1/3r` (Ruby's `1/3` is 0) | yes, via the preparser | `Rational(1, 3)` (Python's `1/3` is a float) | yes | yes |
188
+ | domains | values; parents and elements | parents and elements | ring objects in `polys`, assumptions on symbols | `Dom::...` domains | `declare`, `assume` |
189
+ | `sin(x) = 0` | `{pi*k \| k in ZZ}` | one solution by default | `solveset` gives an image set | image set | one solution, with a warning |
190
+ | undecided zero | `nil`; refuse or generic case | heuristic | heuristic (`equals` may return `None`) | heuristic | may ask the user |
191
+
192
+ These entries describe default behaviour as far as it is documented;
193
+ details differ between versions of each system.
194
+
195
+ Where rcas is weaker, it is weaker by a lot. Its scope is an
196
+ undergraduate curriculum, not research: no complete Risch algorithm
197
+ (rational functions exactly, then heuristics), special functions only up
198
+ to `erf`, `Ei`, `Si`, `Ci` and `li`, limits by leading terms rather than
199
+ Gruntz's algorithm, Gröbner bases by Buchberger's algorithm without F4,
200
+ number fields with at most two generators, no ODEs with variable
201
+ coefficients beyond first order, inequalities only polynomial, rational
202
+ and with absolute values. The complete list is at the end of
203
+ [the reference section](MANUAL.md#2-reference). It is also slow in the
204
+ way interpreted exact arithmetic is slow: the manual's
205
+ [performance notes](MANUAL.md#115-performance-notes) give measured
206
+ numbers, and multivariate factorization by Kronecker substitution has
207
+ cliffs a system with Hensel lifting in several variables does not.
208
+
209
+ Where rcas takes a different position, it does so deliberately:
210
+
211
+ - **Exactness first.** A Float appears only when one was put in or
212
+ asked for (`evalf`, `nsolve`, `nintegrate`), and such answers are
213
+ labelled as numeric.
214
+ - **Decisions are proofs.** A sign on an interval, an inflection, the
215
+ real part of a family, a zero pivot: each is proved or left undecided,
216
+ never read off a few samples (see [section 4](#4-decision-policies)).
217
+ - **Complete answers by default.** `solve` returns every solution over
218
+ `CC`, families included; `principal: true` asks for one period and
219
+ `domain: RR` for the real ones.
220
+ - **Principal branches everywhere, with the real alternative named.**
221
+ `x**(1/3)` is the principal root, `surd(x, 3)` the real one.
222
+ - **No rewriting at construction**, which costs a `simplify` call and
223
+ buys expressions that print the way they were typed - useful when the
224
+ point is to watch a derivation.
225
+
226
+ ## 2. Vocabulary
227
+
228
+ The code and the manual use these words consistently.
229
+
230
+ - **Symbol**: the Ruby object `:x`. Any Ruby identifier qualifies,
231
+ Unicode included (`α`, `β₁`); `RCAS::IDENTIFIER` is the shared pattern.
232
+ `π` and `∞` are aliases of `pi` and `oo`.
233
+ - **Indeterminate**: the role a symbol plays inside an expression or a
234
+ ring (`x**2 - 1`, `ZZ[x]`). This is the word for the mathematics; not
235
+ "variable". The API method that returns them is still called
236
+ `Expression#variables`, following CAS convention.
237
+ - **Variable**: a Ruby binding (`e = (x + 1)*(1 - x)`). In `bin/rcas` a
238
+ bare undefined name evaluates to its symbol and is stored in a variable
239
+ of the same name.
240
+ - **Unknown function**: `u(n + 1)`, `f(x)`: a function node whose name is
241
+ not a built-in function. An undefined name applied to expressions or
242
+ numbers builds one, which is how `rsolve` reads recurrences.
243
+ - **Parameter**: an indeterminate that is not the one being solved,
244
+ integrated or summed for (`a` in `solve(x**2 - a >= 0, x)`).
245
+ - **Image set**: `{pi/6 + 2*pi*k | k in ZZ}`, the answer to an equation
246
+ with infinitely many solutions. It is a statement about expressions,
247
+ like `Equation`, `Inequality` and `Membership`, not an expression; it
248
+ carries the domain of its index, so that `sin` of a member folds to
249
+ `1/2` without any declaration.
250
+ - **domain and base**: a value's `domain` is the smallest domain it is
251
+ known to lie in (its ring, space or field; for an expression the
252
+ inferred number set, or `nil`). A structure's `base` is the domain of
253
+ its entries or coefficients. `space`, `ring` and `field` are the
254
+ precise accessors.
255
+ - **Double-struck sets**: `ℕ ℤ ℚ ℝ ℂ` are constants (Ruby reads them as
256
+ capitals), aliases of `NN ZZ QQ RR CC`. `RCAS.unicode = true` prints
257
+ them and `π`, `∞` in output; the default is ASCII.
258
+
259
+ ## 3. Invariants
260
+
261
+ These are the rules the code keeps; a change that breaks one breaks a
262
+ large part of the system.
263
+
264
+ 1. **Construction never rewrites.** Only the operations rewrite. The
265
+ exception is a function of a constant argument, which folds
266
+ (`Functions#sin` and friends do it; `Fn.new` does not).
267
+ 2. **`==`, `eql?` and `hash` are structural.** The hash is computed once
268
+ in the constructor, before the node is frozen, and combined by hand
269
+ and masked with `Expression::FIXNUM`: an unmasked `31*h1 + h2` grows
270
+ about five bits per level, and the hash of a 20000-term sum became a
271
+ bignum of 99000 bits.
272
+ 3. **One canonical form**, described [above](#the-canonical-form). Sums
273
+ longer than `Simplify::CHAIN` (32) are balanced trees of chains, and
274
+ `termize`/`factorize` are iterative with explicit stacks, so the
275
+ recursion depth stays logarithmic: a 20000-term sum simplifies in well
276
+ under a second, and the performance tests keep it so.
277
+ 4. **Term tables are the shared internal normal form.**
278
+ `Expand.table(e)` returns `[constant, { {base => exponent} => coeff }]`
279
+ and `Simplify.factorize` returns `[coeff, factors]`. Unknown leaf
280
+ classes (constants, integrals, `RootOf`) are atoms.
281
+ 5. **Everything numeric lives in `Num`**: Integer, Rational, Float,
282
+ Complex, finite-field elements (`Mod`, `GFElement`) and the
283
+ arbitrary-precision `Decimal`. A new numeric type is a `Numeric` with
284
+ a `printer_precedence` hook; `Simplify.normalize_number` and
285
+ `pow_number` must stay safe for all of them. `exp(u)` is stored in
286
+ factor tables as a power of one base, so exponentials merge.
287
+ 6. **Domains are values, and membership is exact where possible.**
288
+ `NumberSet#===` is membership, so `case domain when ZZ` is wrong -
289
+ compare with `==`. Matrix and vector spaces are domains: square
290
+ matrices over a ring form a ring, a vector space is neither, and a
291
+ space is not a scalar domain, so a polynomial ring over a matrix space
292
+ is refused.
293
+ 7. **Infinity is a value with arithmetic.** `oo` is a constant and an
294
+ atom in the term tables, so `rebuild_sum` and `rebuild_product` catch
295
+ it: `oo - oo`, `oo/oo` and `0*oo` are `undefined`; `2*oo`, `oo**2` and
296
+ `oo - 2` are `oo`; `1/oo` is 0; `1**oo` and `oo**0` are 1, as IEEE
297
+ `pow` has them. A symbolic coefficient is never absorbed (`x*oo`
298
+ stays), because `0*oo` is undefined.
299
+ 8. **Formal nodes** - `Integral`, `Sum`, `Product`, `Limit`,
300
+ `Derivative`, `RootOf`, `Piecewise` - are expressions and atoms to
301
+ everything else; `evaluate` (`doit`) computes them. A binder keeps the
302
+ shape body, bound variable, bounds (`children[0]`, `children[1]`,
303
+ rest); `variables`, `constant?` and `replace_with` skip bound
304
+ occurrences, and a substitution that would be captured renames the
305
+ bound variable. `Piecewise` carries conditions that are inequalities,
306
+ not expressions, and overrides `variables` and `replace_with` for them.
307
+ 9. **Zero of a matrix entry is decided, not assumed.** `Scalar.zero?` is
308
+ exact for numbers, then asks `Decide`; a non-constant entry is zero
309
+ when it is identically zero (one exact rational point first, then
310
+ expansion, cancellation, `trigsimp`). Symbolic pivots not shown to be
311
+ zero are the generic case, and a symbolic matrix of full rank at one
312
+ rational point has full rank.
313
+ 10. **Ruby folds before rcas sees anything**, so no code may assume a
314
+ literal arrived unevaluated.
315
+ 11. **Bare names are precious.** No bare `e` or `i` (they are common
316
+ names); the constants are `E`, `I`, `PI`, `OO` with bare `pi` and
317
+ `oo`. In the REPL Kernel's `p`, `pp`, `j`, `jj` are undefined so they
318
+ can be indeterminates. Symbol-to-symbol comparison keeps Ruby's
319
+ meaning; comparing a symbol with a number or an expression builds an
320
+ inequality.
321
+
322
+ ## 4. Decision policies
323
+
324
+ ### Settled design decisions
325
+
326
+ Each of these is what most comparable systems do, and MuPAD's choice
327
+ where they differ.
328
+
329
+ 1. **`**` is the principal root; `surd(x, n)` is the real one.**
330
+ `x**(1/n)` and `root(x, n)` are principal on every path, including
331
+ arbitrary precision; `surd(x, n)` is `-|x|**(1/n)` below 0 for odd n
332
+ and `undefined` there for even n; `cbrt` is `surd(x, 3)`. Hence
333
+ `real_domain(x**(1/3))` is `[0, oo)`, and `discuss` and `plot` of such
334
+ a power point at `surd`.
335
+ 2. **`real_domain` requires every subexpression to be real**, as
336
+ Mathematica's `FunctionDomain` does. A decided non-real constant
337
+ subexpression leaves the empty set. Whether the *value* is real is a
338
+ different question, `solve(im(f) == 0, x)`. Scattered domains are
339
+ answers (intervals plus or minus affine families: `x**x`, `(-2)**x`,
340
+ `gamma` of a linear argument).
341
+ 3. **`solve` is complete over `CC`.** `exp(u) = v` is
342
+ `log(v) + 2*pi*i*k`, and several exponentials go through a common
343
+ measure. `domain: RR` keeps the real members of a family whose step is
344
+ not real. Every internal caller that means the real line passes
345
+ `domain: RR`; callers that want one period pass `principal: true`.
346
+ 4. **Karr's convention** [Kar81] for reversed sums and products:
347
+ `sum(f, k, a, b)` is `-sum(f, k, b + 1, a - 1)` for `b < a - 1`, so
348
+ `sum(k, k, 5, 1)` is -9, and the telescoping identities hold for all
349
+ bounds.
350
+ 5. **Coordinates are named.** Vector calculus uses the given list, or the
351
+ free names when they are among `x, y, z`, or - for a field in names of
352
+ its own - as many names as components; anything else is an error
353
+ naming the extra symbol.
354
+ 6. **An antiderivative lists its special parameter values.** The public
355
+ `integrate` returns a `Piecewise` over the values where a denominator
356
+ free of x vanishes, each integrated again:
357
+ `integrate(cos(a*x), x)` is `piecewise(a.eq(0) => x, :else =>
358
+ sin(a*x)/a)`. `generic: true` gives the short form; internal callers
359
+ use the generic `Integrate.integrate`.
360
+ 7. **Log rules need proved positivity.** `expand_log` and `logcombine`
361
+ split or join only factors proved positive and take `force: true` for
362
+ the textbook manipulation. Callers whose results are verified
363
+ afterwards (the logarithmic equations in `solve`) pass it.
364
+ 8. **No domain claim where there is no value.** `1/x` for real x has no
365
+ inferred domain until `x != 0` is known (`Infer.nonzero?`); matrix and
366
+ vector constructors ask the weaker question, real where defined
367
+ (`Infer.where_defined { }`).
368
+
369
+ Printing and normal forms follow two smaller rules: an affine family over
370
+ `ZZ` is written with a positive step and, when possible, a base in
371
+ `[0, step)` (`Solve.normal_family`), so equal solution sets print alike;
372
+ and a negative number on the right of a sum prints with the other sign
373
+ (`x + 7/10` rather than `x - (-7/10)`), which changes printing only.
374
+
375
+ ### Proofs, not samples
376
+
377
+ The common failure of a CAS built from heuristics is an exact rewriting
378
+ justified by a few numeric samples. rcas's rule is that an exact
379
+ statement needs a proof; samples may *veto* a claim, never establish it.
380
+
381
+ - **`Decide` is the only numeric decision procedure.** No module keeps a
382
+ private tolerance. `nil` means undecided and must be handled as such.
383
+ - **The Float route carries an error bound** (`Decide.float_bound`):
384
+ exact inputs half an ulp, sums add absolute errors, products relative
385
+ ones, a function its slope over the interval times the argument's
386
+ error. Near a pole or jump of an evaluated function (`Decide::SINGULAR`)
387
+ there is no bound, and within the error nothing is concluded. A
388
+ constant whose Float evaluation cancelled is taken again in arbitrary
389
+ precision.
390
+ - **Certified digits.** `Precision.evalf` reports the digits two working
391
+ precisions agree on, raising the guard digits (`Precision::GUARDS`)
392
+ until that is what was asked for; a value that looks like 0 must be
393
+ proved 0 by `Decide`, or the call ends in `NoConvergence` saying how far
394
+ it got. An expression with a Float leaf has no exact value to prove,
395
+ and there 0 is an honest answer.
396
+ - **A sign on an interval** (`Analysis.sign_on_interval`) is proved: a
397
+ continuous function keeps its sign on an interval in which it has no
398
+ zero, so `solve` names all zeros (complete, families counted out), and
399
+ there must be no pole, jump or edge of the real domain inside. On a box
400
+ of several variables the sign comes from an interval enclosure
401
+ (`Analysis.enclosure`), or factor by factor. Radii of solids of
402
+ revolution, length elements and `abs` removal depend on this.
403
+ - **Inflections and extrema by order of vanishing**: derivatives are
404
+ taken until one is decided non-zero; odd order at an inflection
405
+ candidate means a sign change. An undecided case raises, and `discuss`
406
+ prints it as "not determined".
407
+ - **"Cannot" is never "none".** An equation that cannot be inverted
408
+ raises instead of returning `[]`; `nintegrate` never counts an
409
+ undefined sample as 0; a truncated search raises rather than reporting
410
+ what it found as complete. rcas's refusals are `RCAS::Unsupported`
411
+ (a `StandardError`); a broad rescue must not turn one into an empty
412
+ answer.
413
+ - **Poles are read off the equation as written**, before simplification:
414
+ `solve((x**2 - 1)/(x - 1), x)` does not return 1. Families and identity
415
+ sets are cut the same way.
416
+ - **Complete, not principal, where the answer is used as all of them**:
417
+ the poles inside a definite integral, the kinks of `abs`, the
418
+ breakpoints of a piecewise function, the rows of `discuss` for a
419
+ non-periodic function. A periodic function is discussed over one
420
+ period of *its own* period, computed from complete solutions.
421
+ - **Periodicity and conservativity are identities**, `f(x + T) = f(x)`
422
+ proved and a potential that is its own proof, not agreement at a few
423
+ points.
424
+
425
+ ### Branch cuts
426
+
427
+ Two rules that look like algebra are statements about branches and are
428
+ applied only where they hold:
429
+
430
+ - `exp(u)**v = exp(u*v)` for integer `v`, or real `u`. Otherwise
431
+ `sqrt(exp(2*pi*i))` would be `exp(pi*i) = -1` instead of 1.
432
+ - `log(exp(u)) = u` only on the principal strip `-pi < im(u) <= pi`.
433
+ A rational multiple of `pi` is compared as a rational; any other
434
+ imaginary part is admitted only when `|im(u)| <= 31/10`, which proves
435
+ it inside because `31/10 < pi`.
436
+
437
+ Both ask whether `u` is real by `Infer.domain(u) <= RR`, so an undeclared
438
+ indeterminate is *not* real and `assume(x: RR)` turns the rules on:
439
+
440
+ ```
441
+ rcas> log(exp(x)).simplify
442
+ => log(exp(x))
443
+ rcas> assume(x: RR) { log(exp(x)).simplify }
444
+ => x
445
+ ```
446
+
447
+ The real branches of `asin` and `acos` past `[-1, 1]` are values
448
+ (`acos(2.0)` is `-i*acosh(2)`, about `-1.317*i`), not errors, and
449
+ `cos(acos(u))` simplifies to `u` for every u while `acos(cos(u))`, which
450
+ is `u` only on `[0, pi]`, stays as it is.
451
+
452
+ ## 5. Algorithms and rule chains
453
+
454
+ Every non-trivial algorithm names its source in its module comment, with
455
+ the key used in [MANUAL.md, section 4](MANUAL.md#4-sources).
456
+
457
+ ### Indefinite integration
458
+
459
+ `Integrate.attempt` splits a sum into terms and pulls out constant
460
+ factors, then tries the rules in a fixed order, and the order carries
461
+ meaning:
462
+
463
+ ```
464
+ table -> trig_product -> IntegralFunctions.antiderivative -> piecewise
465
+ -> rational -> substitution -> by_parts -> Substitutions.radical
466
+ -> Substitutions.gaussian -> heurisch -> Substitutions.root_of_linear
467
+ -> Substitutions.root_of_ratio -> Substitutions.exponential
468
+ -> Substitutions.trigonometric -> shift
469
+ ```
470
+
471
+ - The named integrals (`Ei`, `Si`, `Ci`, `li`) come right after the
472
+ table, because `exp(u)/u` and friends have no elementary antiderivative
473
+ and the later rules would only find longer ways to fail.
474
+ - `piecewise` runs before `rational`, because an integrand with `abs` or
475
+ `sign` is not a rational function. It returns `sign(u)*(F - F(x0))`
476
+ with `x0` the root of the linear `u`; the constant `F(x0)` makes the
477
+ antiderivative continuous, and without it definite integrals across
478
+ `x0` would be wrong.
479
+ - `rational` is exact: Hermite reduction [Her72], [Mac75], then the
480
+ logarithmic part by Lazard-Rioboo-Trager [LR90], [Bro05], and as a
481
+ fallback the real quadratic factors of a biquadratic denominator.
482
+ - `by_parts` must not trade down: a `v` carrying `erf` is rejected when
483
+ `dv` carries none, or `x**2*exp(-x**2)` would recurse to the depth
484
+ limit instead of reaching the Gaussian moment rule.
485
+ - `heurisch` is the Risch-Norman parallel heuristic [NM77], [GS89].
486
+ - An integrand rcas cannot differentiate (`floor`, an unknown function)
487
+ stays formal; a rule that differentiates a subexpression must keep that
488
+ promise. A definite integral inside the integrand that depends on x
489
+ also leaves the integral formal, because every differentiating rule
490
+ would add a layer for ever.
491
+
492
+ ### Definite integrals
493
+
494
+ `Integrate.definite` is not `F(b) - F(a)`. The range is split at every
495
+ pole strictly inside (the denominators and their factors, the zeros of
496
+ `cos(u)` under a `tan`) and at every jump of the antiderivative (the
497
+ Weierstrass substitution puts `tan(x/2)` into it, which breaks where the
498
+ integrand is smooth). Each piece is evaluated with one-sided limits at
499
+ its interior ends, and `+oo` plus `-oo` is `undefined`. Families of
500
+ breakpoints are counted out between the bounds; when they cannot be
501
+ (more than `Integrate::MAX_BREAKS` = 128 for the whole range, a second
502
+ parameter, an unmeasurable step), the answer is "unknown", not "no
503
+ breaks". A pole whose zeros cannot be named but whose denominator changes
504
+ sign keeps the integral formal. When a piece comes out non-real, as
505
+ `log(cos(x))` past `pi/2` does, the pieces are taken again with
506
+ `log|u|` - but only where the integrand is proved real on the range.
507
+
508
+ ### Hypergeometric summation
509
+
510
+ The chain follows Koepf's book [Koe14] and [PWZ96]: **Gosper** [Gos78]
511
+ decides indefinite summation; **Zeilberger** [Zei91] runs Gosper on a
512
+ pencil `sum_j sigma_j F(n + j, k)` and gets a recurrence for a definite
513
+ sum; **Petkovsek** [Pet92] solves the recurrence in hypergeometric
514
+ terms; polynomial solutions with Abramov's degree bound are the common
515
+ subroutine. Each has a q-twin with `x = q**k` in place of `k` [Koo93],
516
+ [GR04].
517
+
518
+ - The sigmas stay linear. The pencil's term ratio has the shape
519
+ `r(k)*D(k)/D(k+1) * N(k+1)/N(k)` with `N` linear in the sigmas, so the
520
+ Gosper-Petkovsek normal form is sigma-free, and Gosper's equation is
521
+ linear in the unknown polynomial's coefficients and the sigmas
522
+ together: one null space gives both.
523
+ - That null space is computed by evaluation and interpolation
524
+ (`PolyMatrix.kernel`), because elimination over rational functions of
525
+ n swells beyond use. Row reduction over rational functions also needs
526
+ cancellation at every step: `Scalar.zero?` on an uncancelled
527
+ `q + q*(q - 1)/(1 - q)` does not see zero.
528
+ - The certificate is assembled in the polynomial ring with one gcd, not
529
+ as an expression to be cancelled (milliseconds against minutes).
530
+ - Creative telescoping proves an identity under the summation sign; the
531
+ natural boundaries are not automatic. `sumrecursion` checks the
532
+ recurrence against the sum for the first few n and refuses when the
533
+ residue is demonstrably non-zero.
534
+ - `sum` calls Zeilberger with order 1 only: a hypergeometric closed form
535
+ satisfies a first-order recurrence. `sumrecursion` goes to order 4.
536
+ - Petkovsek's terms start past the singularities of the ratio, so
537
+ `u(n + 1) = n*u(n)` gives `(n - 1)!`.
538
+
539
+ **Formal power series** follow Koepf [Koe92]: holonomic differential
540
+ equation by undetermined coefficients, the coefficient recurrence, the
541
+ two-term (hypergeometric) case, assembly. The ansatz splits each
542
+ derivative by monomials in the transcendental atoms and asks each to
543
+ cancel on its own, which is sufficient and never spurious. Denominators
544
+ are cleared by one lcm in the polynomial ring, null spaces go through
545
+ `PolyMatrix.kernel`, and the coefficient is offered as factorials, as
546
+ binomial coefficients and as a product, the first form that reproduces
547
+ the Taylor coefficients being kept. `fps` refuses series whose
548
+ coefficients are not hypergeometric (`tan`) rather than guess.
549
+
550
+ ### Polynomial factorization
551
+
552
+ Over `ZZ`: squarefree decomposition [Yun76], factorization modulo a
553
+ prime by Cantor-Zassenhaus [CZ81], Hensel lifting of the non-monic
554
+ polynomial (the monic transformation produced coefficients of hundreds
555
+ of digits), and recombination. Recombination is by subsets [Zas69] while
556
+ the number of subsets of the next size stays within
557
+ `VanHoeij::SUBSET_BUDGET` (50000), and then by lattice reduction on the
558
+ traces of `f*g'/g` [vHo02], [HvHN11], bounded through Fujiwara's root
559
+ bound [Fuj16], with the lifted factors handed over rather than lifted
560
+ again. A threshold on the *number* of local factors was measured and
561
+ rejected: products of small factors are faster by subsets at every size
562
+ tried, because the true factors appear among pairs and triples; what
563
+ makes subsets explode is the size of the subsets the true factors need,
564
+ as for products of translated Swinnerton-Dyer polynomials. Multivariate
565
+ polynomials go through Kronecker substitution [Knu98, §4.6.2], which does
566
+ not preserve squarefreeness, so the image is factored completely and
567
+ recombined by index. Over `QQ(alpha)` factoring is Trager's norm method
568
+ [Tra76].
569
+
570
+ ### Exact linear algebra
571
+
572
+ - **Products** of Integer or Rational matrices run on the bare numbers
573
+ after scaling rows and columns by their denominators (fifteen times
574
+ faster than entry-by-entry arithmetic on nodes). Strassen's algorithm
575
+ in Winograd's form [Str69], [Win71] is used from n = 192 for
576
+ machine-word entries (leaves of 48) and from n = 64 past 62 bits
577
+ (leaves of 16), never for symbolic entries, whose products expand.
578
+ - **Determinant, inverse and square systems** of Integer and Rational
579
+ matrices are computed modulo primes below `2**31` and recombined by
580
+ the Chinese remainder theorem, with the number of primes from
581
+ Hadamard's bound [Had93], so the answer is a proof. Solve and inverse
582
+ may stop early after rational reconstruction, accepted only if
583
+ `A*N = d*B` holds exactly - without the check a too-small modulus
584
+ reconstructs a wrong fraction. The determinant always takes the bound.
585
+ - **One square system** from n = 32 goes through Dixon's p-adic lifting
586
+ [Dix82]: the inverse modulo one prime, then lifting digit by digit,
587
+ reconstruction tried at steps 4, 8, 16, ...
588
+ - **Symbolic matrices** use evaluation and interpolation for
589
+ determinants and kernels (`PolyMatrix`), falling back to elimination.
590
+ - **LLL** is exact, in Cohen's formulation [Coh93, §2.6], [LLL82], with
591
+ the Gram-Schmidt data updated rather than recomputed.
592
+
593
+ ### Other parts worth knowing
594
+
595
+ - **Limits** go through Puiseux series with log terms, a squeeze rule
596
+ for bounded factors, and rewriting through `exp` and `log`; they are
597
+ the textbook strategy, not Gruntz's algorithm [Gru96].
598
+ - **Linear programs** are solved by the two-phase simplex method
599
+ [Dan63] with Bland's rule [Bla77] in exact rationals, integer programs
600
+ by branch and bound [LD60].
601
+ - **Float polynomial roots** of degree above 2 use Durand-Kerner
602
+ [Ker66], with clusters of nearby roots merged into one multiple root.
603
+ This is the one place thresholds are heuristic, because the input is a
604
+ Float and there is no exact answer to decide against; exact
605
+ coefficients never come here.
606
+ - **OpenMath** [OM19] is an object model with XML and POPCORN [HR09] as
607
+ two encodings of it; one phrasebook table maps both directions, and a
608
+ symbol with no row survives the round trip as a held function.
609
+
610
+ ## 6. Implementation notes and traps
611
+
612
+ These are mistakes that have been made in this code base and are easy to
613
+ make again.
614
+
615
+ **Ruby semantics**
616
+
617
+ - `1/2` is 0 and `2**(1/3r)` is a Float before rcas runs. Use `1/2r`,
618
+ `root`, `cbrt`, or `hold { }`.
619
+ - `NotImplementedError` is a `ScriptError`, not a `StandardError`; a
620
+ bare `rescue` does not catch it. rcas's refusals are
621
+ `RCAS::Unsupported < StandardError`. Inside `module Precision` a bare
622
+ `Unsupported` is `Precision::Unsupported`; write `RCAS::Unsupported`.
623
+ - `Math.log(-1.0)` and `Math.asin(2.0)` raise `Math::DomainError`; the
624
+ function layer computes the complex value instead.
625
+ - `Math.respond_to?(name)` is not a guard: including `RCAS::Functions`
626
+ into Object gives the `Math` module a `floor`, and folding recursed.
627
+ Compare against `Functions::MATH_NAMES`.
628
+ - `Hash#inspect` changed in Ruby 3.4 (`{x => 1, a: 2}`). Tests compare
629
+ hash output through `TestSupport.hash_style` on both sides.
630
+ - `filter_map` drops `false` as well as `nil`; a block that returns a
631
+ boolean sign loses the negative ones. Map to symbols.
632
+ - `return x if (x = ...)` is a NameError (the body is parsed first);
633
+ `@x ||=` fails on frozen objects; `a, b = f or return` is a syntax
634
+ error.
635
+ - `Integer#to_f` past `2**1024` is Infinity with a warning; check
636
+ `bit_length` first.
637
+ - A Rational accumulated term by term is slow where bignum gcds are
638
+ slow (`ChiSquare(10**4).cdf(10**4)` took a minute under one Ruby);
639
+ sum Integers over one common denominator.
640
+ - `RubyVM::AbstractSyntaxTree.of` does not work on a Prism-compiled
641
+ block (Ruby 3.4+ default). `hold` cuts the block's source out by the
642
+ instruction sequence's code location and re-parses it; a block without
643
+ readable source is refused, never run.
644
+
645
+ **Numerics**
646
+
647
+ - A sign test is never a product: `a*b > 0` underflows to 0 for two
648
+ values near `1e-165`. Compare signs (`Numerics.same_sign?`).
649
+ - Never write a private tolerance for a decision; ask `Decide`.
650
+ - `nintegrate` compares its bounds exactly: `10**20` and `10**20 + 1`
651
+ are the same Float, and a width of `10**-400` underflows.
652
+ - The tanh-sinh abscissa is measured from the near end, or the digits an
653
+ endpoint singularity needs cancel away; every `exp` in the doubly
654
+ exponential maps is guarded, because BigMath grinds for ever on
655
+ arguments like `10**160`.
656
+
657
+ **Data structures**
658
+
659
+ - Hash writes that should merge: on a factor table that may already hold
660
+ a base use `Simplify.add_factor`, never `factors[b] = e` (`i*i**(1/2)`
661
+ lost a factor that way).
662
+ - `Expand.table` returns a pair; destructure it.
663
+ - A module method named `hash` replaces `Module#hash` and breaks every
664
+ Hash keyed by the module (`LaTeX.table`, not `LaTeX.hash`).
665
+ - A solution keyed by indeterminates is an `Assignment`, so that
666
+ `sol[:x]` and `sol[x]` both work; a plain Hash keyed by nodes answers a
667
+ Symbol key with nil.
668
+ - `Polynomial#content` is positive; `.abs` on a Complex is a magnitude,
669
+ never a sign fix.
670
+ - A polynomial over `Frac(QQ[a])[x]` times a parameter expression must
671
+ not make `a` a new ring variable (`Polynomial#scalar_in_base`).
672
+
673
+ **Algorithms**
674
+
675
+ - `Polynomial#coeff(k)` takes an exponent vector. In a ring of several
676
+ variables the coefficient of a power of one of them is
677
+ `coefficient_in(var, k)`; reading `coeff(k)` there left a
678
+ characteristic polynomial in `QQ[x, _l]` with no eigenvalues.
679
+ - `cancel` treats a radical as an atom, so a zero that depends on
680
+ `B**(1/q)` for a non-constant `B` is invisible to it.
681
+ `Scalar.radical_zero?` replaces each radical by a new indeterminate `t`
682
+ and reduces the numerator modulo `t**q - B`; a zero remainder is a
683
+ proof, since every branch of the radical satisfies that equation.
684
+ - Hensel lifting needs a prime not dividing the leading coefficient.
685
+ - The primitive PRS loses resultant factors where a gcd degree jumps; the
686
+ Rothstein-Trager resultant is the Sylvester determinant.
687
+ - `Array#-` removes all duplicates; recombination works by index.
688
+ - `acos` is neither odd nor even (`acos(-u) = pi - acos(u)`).
689
+ - Folding a number back into a coefficient must skip the imaginary unit,
690
+ or `i` disappears into a Complex coefficient.
691
+ - `evalf` can come back symbolic when folding restores an exact constant
692
+ after floatification (`exp(-1.0)` is `1/e` again); it makes one more
693
+ pass and keeps it only if that ends in a number.
694
+ - `[from, *nil, to]` is `[from, to]`: a function that says "unknown" with
695
+ `nil` must be tested before its result is splatted.
696
+
697
+ **Tests**
698
+
699
+ - Minitest collects public methods only: a test appended after `private`
700
+ never runs. Check the run count when adding to a file with helpers at
701
+ the bottom.
702
+ - Never `include RCAS::Functions` in a test class: `Functions#diff`
703
+ overrides `Minitest::Assertions#diff`, and the first failing assertion
704
+ dies while formatting its message. Write `RCAS.sin(x)`.
705
+ - Tests that call `assume` must `forget` in a teardown, or use the block
706
+ form, which cannot leak.
707
+ - `assert_in_delta`'s third argument is the tolerance, not the message.
708
+ - The suite must run from a path containing a space, under a tty as well
709
+ as piped, and without warnings under `-w`.
710
+
711
+ ## 7. Contributing a feature
712
+
713
+ 1. The algorithm goes in its own module under `lib/rcas/`, in
714
+ `module_function` style, required from `lib/rcas.rb` after its
715
+ dependencies.
716
+ 2. The top-level function goes in `functions.rb` with a one-line doc
717
+ comment; `doc(name)` and the chat's `/help` show it, and the docs test
718
+ fails without it. Keyword forms follow the existing ones:
719
+ `f(expr, x: 0..1)`, `x: 0`, `n: 6`, an endless range for infinity.
720
+ 3. A new expression node class needs cases in the printer, `latex.rb`,
721
+ `Differentiate`, `Infer.domain`, `evalf`, `evaluate`, `Hold::FORMAL`
722
+ if it should stay formal inside `hold`, and a row in
723
+ `openmath/phrasebook.rb` - the OpenMath test enumerates every
724
+ expression class and fails without one.
725
+ 4. A new numeric type inside `Num` needs `Simplify.normalize_number`,
726
+ `pow_number`, the printer's precedence hook, `Scalar` and `Infer`.
727
+ 5. Tests: exact strings for representative outputs, plus a property check
728
+ wherever there is one - integrals are differentiated and compared
729
+ numerically, ODE solutions substituted, sums evaluated at small n, the
730
+ theorems of vector calculus checked against the integrals they relate.
731
+ 6. A manual section with transcripts, a row in the reference table, and
732
+ an entry in the "not implemented" list where the feature stops short.
733
+ 7. The source of the algorithm, cited in the module comment with a key
734
+ and listed in [MANUAL.md, section 4](MANUAL.md#4-sources).
735
+
736
+ A broad `rescue StandardError` in mathematical code calls
737
+ `RCAS.guard!(e)` first: under `RCAS_STRICT=1` (set by the Rake task) it
738
+ re-raises `NoMethodError` and `NameError`, so a bug cannot be mistaken for
739
+ "no answer", and it always passes on `RCAS::Unsupported`, so a refusal is
740
+ not turned into an empty result.
741
+
742
+ **Running the tests.** `ruby -S rake` runs the whole suite;
743
+ `ruby -Ilib -Itest test/solve_test.rb` runs one file;
744
+ `ruby -S rake TESTOPTS="--seed=1"` fixes the order. **MANUAL.md is
745
+ executable**: `test/manual_test.rb` runs every `rcas> ` line of it in a
746
+ session that behaves like `bin/rcas` and compares the `inspect` output
747
+ with the `=> ` lines (continuation lines indented three spaces). When an
748
+ output format changes, the code or the manual is changed deliberately;
749
+ the comparison is never loosened. Locals persist across all code blocks
750
+ of the manual, so new transcripts use fresh names, and transcripts that
751
+ use random objects seed at the top of their own block.
752
+ `ruby -S rake toc` regenerates the manual's table of contents.
753
+
754
+ ## 8. Layout
755
+
756
+ | file | contents |
757
+ |---|---|
758
+ | `expression.rb` | the tree: `Var Num Const Neg Add Sub Mul Div Pow Fn`; lift, subs, evalf, evaluate |
759
+ | `printer.rb`, `latex.rb` | text with minimal parentheses (valid Ruby apart from bare names); LaTeX |
760
+ | `simplify.rb`, `expand.rb` | canonical form and term tables; distribution with like-term merging |
761
+ | `decide.rb`, `scalar.rb` | the numeric decision procedure; entry arithmetic and zero tests |
762
+ | `precision.rb` | arbitrary-precision evaluation over BigDecimal, certified digits, tanh-sinh quadrature |
763
+ | `domains.rb` | number sets, assumptions, `Infer`, polynomial rings, fraction fields |
764
+ | `polynomial.rb`, `gcd.rb`, `factor.rb`, `van_hoeij.rb` | ring elements, gcd by PRS, factorization and recombination |
765
+ | `fraction.rb`, `rational_function.rb` | rational normal form, partial fractions |
766
+ | `algebraic.rb`, `finite_field.rb`, `number_theory.rb` | algebraic numbers and fields, `GF(p**n)`, integers |
767
+ | `groebner.rb`, `lattice.rb` | Buchberger's algorithm; LLL |
768
+ | `differentiate.rb`, `integrate.rb`, `integrate_substitutions.rb`, `integral_functions.rb` | derivatives; the integration rule chain; `Ei Si Ci li` |
769
+ | `series.rb`, `fps.rb`, `fourier.rb` | Puiseux series and limits; formal power series; Fourier series |
770
+ | `summation.rb`, `zeilberger.rb`, `petkovsek.rb`, `poly_recurrence.rb`, `product.rb` | sums, creative telescoping, recurrences, products |
771
+ | `q_functions.rb`, `q_summation.rb`, `q_zeilberger.rb`, `q_difference.rb` | the q-analogues |
772
+ | `solve.rb`, `inequalities.rb`, `piecewise.rb` | equations and systems, inequalities and real sets, piecewise functions |
773
+ | `analysis.rb`, `discussion.rb`, `vector_calculus.rb` | real analysis, curve sketching, line and surface integrals |
774
+ | `ode.rb`, `recurrence.rb`, `laplace.rb` | differential equations, recurrences, the Laplace transform |
775
+ | `matrix.rb`, `vector.rb`, `matrix_multiply.rb`, `multimodular.rb`, `dixon.rb`, `poly_matrix.rb`, `decompositions.rb`, `linear_algebra.rb` | linear algebra |
776
+ | `linear_program.rb`, `geometry.rb`, `statistics.rb`, `distributions.rb`, `hypothesis.rb` | optimization, plane geometry, statistics |
777
+ | `hold.rb`, `steps.rb`, `docs.rb`, `background.rb` | `hold`, worked solutions, documentation and its background texts |
778
+ | `plot.rb`, `plot3d.rb`, `render.rb` | text and picture plots, typesetting |
779
+ | `openmath.rb`, `openmath/` | OpenMath objects, XML, POPCORN, the phrasebook |
780
+ | `functions.rb`, `core_ext.rb`, `constants.rb` | the top-level functions, operators on Symbol and Numeric, constants |
781
+ | `irb.rb`, `results.rb`, `chat.rb`, `chat/`, `app.rb`, `app/` | the three front ends: irb, the terminal chat, the window |
782
+
783
+ All paths are under `lib/rcas/`.