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/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 |
@@ -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`).
@@ -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) and `@lakutata/webrtc` (WebRTC: the signaling, a TURN server, a media server, peer connections).
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 |
@@ -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`, every `@lakutata/*` package at the same version (they
32
- depend on each other's exact version), and `@lakutata/cli` in `devDependencies`.
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 节。