pimath 0.2.13 → 0.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/pimath.js +856 -1882
  2. package/dist/pimath.js.map +1 -1
  3. package/package.json +2 -2
  4. package/src/algebra/equation.ts +562 -558
  5. package/src/algebra/equationSolver.ts +489 -489
  6. package/src/algebra/factor.ts +396 -392
  7. package/src/algebra/linearSystem.ts +446 -442
  8. package/src/algebra/matrix.ts +515 -511
  9. package/src/algebra/monom.ts +932 -928
  10. package/src/algebra/polyFactor.ts +724 -720
  11. package/src/algebra/polynom.ts +1233 -1229
  12. package/src/coefficients/fraction.ts +907 -748
  13. package/src/coefficients/root.ts +500 -359
  14. package/src/geometry/TupleN.ts +129 -129
  15. package/src/geometry/circle.ts +455 -455
  16. package/src/geometry/line.ts +658 -654
  17. package/src/geometry/point.ts +105 -105
  18. package/src/geometry/sphere3.ts +2 -2
  19. package/src/geometry/triangle.ts +488 -488
  20. package/src/geometry/vector.ts +230 -226
  21. package/src/numeric.ts +136 -51
  22. package/src/pimath.interface.ts +2 -0
  23. package/types/algebra/equation.d.ts +1 -0
  24. package/types/algebra/equation.d.ts.map +1 -1
  25. package/types/algebra/equationSolver.d.ts.map +1 -1
  26. package/types/algebra/factor.d.ts +1 -0
  27. package/types/algebra/factor.d.ts.map +1 -1
  28. package/types/algebra/linearSystem.d.ts +1 -0
  29. package/types/algebra/linearSystem.d.ts.map +1 -1
  30. package/types/algebra/matrix.d.ts +6 -5
  31. package/types/algebra/matrix.d.ts.map +1 -1
  32. package/types/algebra/monom.d.ts +1 -0
  33. package/types/algebra/monom.d.ts.map +1 -1
  34. package/types/algebra/polyFactor.d.ts +1 -0
  35. package/types/algebra/polyFactor.d.ts.map +1 -1
  36. package/types/algebra/polynom.d.ts +1 -0
  37. package/types/algebra/polynom.d.ts.map +1 -1
  38. package/types/coefficients/fraction.d.ts +62 -53
  39. package/types/coefficients/fraction.d.ts.map +1 -1
  40. package/types/coefficients/root.d.ts +3 -0
  41. package/types/coefficients/root.d.ts.map +1 -1
  42. package/types/geometry/TupleN.d.ts +1 -1
  43. package/types/geometry/TupleN.d.ts.map +1 -1
  44. package/types/geometry/circle.d.ts.map +1 -1
  45. package/types/geometry/line.d.ts +1 -0
  46. package/types/geometry/line.d.ts.map +1 -1
  47. package/types/geometry/point.d.ts.map +1 -1
  48. package/types/geometry/sphere3.d.ts +2 -2
  49. package/types/geometry/sphere3.d.ts.map +1 -1
  50. package/types/geometry/triangle.d.ts.map +1 -1
  51. package/types/geometry/vector.d.ts +1 -0
  52. package/types/geometry/vector.d.ts.map +1 -1
  53. package/types/index.d.ts +3 -3
  54. package/types/numeric.d.ts +4 -4
  55. package/types/numeric.d.ts.map +1 -1
  56. package/types/pimath.interface.d.ts +1 -0
  57. package/types/pimath.interface.d.ts.map +1 -1
  58. package/types/primes.data.d.ts +2 -0
  59. package/types/primes.data.d.ts.map +1 -0
