lakutata 3.1.1 → 3.1.3
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.
- package/README.md +3 -2
- package/README.zh-CN.md +1 -1
- package/doc/en/Command.md +3 -0
- package/doc/en/FFI.md +458 -0
- package/doc/en/Math.md +394 -0
- package/doc/en/NativeAddons.md +7 -1
- package/doc/en/Overview.md +5 -1
- package/doc/en/UpgradingFrom2.md +4 -2
- package/doc/zh/Command.md +2 -0
- package/doc/zh/FFI.md +337 -0
- package/doc/zh/Math.md +312 -0
- package/doc/zh/NativeAddons.md +2 -1
- package/doc/zh/Overview.md +3 -1
- package/doc/zh/UpgradingFrom2.md +1 -1
- package/package.json +2 -2
package/doc/en/Math.md
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
# Lakutata Math
|
|
2
|
+
|
|
3
|
+
The package `@lakutata/math` does mathematics in Node.js, both symbolic and numeric: algebra and calculus on formulas,
|
|
4
|
+
equations and inequalities, decimals of any precision, matrices, statistics and probability distributions, numeric
|
|
5
|
+
solutions, units, number theory. It is not part of `lakutata`, and it needs no application: its functions are imported
|
|
6
|
+
and called anywhere. What it is like:
|
|
7
|
+
|
|
8
|
+
- **Formulas are written as on paper**: `x^2 - 5x + 6`, `2(x + 1)`, `5!`, `sin(pi/6)`. A result is written the same
|
|
9
|
+
way, so it can be read back, and in TeX for display.
|
|
10
|
+
- **Results are exact**: `0.1 + 0.2` is `3/10`, `sqrt(8)` is `2sqrt(2)`, the roots of `x^2 - 2` are `-sqrt(2)` and
|
|
11
|
+
`sqrt(2)`. A number is computed only when one is asked for: a double, or as many correct decimal digits as wanted.
|
|
12
|
+
- **Symbolic and numeric in one package**: when an operation has no exact answer (an integral, the roots of a polynomial
|
|
13
|
+
of degree five), the numeric methods take the same formula.
|
|
14
|
+
- **A native addon** does the work, built on SymEngine, Boost, Eigen and mpdecimal: it is compiled code, so it is fast.
|
|
15
|
+
The long operations run in its own threads, with a time limit and a cancellation, so the event loop stays free.
|
|
16
|
+
|
|
17
|
+
The README of `@lakutata/math` describes every function and its options; this guide goes through what the package
|
|
18
|
+
computes, with an example of each kind.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @lakutata/math
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The addon is compiled when the package is installed, so the machine needs Python 3 and a C++17 compiler (the README
|
|
25
|
+
lists them for each system); it takes from about half a minute to a few minutes. The package manager has to run the
|
|
26
|
+
install script of the package: npm runs it, and may warn that the script is "not yet covered by allowScripts"
|
|
27
|
+
(`npm install-scripts approve @lakutata/math` records that you allow it); Bun and pnpm run it only for the packages they
|
|
28
|
+
are told to trust (`trustedDependencies` in the package.json for Bun, `pnpm approve-builds` for pnpm). Without the
|
|
29
|
+
addon, loading the package throws an error that tells how to build it.
|
|
30
|
+
|
|
31
|
+
## 1. Formulas and results
|
|
32
|
+
|
|
33
|
+
`expression()` reads a formula. A number next to a name or a parenthesis multiplies it (`2x`, `3(x + 1)`), `^` is a
|
|
34
|
+
power, and the constants (`pi`, `e`, `i`, `inf`) and the functions (`sin`, `ln`, `sqrt`, `gamma`, `erf`, `zeta`…) have
|
|
35
|
+
their usual names; the README lists them all. An expression does not change: each operation gives a new one.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import {createMath, expression} from '@lakutata/math'
|
|
39
|
+
|
|
40
|
+
console.log(expression('0.1 + 0.2').toString()) //3/10
|
|
41
|
+
console.log(expression('sqrt(8) + sin(pi/6)').toString()) //2sqrt(2) + 1/2
|
|
42
|
+
console.log(expression('3(x + 1)^2 - x').expand().toString()) //3x^2 + 5x + 3
|
|
43
|
+
console.log(expression('e^(-x^2/2)/sqrt(2pi)').toTeX()) //\frac{e^{-\frac{x^{2}}{2}} \sqrt{2}}{2 \sqrt{\pi}}
|
|
44
|
+
console.log(expression('cos(10^30)').toNumber(), Math.cos(1e30)) //-0.9959311944053957 -0.9999564608959665
|
|
45
|
+
console.log(expression('pi').toDecimal(50)) //3.1415926535897932384626433832795028841971693993751
|
|
46
|
+
const finance = createMath({definitions: {'rate': '0.13', 'net(x)': 'x/(1 + rate)'}})
|
|
47
|
+
console.log(finance.expression('net(113)').toString()) //100
|
|
48
|
+
console.log(finance.expression('p - net(p)').diff('p').toString()) //13/113
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- `toString()` gives a formula that `expression()` reads back, `toTeX()` its TeX (for KaTeX or MathJax).
|
|
52
|
+
- `toNumber()` gives the nearest double. It is computed from the decimals of the exact value, so all its digits are
|
|
53
|
+
right, even where doubles have none: as a double, `1e30` is not exactly 10^30, and `Math.cos(1e30)` is the cosine of
|
|
54
|
+
another number. `toDecimal(digits)` gives that many correct significant digits, as a string.
|
|
55
|
+
- `substitute({x: 2})` puts values in for the variables; the result stays exact.
|
|
56
|
+
- `createMath({definitions})` makes a scope of functions and constants written as formulas, with the same functions
|
|
57
|
+
as the package. A formula sees through its definitions: derivatives and equations use them. The functions the package
|
|
58
|
+
exports belong to `math`, a scope with no definitions.
|
|
59
|
+
|
|
60
|
+
## 2. Algebra and calculus
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import {expression} from '@lakutata/math'
|
|
64
|
+
|
|
65
|
+
console.log(expression('x^6 - y^6').factor().toString()) //(x^2 - x*y + y^2)(x + y)(x - y)(x^2 + x*y + y^2)
|
|
66
|
+
console.log(expression('(x^2 - 1)/(x^2 + 2x + 1)').cancel().toString()) //(x - 1)/(x + 1)
|
|
67
|
+
console.log(expression('sin(x)^4 - cos(x)^4').simplify().toString()) //-cos(2x)
|
|
68
|
+
console.log(expression('x^2*sin(x)').diff('x').toString()) //2x*sin(x) + x^2*cos(x)
|
|
69
|
+
console.log(expression('x*e^x').integrate('x').toString()) //e^x*(x - 1)
|
|
70
|
+
console.log(expression('1/(x^2 + 1)').integrate('x', '-inf', 'inf').toString()) //pi
|
|
71
|
+
console.log(expression('(1 + a/x)^x').limit('x', 'inf').toString()) //e^a
|
|
72
|
+
console.log(expression('e^x').series('x', 0, 4).toString()) //x^3/6 + x^2/2 + x + 1
|
|
73
|
+
console.log(expression('sum(k^2, k, 1, n)').toString()) //n^3/3 + n^2/2 + n/6
|
|
74
|
+
console.log(expression('t^2*e^(-3t)').laplace('t', 's').toString()) //2/(s + 3)^3
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- **Polynomials and fractions**: `expand`, `factor` (over the rationals, in any number of variables), `cancel`,
|
|
78
|
+
`together`, `apart` (partial fractions) and `completeSquare`.
|
|
79
|
+
- **Simplification**: `simplify()` looks for a shorter form, by identities that hold for every value of the variables
|
|
80
|
+
(trigonometric, hyperbolic, exponential, nested roots); nothing that depends on the signs of the variables, so
|
|
81
|
+
`sqrt(x^2)` stays as it is.
|
|
82
|
+
- **Integrals**: by partial fractions, a table, substitution, integration by parts, and the special functions of
|
|
83
|
+
integrals (`sin(x)/x` gives `Si(x)`). Each antiderivative is checked by its derivative before it is returned. A
|
|
84
|
+
definite integral is split at the poles inside its interval; it is `inf` where it grows without bound.
|
|
85
|
+
- **Limits** by Gruntz's algorithm, from both sides or from one, at a point or at infinity, with the indeterminate
|
|
86
|
+
forms (`0/0`, `1^inf`, `inf - inf`…).
|
|
87
|
+
- **Series** (`series(variable, point, order)`), **sums and products** in closed form (`sum(1/k^2, k, 1, inf)` is
|
|
88
|
+
`pi^2/6`), **piecewise** expressions (`piecewise(x^2, x < 0, x)`), and the **Laplace transform** and its inverse.
|
|
89
|
+
|
|
90
|
+
Most of these operations have an asynchronous version (section 11).
|
|
91
|
+
|
|
92
|
+
## 3. Equations and inequalities
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import {equation, type Inequality, parse, solve} from '@lakutata/math'
|
|
96
|
+
|
|
97
|
+
console.log(equation('x^3 - 6x^2 + 11x - 6 = 0').solve('x').map(String)) //['1', '2', '3']
|
|
98
|
+
console.log(equation('x^4 = 16').solve('x').map(String)) //['-2', '2', '-2i', '2i']
|
|
99
|
+
console.log(equation('x^4 = 16').solve('x', {domain: 'real'}).map(String)) //['-2', '2']
|
|
100
|
+
console.log(equation('x^2 - (a + b)x + a*b = 0').solve('x').map(String)) //['a', 'b']
|
|
101
|
+
const [solution] = solve(['x + y = 3', 'x - y = 1'], ['x', 'y'])
|
|
102
|
+
console.log(solution.x.toString(), solution.y.toString()) //2 1
|
|
103
|
+
console.log((parse('(x + 1)/(x - 2) >= 0') as Inequality).solve('x').set.toString()) //(-inf, -1] ∪ (2, inf)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- A polynomial equation is factored first, so its roots come out simple (1, 2, 3, not the radicals of Cardano's
|
|
107
|
+
formula); the factors are solved exactly up to degree four, and `a x^n + b` of any degree. The solutions are complex
|
|
108
|
+
numbers unless `{domain: 'real'}` is given.
|
|
109
|
+
- `solve(equations, variables)` solves a system of linear equations, in terms of the free variables when it has
|
|
110
|
+
infinitely many solutions.
|
|
111
|
+
- An inequality of fractions of polynomials gives its real solutions as a set of intervals and values (`MathSet`), with
|
|
112
|
+
union, intersection, complement and membership.
|
|
113
|
+
|
|
114
|
+
## 4. Decimals
|
|
115
|
+
|
|
116
|
+
`decimal()` gives a decimal number with a chosen precision (32 significant digits by default, up to 100000): for amounts
|
|
117
|
+
of money, or for values that need many digits. Each operation rounds its result correctly, halves to even.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import {decimal, expression} from '@lakutata/math'
|
|
121
|
+
|
|
122
|
+
console.log(decimal(0.1).add(0.2).toString()) //0.3
|
|
123
|
+
console.log(decimal('19.99').mul(3).toFixed(2)) //59.97
|
|
124
|
+
console.log(decimal(1, {precision: 50}).div(7).toString()) //0.14285714285714285714285714285714285714285714285714
|
|
125
|
+
console.log(decimal(2, {precision: 40}).sqrt().toString()) //1.41421356237309504880168872420969807857
|
|
126
|
+
console.log(expression('gamma(1/3)').toDecimal(40)) //2.678938534707747633655692940974677644129
|
|
127
|
+
console.log(expression('e^(pi*sqrt(163))').toDecimal(40)) //262537412640768743.9999999999992500725972
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
- A number is read as it is written (`0.1` is one tenth, not the binary value of the double), a string as a decimal
|
|
131
|
+
number (`'19.99'`), a bigint exactly.
|
|
132
|
+
- Besides the arithmetic, decimals have roots, `exp` and the logarithms, the trigonometric and hyperbolic functions,
|
|
133
|
+
`gamma`, `erf`, `zeta`, `lambertw` and the rounding functions. `toDecimal()` of an expression computes the same
|
|
134
|
+
functions and more special ones (`polygamma`, `beta`, `Si`, `Ei`…).
|
|
135
|
+
- `toDecimal()` computes the value again with more digits until two results agree, so every digit it gives is right:
|
|
136
|
+
`e^(pi*sqrt(163))` is not an integer, though its first twelve decimals are nines.
|
|
137
|
+
|
|
138
|
+
## 5. Matrices
|
|
139
|
+
|
|
140
|
+
There are two kinds of matrices: of numbers, for computing, and of expressions, for exact linear algebra.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import {expressionMatrix, linalg, matrix} from '@lakutata/math'
|
|
144
|
+
|
|
145
|
+
const a = matrix([[4, 1], [2, 3]])
|
|
146
|
+
console.log(linalg.solve(a, [5, 5])) //Float64Array(2) [1, 1]
|
|
147
|
+
console.log(linalg.inverse(a).toString(), linalg.determinant(a)) //[0.3, -0.1; -0.2, 0.4] 10
|
|
148
|
+
const m = expressionMatrix('[a, b; c, d]')
|
|
149
|
+
console.log(m.determinant().toString()) //a*d - b*c
|
|
150
|
+
console.log(m.inverse().toString()) //[d/(a*d - b*c), -b/(a*d - b*c); -c/(a*d - b*c), a/(a*d - b*c)]
|
|
151
|
+
console.log(expressionMatrix('[2, 1; 1, 2]').eigenvalues().map(({value, multiplicity}) => [value.toString(), multiplicity])) //[['1', 1], ['3', 1]]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- `matrix()` holds doubles in a `Float64Array`, and `linalg` computes with Eigen: systems, least squares, inverses,
|
|
155
|
+
determinants, the decompositions (LU, QR, SVD, Cholesky, eigenvalues, Schur), the exponential and the square root of
|
|
156
|
+
a matrix, Sylvester and Lyapunov equations, and sparse matrices with their direct and iterative solvers.
|
|
157
|
+
- `expressionMatrix()` holds expressions: determinants, inverses, systems, reduced row echelon forms, null spaces,
|
|
158
|
+
characteristic polynomials, eigenvalues and eigenvectors, all exact, also with variables. `jacobian()` gives the
|
|
159
|
+
Jacobian matrix of expressions.
|
|
160
|
+
|
|
161
|
+
## 6. Statistics and probability
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import {stats} from '@lakutata/math'
|
|
165
|
+
|
|
166
|
+
const values = [2, 4, 4, 4, 5, 5, 7, 9]
|
|
167
|
+
console.log(stats.mean(values), stats.median(values), stats.standardDeviation(values)) //5 4.5 2
|
|
168
|
+
console.log(stats.quantile(values, [0.25, 0.75])) //Float64Array(2) [4, 5.5]
|
|
169
|
+
const iq = stats.normal({mean: 100, standardDeviation: 15})
|
|
170
|
+
console.log(iq.cdf(130) - iq.cdf(70)) //0.9544997361036416
|
|
171
|
+
console.log(iq.quantile(0.975)) //129.3994597681008
|
|
172
|
+
console.log(stats.binomial({trials: 10, probability: 0.3}).pdf(3)) //0.2668279319999998
|
|
173
|
+
console.log(stats.random(42).integers(5, 1, 7)) //Float64Array(5) [1, 1, 6, 6, 5]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- The statistics of values: sums (compensated for their roundings), means, medians, modes, variances (of a population
|
|
177
|
+
by default, of a sample with `{normalization: 'unbiased'}`), quantiles, covariances, correlations, z-scores.
|
|
178
|
+
- 24 probability distributions, computed by Boost.Math, with their density, cdf, quantiles, moments and samples. Their
|
|
179
|
+
parameters are given by name (`{shape, scale}`), since libraries parameterize them differently; the README gives the
|
|
180
|
+
equivalents in SciPy.
|
|
181
|
+
- `stats.random(seed)` gives a generator (xoshiro256**): from the same seed, it draws the same uniform numbers and
|
|
182
|
+
integers, on every platform. It is not meant for cryptography.
|
|
183
|
+
|
|
184
|
+
## 7. Numeric solutions
|
|
185
|
+
|
|
186
|
+
When an equation or an integral has no exact solution, or numbers are enough, `numeric` takes the same formulas:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import {numeric} from '@lakutata/math'
|
|
190
|
+
|
|
191
|
+
console.log(numeric.findRoot('cos(x) = x', 'x', {bracket: [0, 1]}).root) //0.7390851332151606
|
|
192
|
+
console.log(numeric.findRoot('x^5 - x - 1', 'x', {bracket: [1, 2]}).root) //1.167303978261426
|
|
193
|
+
const {solution, converged} = numeric.solveSystem(['x^2 + y^2 = 4', 'x*y = 1'], ['x', 'y'], [2, 0])
|
|
194
|
+
console.log(solution, converged) //Float64Array(2) [1.9318516525781366, 0.5176380902050416] true
|
|
195
|
+
console.log(numeric.integrate('e^(-x^2)*ln(1 + x)', 'x', [0, Infinity]).value) //0.36623762882311717
|
|
196
|
+
const decay = numeric.solveODE({derivatives: '-2y', states: 'y', span: [0, 1], initial: 1}, {points: [0.5, 1]})
|
|
197
|
+
console.log(decay.y.data) //Float64Array(2) [0.3678795037443847, 0.13533535227865426]
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
- `polynomialRoots` gives all the roots of a polynomial, complex ones too; `findRoot` a root in a bracket or from a
|
|
201
|
+
guess; `solveSystem` a solution of nonlinear equations.
|
|
202
|
+
- `integrate` computes a definite integral by QUADPACK's method (as SciPy's `quad`), on infinite intervals too, and
|
|
203
|
+
with singularities at the ends or at given points.
|
|
204
|
+
- `solveODE` solves ordinary differential equations and systems of them, by an explicit Runge-Kutta method (as SciPy's
|
|
205
|
+
RK45) or, for stiff equations, by Radau IIA.
|
|
206
|
+
- When a method does not converge, its result says so (`converged`, `completed`) instead of throwing.
|
|
207
|
+
|
|
208
|
+
## 8. Units and physical constants
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import {constant, unit} from '@lakutata/math'
|
|
212
|
+
|
|
213
|
+
console.log(unit('5 cm + 2 inch').toString()) //10.08 cm
|
|
214
|
+
console.log(unit('100 km/h').to('m/s').toString()) //(250/9) m / s
|
|
215
|
+
console.log(unit('20 degC').to('degF').toString()) //68 degF
|
|
216
|
+
console.log(unit('1.5 h').split(['h', 'min']).map(String)) //['1 h', '30 min']
|
|
217
|
+
console.log(constant('boltzmann').mul('300 K').to('eV').toDecimal(6)) //0.025852 eV
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The table of units is that of mathjs (251 units, with their prefixes), with exact values: `1 inch` is exactly `2.54 cm`.
|
|
221
|
+
Values of different dimensions cannot be added or converted. `constant()` gives the 52 physical constants of mathjs,
|
|
222
|
+
with the values of CODATA 2022, and `createMath({units})` adds units of your own.
|
|
223
|
+
|
|
224
|
+
## 9. Number theory and polynomials
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import {ntheory, poly} from '@lakutata/math'
|
|
228
|
+
|
|
229
|
+
console.log(ntheory.factor(360)) //[{prime: 2n, exponent: 3}, {prime: 3n, exponent: 2}, {prime: 5n, exponent: 1}]
|
|
230
|
+
console.log(ntheory.isPrime(2n ** 61n - 1n)) //true
|
|
231
|
+
console.log(ntheory.convergents(ntheory.continuedFraction('pi', {terms: 4})).map(String)) //['3', '22/7', '333/106', '355/113']
|
|
232
|
+
console.log(poly.gcd('x^4 - 1', 'x^6 - 1').toString()) //x^2 - 1
|
|
233
|
+
console.log(poly.discriminant('a*x^2 + b*x + c', 'x').toString()) //-4a*c + b^2
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`ntheory` works on integers (bigints): divisibility, modular arithmetic, the Chinese remainder theorem, primes and
|
|
237
|
+
factors, sequences and counting, continued fractions. `poly` works on polynomials in any number of variables: their
|
|
238
|
+
greatest common divisors and least common multiples, resultants, discriminants and factors.
|
|
239
|
+
|
|
240
|
+
## 10. Formulas as functions
|
|
241
|
+
|
|
242
|
+
An expression evaluated many times is compiled into a function of numbers, or written as code:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import {expression} from '@lakutata/math'
|
|
246
|
+
|
|
247
|
+
const f = expression('x^2 + sin(y)').compile(['x', 'y'])
|
|
248
|
+
console.log(f(2, 0)) //4
|
|
249
|
+
console.log(f.batch(Float64Array.of(1, 0, 2, 0, 3, 0))) //Float64Array(3) [1, 4, 9]
|
|
250
|
+
console.log(expression('x^2 - 5x + 6').toJavaScript()) //Math.pow(x, 2) - 5*x + 6
|
|
251
|
+
console.log(expression('sqrt(x^2 + y^2)').toC()) //sqrt(pow(x, 2.0) + pow(y, 2.0))
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
`batch()` evaluates many rows of values in one call. `cse()` finds the parts that expressions share, so that the code
|
|
255
|
+
written from them computes each part once.
|
|
256
|
+
|
|
257
|
+
## 11. Long operations
|
|
258
|
+
|
|
259
|
+
The operations run in the thread of JavaScript, and some of them can take long: the expansion of a large power, an
|
|
260
|
+
integral, thousands of digits, the factors of a large number. While one runs, the event loop waits. The operations that
|
|
261
|
+
can take long have an asynchronous version (`expandAsync()`, `simplifyAsync()`, `integrateAsync()`, `solveAsync()`,
|
|
262
|
+
`toDecimalAsync()`… and those of `linalg`, `ntheory`, `poly` and `numeric`), which runs in the threads of the addon:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import {createMath, MathTimeoutException} from '@lakutata/math'
|
|
266
|
+
|
|
267
|
+
const scope = createMath({timeout: 2000, concurrency: 4})
|
|
268
|
+
const expanded = await scope.expression('(x + y + 1)^30').expandAsync()
|
|
269
|
+
console.log(expanded.variables()) //['x', 'y']
|
|
270
|
+
try {
|
|
271
|
+
await scope.expression('(x + y + z + 1)^500').expandAsync({timeout: 100})
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.log(error instanceof MathTimeoutException) //true
|
|
274
|
+
}
|
|
275
|
+
const controller: AbortController = new AbortController()
|
|
276
|
+
setTimeout((): void => controller.abort(), 50)
|
|
277
|
+
await scope.expression('(x + y + z + 1)^500').expandAsync({signal: controller.signal}).catch((error: Error): void => console.log(error.name)) //MathCancelledException
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
- The addon has as many threads as the processor has cores, minus one, or the number in the environment variable
|
|
281
|
+
`LAKUTATA_MATH_THREADS`. They are not the thread pool of libuv, so the files and the DNS lookups of the program do not
|
|
282
|
+
wait for them.
|
|
283
|
+
- `timeout` (an option of `createMath()`, or of one call; none by default) rejects the operation with
|
|
284
|
+
`MathTimeoutException`, and an aborted `signal` with `MathCancelledException`. The time spent waiting for a free
|
|
285
|
+
thread does not count.
|
|
286
|
+
- A cancelled operation stops at the next point where it checks for a cancellation, between the steps of its work. A
|
|
287
|
+
single step (a decomposition of a matrix by Eigen, a derivative by SymEngine) runs to its end, and the thread is free
|
|
288
|
+
only then: the promise is rejected at the time limit, while the work may go on for a while.
|
|
289
|
+
- `concurrency` (an option of `createMath()`) limits how many operations of a scope run at the same time; the others
|
|
290
|
+
wait their turn.
|
|
291
|
+
|
|
292
|
+
## 12. In an application
|
|
293
|
+
|
|
294
|
+
A controller calls the package as any other code does. With a formula from a client, use an asynchronous operation and
|
|
295
|
+
the `signal` of the request: when the client leaves, the operation is cancelled.
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import {Application, DTO} from 'lakutata'
|
|
299
|
+
import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
|
|
300
|
+
import {POST} from 'lakutata/decorator/ctrl'
|
|
301
|
+
import {Expect} from 'lakutata/decorator/dto'
|
|
302
|
+
import {expressHTTPEntrypoint} from 'lakutata/adapter/express'
|
|
303
|
+
import {createMath, type MathScope, type Solutions} from '@lakutata/math'
|
|
304
|
+
|
|
305
|
+
const scope: MathScope = createMath({timeout: 2000})
|
|
306
|
+
|
|
307
|
+
class SolveDTO extends DTO {
|
|
308
|
+
@Expect(DTO.String().max(1000).required())
|
|
309
|
+
public equation: string
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
class SolveController extends Controller {
|
|
313
|
+
@POST('/solve', SolveDTO)
|
|
314
|
+
public async solve(input: SolveDTO): Promise<{ solutions: Solutions, set: string }> {
|
|
315
|
+
const solutions: Solutions = await scope.equation(input.equation).solveAsync('x', {signal: this.signal})
|
|
316
|
+
return {solutions: solutions, set: solutions.set.toString()}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
Application.run(() => ({
|
|
321
|
+
id: 'solver.app',
|
|
322
|
+
name: 'Solver',
|
|
323
|
+
components: {
|
|
324
|
+
entrypoint: buildEntrypoints({
|
|
325
|
+
controllers: [SolveController],
|
|
326
|
+
http: expressHTTPEntrypoint({port: 8080})
|
|
327
|
+
})
|
|
328
|
+
},
|
|
329
|
+
bootstrap: ['entrypoint']
|
|
330
|
+
}))
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
```text
|
|
334
|
+
$ curl -X POST localhost:8080/solve -H 'Content-Type: application/json' -d '{"equation":"x^2 - 5x + 6 = 0"}'
|
|
335
|
+
{"solutions":["2","3"],"set":"{2, 3}"}
|
|
336
|
+
$ curl -X POST localhost:8080/solve -H 'Content-Type: application/json' -d '{"equation":"x^2 - 5x +* 6 = 0"}'
|
|
337
|
+
{"errno":"E_MATH_SYNTAX","errMsg":"Unexpected *"}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
- The exceptions of the package have no `statusCode`, so the wrong formula above is answered with the status 500 and
|
|
341
|
+
`{"errno": "E_MATH_SYNTAX", …}`. To answer the errors in the formulas of the clients with 400, catch `MathException`
|
|
342
|
+
in the action and throw an exception of the application (see [Exceptions](Exceptions.md)); leave `E_MATH_INTERNAL`, an
|
|
343
|
+
internal error of the addon, as it is.
|
|
344
|
+
- The length of a formula does not bound the work it takes: `(x + 1)^100000` is short. The time limit does.
|
|
345
|
+
- The values of the package become JSON through their `toJSON()`: an expression, an equation, a decimal and a value
|
|
346
|
+
with units give their text, solutions an array of texts, a matrix its rows. A vector is a `Float64Array`, which
|
|
347
|
+
`JSON.stringify()` writes as an object (`{"0": 1, "1": 1}`): send `Array.from(vector)`.
|
|
348
|
+
- The bundles, the executables and the installers made by `lakutata package` carry the compiled addon
|
|
349
|
+
(`build/Release/math.node`). Build them on each target platform, after installing the package there; the rules of
|
|
350
|
+
the native addons apply (see [Packaging](Packaging.md) and [NativeAddons](NativeAddons.md)).
|
|
351
|
+
- The package runs in Node.js `^22.13.0 || >=24.11.0`, in Bun and in the main process of Electron, which load the
|
|
352
|
+
addon compiled for Node.js; not in the renderers of Electron nor in the browsers. In Bun, terminating a worker thread
|
|
353
|
+
while operations of the addon still run in it ends the whole process with a fatal error of Node-API (a bug of Bun
|
|
354
|
+
1.4).
|
|
355
|
+
|
|
356
|
+
## 13. How it is checked, and what it does not do
|
|
357
|
+
|
|
358
|
+
The tests of the package compare its results on about 2500 cases with those of SymPy and mpmath (the formulas, the
|
|
359
|
+
decimals, the units), of NumPy and SciPy (the matrices, the statistics, the distributions, the numeric solutions), and
|
|
360
|
+
of mathjs and nerdamer; they run on macOS (arm64: Node.js 24, Bun 1.4, Electron 44), on Linux (arm64, glibc and musl:
|
|
361
|
+
Node.js 24, Bun 1.4) and on Windows 11 (arm64: Node.js 24).
|
|
362
|
+
|
|
363
|
+
What it does not do, whether by design or not yet:
|
|
364
|
+
|
|
365
|
+
- An integral whose fraction needs algebraic numbers to factor its denominator (`1/(x^4 + 1)`), or whose antiderivative
|
|
366
|
+
is neither elementary nor one of the special functions of integrals, throws `MathNotSupportedException`; it is not
|
|
367
|
+
kept as an unevaluated integral. `numeric.integrate()` computes the definite integral.
|
|
368
|
+
- `solve()` solves one equation, or a system of linear equations; a system of nonlinear equations is for
|
|
369
|
+
`numeric.solveSystem()`. An equation with infinitely many solutions, such as `sin(x) = 0`, gives no array of them: its
|
|
370
|
+
`set` only describes them (`{2pi*n + pi | n integer} or {2pi*n | n integer}`), and the operations of the sets do not
|
|
371
|
+
take it.
|
|
372
|
+
- An inequality must be made of fractions of polynomials with rational coefficients.
|
|
373
|
+
- The exact eigenvalues of a matrix of expressions are found up to degree four; `linalg.eigen()` gives them as numbers.
|
|
374
|
+
- Nothing that depends on the signs of the variables: `sqrt(x^2)` is not `abs(x)`, and a limit that depends on the
|
|
375
|
+
sign of a parameter (`exp(a*x)` at `inf`) is not found.
|
|
376
|
+
- Exact numbers have at most about 315000 digits: a larger power stays a power (`2^(10^7)`).
|
|
377
|
+
|
|
378
|
+
## 14. Exceptions
|
|
379
|
+
|
|
380
|
+
All of them extend `MathException`. The errors in a formula carry the `position` of the character where they are found.
|
|
381
|
+
The README of the package lists every case.
|
|
382
|
+
|
|
383
|
+
| Exception | `errno` | When |
|
|
384
|
+
|---|---|---|
|
|
385
|
+
| `MathSyntaxException` | `E_MATH_SYNTAX` | A formula is not written correctly, an equation is given where an expression is expected, or the other way round |
|
|
386
|
+
| `MathUnknownFunctionException` | `E_MATH_UNKNOWN_FUNCTION` | A formula calls a function that is neither built in nor defined |
|
|
387
|
+
| `MathDefinitionException` | `E_MATH_DEFINITION` | A definition or a unit of `createMath()` is not valid |
|
|
388
|
+
| `MathUnitException` | `E_MATH_UNIT` | Units of different dimensions added, compared or converted; a unit that is not known |
|
|
389
|
+
| `MathDomainException` | `E_MATH_DOMAIN` | A value outside the domain of a function: a complex value asked as a number, a singular matrix inverted, a limit that does not exist |
|
|
390
|
+
| `MathDivisionByZeroException` | `E_MATH_DIVISION_BY_ZERO` | A division by zero while computing decimals, a modulus of 0 |
|
|
391
|
+
| `MathNotSupportedException` | `E_MATH_NOT_SUPPORTED` | An operation the package does not do for this formula (section 13) |
|
|
392
|
+
| `MathArgumentException` | `E_MATH_ARGUMENT` | An argument that is not valid: variables left where a number is asked, matrices of the wrong dimensions |
|
|
393
|
+
| `MathTimeoutException`, `MathCancelledException` | `E_MATH_TIMEOUT`, `E_MATH_CANCELLED` | An asynchronous operation past its time limit, or cancelled |
|
|
394
|
+
| `MathException` | `E_MATH_INTERNAL` | An internal error of the addon |
|
package/doc/en/NativeAddons.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
A native addon is a library written in C++ or Rust that Node.js loads as a `.node` file: for drivers and devices,
|
|
4
4
|
codecs, system APIs, or code that must run fast. `lakutata add --native` creates an addon package in a workspace,
|
|
5
5
|
ready to build and test; `lakutata native` links libraries to it; and `lakutata package` ships it with the application.
|
|
6
|
-
The addons use Node-API, so one build runs in all the supported versions of Node.js and in Electron.
|
|
6
|
+
The addons use Node-API, so one build runs in all the supported versions of Node.js and in Electron. To call the C
|
|
7
|
+
functions of a library without writing an addon, see [FFI](FFI.md).
|
|
7
8
|
|
|
8
9
|
## 1. Creating an addon
|
|
9
10
|
|
|
@@ -205,6 +206,11 @@ The application depends on the addon package, and imports it as any package (`im
|
|
|
205
206
|
the directory layout ([Packaging](Packaging.md)). The addon is built for the platform of the machine that packs the
|
|
206
207
|
application, so pack the application of each platform on that platform (a CI matrix, or a container of the
|
|
207
208
|
architecture for Linux).
|
|
209
|
+
- **An addon package built on the framework** (it depends on `lakutata` or `@lakutata/core`, to provide a component,
|
|
210
|
+
for instance) is bundled with the application instead, so that there is one copy of the framework, and only its addon
|
|
211
|
+
and the files beside it go into the tree. The addon is shipped when it is loaded as the package of `lakutata add`
|
|
212
|
+
loads it (`node-gyp-build` given the directory of the package), by a `require` of its file, or by a path from the
|
|
213
|
+
module (`path.join(__dirname, 'prebuilds', ...)`); `lakutata package` warns of an addon loaded in another way.
|
|
208
214
|
- **Electron**: the addons are copied as they are installed. The addons that `lakutata add` creates run in Electron
|
|
209
215
|
without being rebuilt, as do all the addons built on Node-API; an addon built on the V8 API of Node.js must be
|
|
210
216
|
installed in its build for Electron before packing (`electron-rebuild`).
|
package/doc/en/Overview.md
CHANGED
|
@@ -47,7 +47,9 @@ tool, a web and a desktop application, calls between services, background jobs,
|
|
|
47
47
|
Not part of this package: `@lakutata/cli` (the `lakutata` command for creating projects, running migrations and packing
|
|
48
48
|
applications, see [Command](Command.md)), `@lakutata/grpc` (a gRPC client, server and service transport),
|
|
49
49
|
`@lakutata/nats` (a NATS component and service transport), `@lakutata/dbus` (D-Bus), `@lakutata/mcp` (the Model
|
|
50
|
-
Context Protocol)
|
|
50
|
+
Context Protocol), `@lakutata/webrtc` (WebRTC: the signaling, a TURN server, a media server, peer connections),
|
|
51
|
+
`@lakutata/ffi` (the C functions of native libraries) and `@lakutata/math` (symbolic and numeric mathematics, as
|
|
52
|
+
functions that need no lakutata application).
|
|
51
53
|
|
|
52
54
|
## The guides
|
|
53
55
|
|
|
@@ -91,6 +93,8 @@ Context Protocol) and `@lakutata/webrtc` (WebRTC: the signaling, a TURN server,
|
|
|
91
93
|
| [DBus](DBus.md) | A D-Bus service and client (`@lakutata/dbus`): methods, objects, properties, signals |
|
|
92
94
|
| [MCP](MCP.md) | The tools, resources and prompts of the Model Context Protocol (`@lakutata/mcp`), over stdio and HTTP |
|
|
93
95
|
| [WebRTC](WebRTC.md) | Peer-to-peer connections and a media server (`@lakutata/webrtc`): the signaling, the TURN server, browsers and applications, the actions of an application called through data channels, WHIP and WHEP |
|
|
96
|
+
| [FFI](FFI.md) | The C functions of native libraries (`@lakutata/ffi`): declarations copied from headers, structs, callbacks, memory, packaging |
|
|
97
|
+
| [Math](Math.md) | Mathematics (`@lakutata/math`): formulas and exact results, equations, decimals of any precision, matrices, statistics, numeric solutions, units |
|
|
94
98
|
| [Command](Command.md) | The `lakutata` command: creating projects, migrations, translations, environment variables |
|
|
95
99
|
| [Workspaces](Workspaces.md) | Projects of several packages: running their scripts, giving them a version, adding packages |
|
|
96
100
|
| [NativeAddons](NativeAddons.md) | Native addons in C++ or Rust: creating them, linking libraries, shipping them |
|
package/doc/en/UpgradingFrom2.md
CHANGED
|
@@ -28,8 +28,10 @@ that the project declares and the one installed. There is no upgrade command: th
|
|
|
28
28
|
matrix and the base images of the Dockerfiles.
|
|
29
29
|
- **A TypeScript that knows `"module": "Node20"`** (the templates use TypeScript 6): `lakutata/tsconfig.json` now sets
|
|
30
30
|
it (2.x: `Node16`), and older compilers reject it.
|
|
31
|
-
- **`package.json`**: `"type": "module"`, `lakutata` `^3.0.0`,
|
|
32
|
-
|
|
31
|
+
- **`package.json`**: `"type": "module"`, `lakutata` `^3.0.0`, the `@lakutata/*` packages at `^3.0.0` too, and
|
|
32
|
+
`@lakutata/cli` in `devDependencies`. Do not give the other packages the version of `lakutata`: a patch release
|
|
33
|
+
publishes only the packages it changes, so their latest versions can differ (`lakutata` 3.1.4 with
|
|
34
|
+
`@lakutata/ffi` 3.1.2).
|
|
33
35
|
- **A clean install**: remove `node_modules` and the lock file, then install. Upgrading in place can leave several
|
|
34
36
|
copies of `@lakutata/core`, whose classes and decorator metadata do not match (`instanceof` and injection fail across
|
|
35
37
|
them). `npm ls @lakutata/core` must print one version.
|
package/doc/zh/Command.md
CHANGED
|
@@ -55,6 +55,8 @@ lakutata create --name tool --template cli --package-manager none --no-git
|
|
|
55
55
|
|
|
56
56
|
`lakutata add <name> --native gyp|cmake|rust` 添加一个用 C++ 或 Rust 编写的原生插件,`lakutata native <link|include|define> <value>` 给它链接库。见 [NativeAddons](NativeAddons.md)。
|
|
57
57
|
|
|
58
|
+
`lakutata ffi generate <header>` 根据 C 头文件生成 `@lakutata/ffi` 原生库的类。见 [FFI](FFI.md) 第 9 节。
|
|
59
|
+
|
|
58
60
|
## 4. 迁移与翻译
|
|
59
61
|
|
|
60
62
|
`lakutata migration <action>` 生成、创建、执行、回滚和查看应用的数据库迁移。它用应用的 `dev` 脚本构建并启动应用,此时应用只连接数据库。见 [Migrations](Migrations.md) 第 2 节。
|