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
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/`.
|