@@ -1,489 +1,489 @@
1
- import type {InputValue} from "../pimath.interface"
2
- import type {Polynom} from "./polynom"
3
- import {Fraction} from "../coefficients"
4
- import {Numeric} from "../numeric"
5
- import type {Equation} from "./equation"
6
- import {Solution} from "../analyze"
7
- import {MathError} from "../errors"
8
-
9
- export class EquationSolver {
10
- _: number
11
- #bissectionDeltaX: number
12
- readonly #leftPolynom: Polynom
13
- readonly #variable: string
14
-
15
- constructor(left: Polynom | Equation, right?: Polynom, variable = "x") {
16
- this.#variable = variable
17
- this.#bissectionDeltaX = 1e-4
18
- this._ = 0
19
-
20
- // do this to avoid importing Polynom or Equation
21
- if (Object.hasOwn(left, 'moveLeft')) {
22
- const equ = left as Equation
23
- this.#leftPolynom = equ.left.clone().subtract(equ.right)
24
- } else {
25
- this.#leftPolynom = (left as Polynom).clone().subtract(right ?? 0)
26
- }
27
-
28
- return this
29
- }
30
-
31
- get bissectionComplexityCounter() {
32
- return this._
33
- }
34
-
35
- get bissectionDeltaX() {
36
- return this.#bissectionDeltaX
37
- }
38
-
39
- set bissectionDeltaX(value: number) {
40
- this.#bissectionDeltaX = value
41
- }
42
-
43
- public solve(): Solution[] {
44
- const degree = this.#leftPolynom.degree().value
45
-
46
- if (degree === 0) {
47
- return []
48
- }
49
-
50
- if (degree === 1) {
51
- return this.#solveLinear()
52
- }
53
-
54
- if (degree === 2) {
55
- return this.#solveQuadratic()
56
- }
57
-
58
- // Try to solve by factorization -> exact solutions.
59
- const {solutions, reminder} = this.#solveByFactorization()
60
-
61
- // The remaining polynom is of degree zero. No more solutions available.
62
- if (reminder.degree().isZero()) {
63
- return solutions
64
- }
65
-
66
- // The remaining polyno is of degree one or two, but cannot be solved by factorization (!).
67
- if (reminder.degree().value <= 2) {
68
- return solutions.concat(
69
- new EquationSolver(reminder.clone()).solve()
70
- )
71
- }
72
-
73
- // Use approximative solutions, using bissection algorithm.
74
- this._ = 0
75
- return solutions.concat(
76
- this.#solveByBissection(reminder)
77
- ).sort((a, b) => a.value - b.value)
78
- }
79
-
80
- public solveAsCardan(): Solution[] {
81
- if (this.#leftPolynom.degree().value !== 3) {
82
- throw new MathError("The equation is not cubic.")
83
- }
84
- return this.#solveCubic_CardanFormula()
85
- }
86
-
87
- #makeApproximativeSolution(value: number, output?: { tex: string, display: string }): Solution {
88
- const sol = new Solution()
89
-
90
- sol.exact = false
91
- sol.tex = output?.tex ?? null
92
- sol.display = output?.display ?? null
93
- sol.fraction = new Fraction(value)
94
- sol.fraction.exact = false
95
- sol.variable = this.#variable
96
-
97
- return sol
98
- }
99
-
100
- #makeSolution(value: InputValue<Fraction>): Solution {
101
- if (value instanceof Fraction && !value.exact) {
102
- return this.#makeApproximativeSolution(value.value)
103
- }
104
-
105
- return Solution.fromFraction(value)
106
- }
107
-
108
- // Solve using bissection algorithm (approximative solution)
109
- #solveByBissection(polynom: Polynom): Solution[] {
110
- const solutions: Solution[] = []
111
- const degree = polynom.degree().value
112
-
113
- // Calculate the Cauchy Bounds.
114
- const [a, ...values] = polynom.getCoefficients()
115
- const B = 2 + Math.max(...values.map(x => x.value / a.value))
116
-
117
- // Cut the [-B;B] interval in *n* parts : n = 100
118
-
119
- // Calculate the f(x) value at each points
120
- const evaluatedPoints = this.#solveByBissection_evaluatePoints(polynom, B, 100)
121
-
122
- // Check if there is a least n opposite couples
123
- const couples = this.#solveByBissection_getCouples(evaluatedPoints, degree)
124
-
125
- // All solutions fund !
126
- couples.forEach(couple => {
127
- const [a, b] = couple
128
-
129
- if (a === b) {
130
- // Exact solution
131
- solutions.push(this.#makeSolution(a))
132
- } else {
133
- const bissection = this.#solveByBissection_algorithm(polynom, a, b)
134
-
135
- if (bissection !== null) {
136
- solutions.push(this.#makeApproximativeSolution(bissection))
137
- }
138
- }
139
- })
140
-
141
- return solutions
142
- }
143
-
144
- #solveByBissection_algorithm(polynom: Polynom, a: number, b: number): number | null {
145
- let fa = polynom.evaluate(a, true) as number
146
- let fb = polynom.evaluate(b, true) as number
147
-
148
- if (fa * fb > 0) {
149
- console.log("Pas de racine dans l'intervalle donné")
150
- return null
151
- }
152
-
153
- let mid: number
154
- while ((b - a) / 2 > this.#bissectionDeltaX) {
155
- this._++
156
-
157
- mid = (a + b) / 2
158
- const fmid = polynom.evaluate(mid, true) as number
159
-
160
- if (fmid === 0) {
161
- return mid // racine exacte trouvée
162
- } else if (fa * fmid < 0) {
163
- b = mid
164
- fb = fmid
165
- } else {
166
- a = mid
167
- fa = fmid
168
- }
169
- }
170
- return (a + b) / 2 // retourner la racine approximative
171
- }
172
-
173
- #solveByBissection_evaluatePoints(polynom: Polynom, bounds: number, slice: number): { x: number, fx: number }[] {
174
-
175
- const evaluatedPoints: { x: number, fx: number }[] = []
176
-
177
- const dx = 2 * bounds / slice
178
-
179
- for (let searchValue = -bounds; searchValue <= bounds; searchValue += dx) {
180
-
181
- const x = Numeric.numberCorrection(searchValue)
182
- evaluatedPoints.push(
183
- {
184
- x,
185
- fx: polynom.evaluate(x, true) as number
186
- }
187
- )
188
- }
189
-
190
- return evaluatedPoints
191
- }
192
-
193
- #solveByBissection_getCouples(evaluatedPoints: { x: number, fx: number }[], degree: number): [number, number][] {
194
- const couples: [number, number][] = []
195
-
196
- for (let index = 1; index < evaluatedPoints.length; index++) {
197
-
198
- const value = evaluatedPoints[index]
199
- const previous = evaluatedPoints[index - 1]
200
-
201
- if (value.fx === 0) {
202
- // exact value
203
- couples.push([value.x, value.x])
204
- } else if (value.fx * previous.fx < 0) {
205
- // both evaluated expression are of opposite sign.
206
- couples.push([previous.x, value.x])
207
- }
208
-
209
- if (couples.length === degree) {
210
- // All couples are found.
211
- return couples
212
- }
213
- }
214
-
215
- return couples
216
- }
217
-
218
- #solveByFactorization(): { solutions: Solution[], reminder: Polynom } {
219
- // Move everything to the left.
220
-
221
- // Get the polynom on the left (on the right, it's zero)
222
- const left = this.#leftPolynom.clone()
223
-
224
- // The solutions of the equation
225
- const solutions: Solution[] = []
226
-
227
- // multiply by the lcm of the denominators
228
- // to get rid of the fractions
229
- const lcm = left.lcmDenominator()
230
- if (lcm !== 1) {
231
- left.multiply(lcm)
232
- }
233
-
234
- // alternative method : if there is no monom of degree zero.
235
- // - get the monom with the smallest degree.
236
- // - if degree>0, divide by x^{degree}
237
- const a = left.monomByDegree().coefficient
238
- const b = left.monomByDegree(0).coefficient
239
- if (b.isZero()) {
240
- solutions.push(this.#makeSolution(0))
241
-
242
- const m = left.monoms.reduce((min, curr) => curr.degree().value < min.degree().value ? curr : min)
243
- const k = m.coefficient
244
- m.clone().divide(k) // make the monom unit
245
- left.divide(m)
246
- }
247
-
248
- // get all dividers of a and b
249
- const dividersA = Numeric.dividers(a.value)
250
- const dividersB = Numeric.dividers(b.value)
251
-
252
- // gel all possible solutions
253
- const testingSolutions: Fraction[] = []
254
- for (const da of dividersA) {
255
- for (const db of dividersB) {
256
- const f = new Fraction(db, da)
257
- if (!testingSolutions.find(s => s.value === f.value)) {
258
- testingSolutions.push(f.clone())
259
- testingSolutions.push(f.opposite().clone())
260
- }
261
-
262
- }
263
- }
264
-
265
- // Each value in testingSolutions are "unique" -> juste test them to see if it evaluates to zero.
266
- testingSolutions.forEach(f => {
267
- if ((left.evaluate(f) as Fraction).isZero()) {
268
- solutions.push(this.#makeSolution(f))
269
- }
270
- })
271
-
272
- // divide the left polynom by the solutions (as polynom)
273
- // to get the reduced polynom
274
- for (const s of solutions) {
275
- // all solutions are exact solutions.
276
- // skip the zero solutions if it exists.
277
- if (s.isZero()) {
278
- continue
279
- }
280
-
281
- // The divider polynom
282
- const p = left.clone()
283
- .fromCoefficients(s.fraction.denominator, -s.fraction.numerator)
284
-
285
- // Reset the count
286
- s.count = 0
287
-
288
- // Divide as long it's dividable.
289
- while (left.isDividableBy(p)) {
290
- left.divide(p)
291
- s.count++
292
- }
293
- }
294
-
295
- // if the reduced polynom is of degree 0, we have found all the solutions
296
- // if the reduced polynom is of degree greater than 3, we can't solve it
297
- if (left.degree().isZero() || left.degree().value > 3) {
298
- // Tri des réponses
299
- solutions.sort((a, b) => a.value - b.value)
300
- return {solutions, reminder: left}
301
- }
302
-
303
- // if the reduced polynom is of degree 1 or 2, we can solve it
304
- const zeroPolynom = left.clone().zero()
305
-
306
- const solver = new EquationSolver(left, zeroPolynom, this.#variable)
307
- return {
308
- solutions: solutions
309
- .concat(solver.solve())
310
- .sort((a, b) => a.value - b.value),
311
- reminder: zeroPolynom
312
- }
313
- }
314
-
315
- #solveCubic_CardanFormula(): Solution[] {
316
- // get the coefficients of the equation
317
- const left = this.#leftPolynom
318
-
319
- // left is a polynom ax^3+bx^2+cx+d => the solution is x = (-b±√(b^2-4ac))/2a
320
- const a = left.monomByDegree(3).coefficient
321
- const b = left.monomByDegree(2).coefficient
322
- const c = left.monomByDegree(1).coefficient
323
- const d = left.monomByDegree(0).coefficient
324
-
325
- // normalize the coefficient by dividing by a
326
- const an = b.clone().divide(a)
327
- const bn = c.clone().divide(a)
328
- const cn = d.clone().divide(a)
329
-
330
- // Depressed cubic equation
331
- // x^3+px+q=0
332
- const p = bn.clone().subtract(an.clone().pow(2).divide(3))
333
- const q = cn.clone()
334
- .subtract(an.clone().multiply(bn).divide(3))
335
- .add(an.clone().pow(3).multiply(2).divide(27))
336
-
337
-
338
- // Cardan method
339
- // X^2 + qX - p^3/27 = 0
340
- // X^2 -SX + P = 0
341
- // S = u^3 + v^3 = -q
342
- // P = u^3v^3 = -p^3/27
343
- // u^3 and v^3 are the roots of the equation
344
- const S = q.clone().opposite()
345
- const P = p.clone().opposite().pow(3).divide(27)
346
-
347
- // Discriminant : delta = -(S^2 - 4P)
348
- // delta < 0 : 1 real solution
349
- // delta = 0 : 2 real solutions
350
- // delta > 0 : 3 real solutions
351
- const delta = S.clone().pow(2).subtract(P.clone().multiply(4)).opposite()
352
-
353
- // if delta is negative, there is one real solution
354
- if (delta.isNegative()) {
355
- const u = q.clone().opposite().add(delta.clone().opposite().sqrt()).divide(2).root(3)
356
- const v = q.clone().opposite().subtract(delta.clone().opposite().sqrt()).divide(2).root(3)
357
-
358
- const x = u.clone().add(v).subtract(an.clone().divide(3))
359
-
360
- return [this.#makeSolution(x)]
361
- }
362
-
363
- // if delta is zero, there are two real solutions
364
- if (delta.isZero()) {
365
- const u = q.clone().opposite().divide(2).root(3)
366
-
367
- const x1 = u.clone().opposite().subtract(an.clone().divide(3))
368
- const x2 = u.clone().multiply(2).subtract(an.clone().divide(3))
369
-
370
- // There is only one unique solution
371
- if (x1.isEqual(x2)) {
372
- return [this.#makeSolution(x1)]
373
- }
374
-
375
- return [
376
- this.#makeSolution(x2),
377
- this.#makeSolution(x1)
378
- ].sort((a, b) => a.value - b.value)
379
- }
380
-
381
- // if delta is positive, there are three real solutions
382
- if (delta.isPositive()) {
383
- const x: number[] = []
384
- const pv = p.value,
385
- qv = q.value,
386
- anv = an.value
387
-
388
- for (let i = 0; i < 3; i++) {
389
- x.push(2 * Math.sqrt(-pv / 3) * Math.cos(Math.acos(3 * qv / (2 * pv) * Math.sqrt(-3 / pv)) / 3 + 2 * Math.PI * i / 3) - anv / 3)
390
- }
391
-
392
- return x
393
- .map(v => this.#makeApproximativeSolution(v))
394
- .sort((a, b) => a.value - b.value)
395
-
396
- }
397
-
398
- return []
399
- }
400
-
401
- #solveLinear(): Solution[] {
402
- // The equation is linear.
403
- const [a, b] = this.#leftPolynom.getCoefficients()
404
-
405
- // left is a polynom ax+b => the solution is x = -b/a
406
- const f = b.opposite().divide(a)
407
-
408
- return [
409
- this.#makeSolution(f)
410
- ]
411
- }
412
-
413
- #solveQuadratic(): Solution[] {
414
-
415
- // The equation is quadratic.
416
- // We can solve it by isolating the variable.
417
-
418
- // The monom with greatest degree must be positive.
419
- const left = this.#leftPolynom
420
- if (left.monomByDegree().coefficient.isNegative()) {
421
- left.opposite()
422
- }
423
-
424
- // left is a polynom ax^2+bx+c => the solution is x = (-b±√(b^2-4ac))/2a
425
- const [a, b, c] = left.getCoefficients()
426
-
427
- // delta2 = b^2-4ac
428
- const delta2 = b.clone().pow(2).subtract(a.clone().multiply(c).multiply(4))
429
-
430
- // if delta2 is negative, there is no solution
431
- if (delta2.isNegative()) {
432
- return []
433
- }
434
-
435
- // if delta2 is zero, there is one solution
436
- // if delta2 is positive, there are two solutions
437
- // if delta2 is a square, it will be an exact solution.
438
-
439
- if (delta2.isSquare()) {
440
- // delta is a fraction.
441
- // the solutions are (-b±√(b^2-4ac))/2a
442
- const delta = delta2.sqrt()
443
- const f1 = b.clone().opposite().subtract(delta).divide(a.clone().multiply(2))
444
- const f2 = b.clone().opposite().add(delta).divide(a.clone().multiply(2))
445
-
446
- // Delta is zero, there is only one solution
447
- if (delta.isZero()) {
448
- const sol = this.#makeSolution(f1)
449
- sol.count = 2
450
- return [sol]
451
- }
452
-
453
- // delta is positive, there are two solutions
454
- return [
455
- this.#makeSolution(f1),
456
- this.#makeSolution(f2)
457
- ].sort((a, b) => a.value - b.value)
458
- }
459
-
460
- // delta is not a square, there are one or two approximative solutions.
461
- // We will use the approximate value of the square root.
462
- // const delta = delta2.value ** 0.5
463
- // const f1 = (-b.value + delta) / (2 * a.value)
464
- // const f2 = (-b.value - delta) / (2 * a.value)
465
-
466
- return this.#solveQuadratic_Output(a, b, delta2)
467
- }
468
-
469
- #solveQuadratic_Output(a: Fraction, b: Fraction, delta: Fraction): Solution[] {
470
- // -b +/- sqrt(delta) / 2a
471
- // reduce the sqrt - extract pow.
472
- const a2 = a.clone().multiply(2)
473
-
474
- const sol1 = new Solution()
475
- sol1.fraction = b.clone().opposite().divide(a2.clone())
476
- sol1.root.radical = delta.clone()
477
- sol1.root.factor = new Fraction().one().divide(a2.clone())
478
- sol1.exact = true
479
-
480
- const sol2 = new Solution()
481
- sol2.fraction = b.clone().opposite().divide(a2.clone())
482
- sol2.root.radical = delta.clone()
483
- sol2.root.factor = new Fraction().one().divide(a2.clone()).opposite()
484
- sol2.exact = true
485
-
486
- return [sol1, sol2]
487
- .sort((a, b) => a.value - b.value)
488
- }
489
- }
1
+ import type {InputValue} from "../pimath.interface"
2
+ import type {Polynom} from "./polynom"
3
+ import {Fraction} from "../coefficients"
4
+ import {Numeric} from "../numeric"
5
+ import type {Equation} from "./equation"
6
+ import {Solution} from "../analyze"
7
+ import {MathError} from "../errors"
8
+
9
+ export class EquationSolver {
10
+ _: number
11
+ #bissectionDeltaX: number
12
+ readonly #leftPolynom: Polynom
13
+ readonly #variable: string
14
+
15
+ constructor(left: Polynom | Equation, right?: Polynom, variable = "x") {
16
+ this.#variable = variable
17
+ this.#bissectionDeltaX = 1e-4
18
+ this._ = 0
19
+
20
+ // do this to avoid importing Polynom or Equation
21
+ if (Object.hasOwn(left, 'moveLeft')) {
22
+ const equ = left as Equation
23
+ this.#leftPolynom = equ.left.clone().subtract(equ.right)
24
+ } else {
25
+ this.#leftPolynom = (left as Polynom).clone().subtract(right ?? 0)
26
+ }
27
+
28
+ return this
29
+ }
30
+
31
+ get bissectionComplexityCounter() {
32
+ return this._
33
+ }
34
+
35
+ get bissectionDeltaX() {
36
+ return this.#bissectionDeltaX
37
+ }
38
+
39
+ set bissectionDeltaX(value: number) {
40
+ this.#bissectionDeltaX = value
41
+ }
42
+
43
+ solve(): Solution[] {
44
+ const degree = this.#leftPolynom.degree().value
45
+
46
+ if (degree === 0) {
47
+ return []
48
+ }
49
+
50
+ if (degree === 1) {
51
+ return this.#solveLinear()
52
+ }
53
+
54
+ if (degree === 2) {
55
+ return this.#solveQuadratic()
56
+ }
57
+
58
+ // Try to solve by factorization -> exact solutions.
59
+ const {solutions, reminder} = this.#solveByFactorization()
60
+
61
+ // The remaining polynom is of degree zero. No more solutions available.
62
+ if (reminder.degree().isZero()) {
63
+ return solutions
64
+ }
65
+
66
+ // The remaining polyno is of degree one or two, but cannot be solved by factorization (!).
67
+ if (reminder.degree().value <= 2) {
68
+ return solutions.concat(
69
+ new EquationSolver(reminder.clone()).solve()
70
+ )
71
+ }
72
+
73
+ // Use approximative solutions, using bissection algorithm.
74
+ this._ = 0
75
+ return solutions.concat(
76
+ this.#solveByBissection(reminder)
77
+ ).sort((a, b) => a.value - b.value)
78
+ }
79
+
80
+ solveAsCardan(): Solution[] {
81
+ if (this.#leftPolynom.degree().value !== 3) {
82
+ throw new MathError("The equation is not cubic.")
83
+ }
84
+ return this.#solveCubic_CardanFormula()
85
+ }
86
+
87
+ #makeApproximativeSolution(value: number, output?: { tex: string, display: string }): Solution {
88
+ const sol = new Solution()
89
+
90
+ sol.exact = false
91
+ sol.tex = output?.tex ?? null
92
+ sol.display = output?.display ?? null
93
+ sol.fraction = new Fraction(value)
94
+ sol.fraction.exact = false
95
+ sol.variable = this.#variable
96
+
97
+ return sol
98
+ }
99
+
100
+ #makeSolution(value: InputValue<Fraction>): Solution {
101
+ if (value instanceof Fraction && !value.exact) {
102
+ return this.#makeApproximativeSolution(value.value)
103
+ }
104
+
105
+ return Solution.fromFraction(value)
106
+ }
107
+
108
+ // Solve using bissection algorithm (approximative solution)
109
+ #solveByBissection(polynom: Polynom): Solution[] {
110
+ const solutions: Solution[] = []
111
+ const degree = polynom.degree().value
112
+
113
+ // Calculate the Cauchy Bounds.
114
+ const [a, ...values] = polynom.getCoefficients()
115
+ const B = 2 + Math.max(...values.map(x => x.value / a.value))
116
+
117
+ // Cut the [-B;B] interval in *n* parts : n = 100
118
+
119
+ // Calculate the f(x) value at each points
120
+ const evaluatedPoints = this.#solveByBissection_evaluatePoints(polynom, B, 100)
121
+
122
+ // Check if there is a least n opposite couples
123
+ const couples = this.#solveByBissection_getCouples(evaluatedPoints, degree)
124
+
125
+ // All solutions fund !
126
+ couples.forEach(couple => {
127
+ const [a, b] = couple
128
+
129
+ if (a === b) {
130
+ // Exact solution
131
+ solutions.push(this.#makeSolution(a))
132
+ } else {
133
+ const bissection = this.#solveByBissection_algorithm(polynom, a, b)
134
+
135
+ if (bissection !== null) {
136
+ solutions.push(this.#makeApproximativeSolution(bissection))
137
+ }
138
+ }
139
+ })
140
+
141
+ return solutions
142
+ }
143
+
144
+ #solveByBissection_algorithm(polynom: Polynom, a: number, b: number): number | null {
145
+ let fa = polynom.evaluate(a, true) as number
146
+ let fb = polynom.evaluate(b, true) as number
147
+
148
+ if (fa * fb > 0) {
149
+ console.log("Pas de racine dans l'intervalle donné")
150
+ return null
151
+ }
152
+
153
+ let mid: number
154
+ while ((b - a) / 2 > this.#bissectionDeltaX) {
155
+ this._++
156
+
157
+ mid = (a + b) / 2
158
+ const fmid = polynom.evaluate(mid, true) as number
159
+
160
+ if (fmid === 0) {
161
+ return mid // racine exacte trouvée
162
+ } else if (fa * fmid < 0) {
163
+ b = mid
164
+ fb = fmid
165
+ } else {
166
+ a = mid
167
+ fa = fmid
168
+ }
169
+ }
170
+ return (a + b) / 2 // retourner la racine approximative
171
+ }
172
+
173
+ #solveByBissection_evaluatePoints(polynom: Polynom, bounds: number, slice: number): { x: number, fx: number }[] {
174
+
175
+ const evaluatedPoints: { x: number, fx: number }[] = []
176
+
177
+ const dx = 2 * bounds / slice
178
+
179
+ for (let searchValue = -bounds; searchValue <= bounds; searchValue += dx) {
180
+
181
+ const x = Numeric.numberCorrection(searchValue)
182
+ evaluatedPoints.push(
183
+ {
184
+ x,
185
+ fx: polynom.evaluate(x, true) as number
186
+ }
187
+ )
188
+ }
189
+
190
+ return evaluatedPoints
191
+ }
192
+
193
+ #solveByBissection_getCouples(evaluatedPoints: { x: number, fx: number }[], degree: number): [number, number][] {
194
+ const couples: [number, number][] = []
195
+
196
+ for (let index = 1; index < evaluatedPoints.length; index++) {
197
+
198
+ const value = evaluatedPoints[index]
199
+ const previous = evaluatedPoints[index - 1]
200
+
201
+ if (value.fx === 0) {
202
+ // exact value
203
+ couples.push([value.x, value.x])
204
+ } else if (value.fx * previous.fx < 0) {
205
+ // both evaluated expression are of opposite sign.
206
+ couples.push([previous.x, value.x])
207
+ }
208
+
209
+ if (couples.length === degree) {
210
+ // All couples are found.
211
+ return couples
212
+ }
213
+ }
214
+
215
+ return couples
216
+ }
217
+
218
+ #solveByFactorization(): { solutions: Solution[], reminder: Polynom } {
219
+ // Move everything to the left.
220
+
221
+ // Get the polynom on the left (on the right, it's zero)
222
+ const left = this.#leftPolynom.clone()
223
+
224
+ // The solutions of the equation
225
+ const solutions: Solution[] = []
226
+
227
+ // multiply by the lcm of the denominators
228
+ // to get rid of the fractions
229
+ const lcm = left.lcmDenominator()
230
+ if (lcm !== 1) {
231
+ left.multiply(lcm)
232
+ }
233
+
234
+ // alternative method : if there is no monom of degree zero.
235
+ // - get the monom with the smallest degree.
236
+ // - if degree>0, divide by x^{degree}
237
+ const a = left.monomByDegree().coefficient
238
+ const b = left.monomByDegree(0).coefficient
239
+ if (b.isZero()) {
240
+ solutions.push(this.#makeSolution(0))
241
+
242
+ const m = left.monoms.reduce((min, curr) => curr.degree().value < min.degree().value ? curr : min)
243
+ const k = m.coefficient
244
+ m.clone().divide(k) // make the monom unit
245
+ left.divide(m)
246
+ }
247
+
248
+ // get all dividers of a and b
249
+ const dividersA = Numeric.dividers(a.value)
250
+ const dividersB = Numeric.dividers(b.value)
251
+
252
+ // gel all possible solutions
253
+ const testingSolutions: Fraction[] = []
254
+ for (const da of dividersA) {
255
+ for (const db of dividersB) {
256
+ const f = new Fraction(db, da)
257
+ if (!testingSolutions.find(s => s.value === f.value)) {
258
+ testingSolutions.push(f.clone())
259
+ testingSolutions.push(f.opposite().clone())
260
+ }
261
+
262
+ }
263
+ }
264
+
265
+ // Each value in testingSolutions are "unique" -> juste test them to see if it evaluates to zero.
266
+ testingSolutions.forEach(f => {
267
+ if ((left.evaluate(f) as Fraction).isZero()) {
268
+ solutions.push(this.#makeSolution(f))
269
+ }
270
+ })
271
+
272
+ // divide the left polynom by the solutions (as polynom)
273
+ // to get the reduced polynom
274
+ for (const s of solutions) {
275
+ // all solutions are exact solutions.
276
+ // skip the zero solutions if it exists.
277
+ if (s.isZero()) {
278
+ continue
279
+ }
280
+
281
+ // The divider polynom
282
+ const p = left.clone()
283
+ .fromCoefficients(s.fraction.denominator, -s.fraction.numerator)
284
+
285
+ // Reset the count
286
+ s.count = 0
287
+
288
+ // Divide as long it's dividable.
289
+ while (left.isDividableBy(p)) {
290
+ left.divide(p)
291
+ s.count++
292
+ }
293
+ }
294
+
295
+ // if the reduced polynom is of degree 0, we have found all the solutions
296
+ // if the reduced polynom is of degree greater than 3, we can't solve it
297
+ if (left.degree().isZero() || left.degree().value > 3) {
298
+ // Tri des réponses
299
+ solutions.sort((a, b) => a.value - b.value)
300
+ return {solutions, reminder: left}
301
+ }
302
+
303
+ // if the reduced polynom is of degree 1 or 2, we can solve it
304
+ const zeroPolynom = left.clone().zero()
305
+
306
+ const solver = new EquationSolver(left, zeroPolynom, this.#variable)
307
+ return {
308
+ solutions: solutions
309
+ .concat(solver.solve())
310
+ .sort((a, b) => a.value - b.value),
311
+ reminder: zeroPolynom
312
+ }
313
+ }
314
+
315
+ #solveCubic_CardanFormula(): Solution[] {
316
+ // get the coefficients of the equation
317
+ const left = this.#leftPolynom
318
+
319
+ // left is a polynom ax^3+bx^2+cx+d => the solution is x = (-b±√(b^2-4ac))/2a
320
+ const a = left.monomByDegree(3).coefficient
321
+ const b = left.monomByDegree(2).coefficient
322
+ const c = left.monomByDegree(1).coefficient
323
+ const d = left.monomByDegree(0).coefficient
324
+
325
+ // normalize the coefficient by dividing by a
326
+ const an = b.clone().divide(a)
327
+ const bn = c.clone().divide(a)
328
+ const cn = d.clone().divide(a)
329
+
330
+ // Depressed cubic equation
331
+ // x^3+px+q=0
332
+ const p = bn.clone().subtract(an.clone().pow(2).divide(3))
333
+ const q = cn.clone()
334
+ .subtract(an.clone().multiply(bn).divide(3))
335
+ .add(an.clone().pow(3).multiply(2).divide(27))
336
+
337
+
338
+ // Cardan method
339
+ // X^2 + qX - p^3/27 = 0
340
+ // X^2 -SX + P = 0
341
+ // S = u^3 + v^3 = -q
342
+ // P = u^3v^3 = -p^3/27
343
+ // u^3 and v^3 are the roots of the equation
344
+ const S = q.clone().opposite()
345
+ const P = p.clone().opposite().pow(3).divide(27)
346
+
347
+ // Discriminant : delta = -(S^2 - 4P)
348
+ // delta < 0 : 1 real solution
349
+ // delta = 0 : 2 real solutions
350
+ // delta > 0 : 3 real solutions
351
+ const delta = S.clone().pow(2).subtract(P.clone().multiply(4)).opposite()
352
+
353
+ // if delta is negative, there is one real solution
354
+ if (delta.isNegative()) {
355
+ const u = q.clone().opposite().add(delta.clone().opposite().sqrt()).divide(2).root(3)
356
+ const v = q.clone().opposite().subtract(delta.clone().opposite().sqrt()).divide(2).root(3)
357
+
358
+ const x = u.clone().add(v).subtract(an.clone().divide(3))
359
+
360
+ return [this.#makeSolution(x)]
361
+ }
362
+
363
+ // if delta is zero, there are two real solutions
364
+ if (delta.isZero()) {
365
+ const u = q.clone().opposite().divide(2).root(3)
366
+
367
+ const x1 = u.clone().opposite().subtract(an.clone().divide(3))
368
+ const x2 = u.clone().multiply(2).subtract(an.clone().divide(3))
369
+
370
+ // There is only one unique solution
371
+ if (x1.isEqual(x2)) {
372
+ return [this.#makeSolution(x1)]
373
+ }
374
+
375
+ return [
376
+ this.#makeSolution(x2),
377
+ this.#makeSolution(x1)
378
+ ].sort((a, b) => a.value - b.value)
379
+ }
380
+
381
+ // if delta is positive, there are three real solutions
382
+ if (delta.isPositive()) {
383
+ const x: number[] = []
384
+ const pv = p.value,
385
+ qv = q.value,
386
+ anv = an.value
387
+
388
+ for (let i = 0; i < 3; i++) {
389
+ x.push(2 * Math.sqrt(-pv / 3) * Math.cos(Math.acos(3 * qv / (2 * pv) * Math.sqrt(-3 / pv)) / 3 + 2 * Math.PI * i / 3) - anv / 3)
390
+ }
391
+
392
+ return x
393
+ .map(v => this.#makeApproximativeSolution(v))
394
+ .sort((a, b) => a.value - b.value)
395
+
396
+ }
397
+
398
+ return []
399
+ }
400
+
401
+ #solveLinear(): Solution[] {
402
+ // The equation is linear.
403
+ const [a, b] = this.#leftPolynom.getCoefficients()
404
+
405
+ // left is a polynom ax+b => the solution is x = -b/a
406
+ const f = b.opposite().divide(a)
407
+
408
+ return [
409
+ this.#makeSolution(f)
410
+ ]
411
+ }
412
+
413
+ #solveQuadratic(): Solution[] {
414
+
415
+ // The equation is quadratic.
416
+ // We can solve it by isolating the variable.
417
+
418
+ // The monom with greatest degree must be positive.
419
+ const left = this.#leftPolynom
420
+ if (left.monomByDegree().coefficient.isNegative()) {
421
+ left.opposite()
422
+ }
423
+
424
+ // left is a polynom ax^2+bx+c => the solution is x = (-b±√(b^2-4ac))/2a
425
+ const [a, b, c] = left.getCoefficients()
426
+
427
+ // delta2 = b^2-4ac
428
+ const delta2 = b.clone().pow(2).subtract(a.clone().multiply(c).multiply(4))
429
+
430
+ // if delta2 is negative, there is no solution
431
+ if (delta2.isNegative()) {
432
+ return []
433
+ }
434
+
435
+ // if delta2 is zero, there is one solution
436
+ // if delta2 is positive, there are two solutions
437
+ // if delta2 is a square, it will be an exact solution.
438
+
439
+ if (delta2.isSquare()) {
440
+ // delta is a fraction.
441
+ // the solutions are (-b±√(b^2-4ac))/2a
442
+ const delta = delta2.sqrt()
443
+ const f1 = b.clone().opposite().subtract(delta).divide(a.clone().multiply(2))
444
+ const f2 = b.clone().opposite().add(delta).divide(a.clone().multiply(2))
445
+
446
+ // Delta is zero, there is only one solution
447
+ if (delta.isZero()) {
448
+ const sol = this.#makeSolution(f1)
449
+ sol.count = 2
450
+ return [sol]
451
+ }
452
+
453
+ // delta is positive, there are two solutions
454
+ return [
455
+ this.#makeSolution(f1),
456
+ this.#makeSolution(f2)
457
+ ].sort((a, b) => a.value - b.value)
458
+ }
459
+
460
+ // delta is not a square, there are one or two approximative solutions.
461
+ // We will use the approximate value of the square root.
462
+ // const delta = delta2.value ** 0.5
463
+ // const f1 = (-b.value + delta) / (2 * a.value)
464
+ // const f2 = (-b.value - delta) / (2 * a.value)
465
+
466
+ return this.#solveQuadratic_Output(a, b, delta2)
467
+ }
468
+
469
+ #solveQuadratic_Output(a: Fraction, b: Fraction, delta: Fraction): Solution[] {
470
+ // -b +/- sqrt(delta) / 2a
471
+ // reduce the sqrt - extract pow.
472
+ const a2 = a.clone().multiply(2)
473
+
474
+ const sol1 = new Solution()
475
+ sol1.fraction = b.clone().opposite().divide(a2.clone())
476
+ sol1.root.radical = delta.clone()
477
+ sol1.root.factor = new Fraction().one().divide(a2.clone())
478
+ sol1.exact = true
479
+
480
+ const sol2 = new Solution()
481
+ sol2.fraction = b.clone().opposite().divide(a2.clone())
482
+ sol2.root.radical = delta.clone()
483
+ sol2.root.factor = new Fraction().one().divide(a2.clone()).opposite()
484
+ sol2.exact = true
485
+
486
+ return [sol1, sol2]
487
+ .sort((a, b) => a.value - b.value)
488
+ }
489
+ }