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,558 +1,562 @@
1
- import type {
2
- EQUATION_SIGN,
3
- IAlgebra,
4
- IEquation,
5
- InputAlgebra,
6
- InputValue,
7
- IPiMathObject,
8
- literalType
9
- } from "../pimath.interface"
10
- import {Fraction} from "../coefficients"
11
- import {Numeric} from "../numeric"
12
- import {ParseError} from "../errors"
13
- import {EquationSolver} from "./equationSolver"
14
- import {Monom} from "./monom"
15
- import {Polynom} from "./polynom"
16
- import type {Solution} from "../analyze"
17
-
18
- export class Equation implements IPiMathObject<Equation>,
19
- IEquation<Equation>,
20
- IAlgebra<Equation> {
21
-
22
- // Left part of the equation
23
- #left: Polynom
24
- // Right part of the equation
25
- #right: Polynom
26
- // Signe of the equation
27
- #sign: EQUATION_SIGN
28
-
29
-
30
- constructor(equation: InputAlgebra<Polynom> | Equation)
31
- constructor(left: InputAlgebra<Polynom>, right: InputAlgebra<Polynom>, sign?: EQUATION_SIGN)
32
- constructor(left?: InputAlgebra<Polynom> | Equation, right?: InputAlgebra<Polynom>, sign?: EQUATION_SIGN) {
33
- // Default equation
34
- this.#left = new Polynom().zero()
35
- this.#right = new Polynom().zero()
36
- this.#sign = '='
37
-
38
- // Only one value, it's an equation
39
- if (left !== undefined && right === undefined) {
40
- if (left instanceof Equation) {
41
- return left.clone()
42
- } else if (typeof left === 'string') {
43
- // Parse the equation as a string.
44
- this.parse(left)
45
- }
46
-
47
- } else if (left !== undefined && right !== undefined) {
48
- // Two values, it's an equation with left and right polynoms.
49
- this.left = new Polynom(left as InputAlgebra<Polynom>)
50
- this.right = new Polynom(right)
51
- }
52
-
53
- if (sign !== undefined) {
54
- this.sign = sign
55
- }
56
-
57
- return this
58
- }
59
-
60
- // ------------------------------------------
61
- public parse = (equationString: string): this => {
62
- // Find the string separator
63
- const strSign: string | false = this.#findSign(equationString)
64
-
65
- if (strSign === false) {
66
- throw new ParseError('The equation is not valid (no sign found)')
67
- }
68
-
69
- // The StrSign is found
70
- const pStr: string[] = equationString.split(strSign)
71
-
72
- return this.create(new Polynom(pStr[0]), new Polynom(pStr[1]), this.#formatSign(strSign))
73
- }
74
-
75
- public clone = (): Equation => {
76
- return new Equation(this.#left.clone(), this.#right.clone(), this.#sign)
77
- }
78
-
79
- public get tex(): string {
80
- return `${this.#left.tex}${this.signAsTex}${this.#right.tex}`
81
- }
82
-
83
- public get display(): string {
84
- return `${this.#left.display}${this.signAsTex}${this.#right.display}`
85
- }
86
-
87
- public static isEquationString(equationString: string): boolean {
88
- // The equation sign can be one of the following:
89
- // =, <, >, <=, >=
90
-
91
- return equationString.includes('=') ||
92
- equationString.includes('<') ||
93
- equationString.includes('>') ||
94
- equationString.includes('<=') ||
95
- equationString.includes('>=')
96
- }
97
-
98
- public static makeSolutionsUnique(solutions: Solution[], sorted?: boolean): Solution[] {
99
- const solutionAsTex: string[] = []
100
- const uniqueSolutions = solutions.filter(sol => {
101
- if (!solutionAsTex.includes(sol.tex)) {
102
- solutionAsTex.push(sol.tex)
103
- return true
104
- } else {
105
- return false
106
- }
107
- })
108
-
109
- if (sorted === true) {
110
- uniqueSolutions.sort((a, b) => a.value - b.value)
111
- }
112
-
113
- return uniqueSolutions
114
- }
115
-
116
- /**
117
- * Add a value to the equation
118
- * if value is an equation, add the left part to the left part of the equation
119
- * and the right part to the right part of the equation
120
- * if value is a string, try to create an equation
121
- * if it fails, create a polynom and add it to the left and right part of the equation
122
- * @param value | Polynom | Monom | Fraction | string | monom
123
- */
124
- public add(value: InputValue<Equation | Polynom>): this {
125
-
126
- if (value instanceof Equation) {
127
- // add the left part of the equation
128
- this.#left.add(value.left)
129
- // add the right part of the equation
130
- this.#right.add(value.right)
131
-
132
- return this
133
- }
134
-
135
- if (typeof value === 'string' &&
136
- !Equation.isEquationString(value)) {
137
-
138
- return this.add(new Equation(value))
139
- }
140
-
141
- const p = new Polynom(value)
142
- this.#left.add(p)
143
- this.#right.add(p)
144
-
145
- return this
146
- }
147
-
148
- public create = (left: Polynom, right: Polynom, sign?: string): this => {
149
- this.#left = left
150
- this.#right = right
151
- this.#sign = this.#formatSign(sign ?? "=")
152
- return this
153
- }
154
-
155
- /**
156
- * Get the degree of the equation
157
- * @param letter
158
- */
159
- public degree = (letter?: string): Fraction => {
160
- return Fraction.max(this.#left.degree(letter), this.#right.degree(letter))
161
- }
162
-
163
- /**
164
- * divide an equation by a given value (transformed as a fraction)
165
- *
166
- * ```
167
- * 8x+10=6x \vert 2
168
- * 4x+5=3x
169
- * ```
170
- *
171
- * |>Alternatively with $3x-4$ maybe it's working ?
172
- * $$\frac{3x}{5}$$
173
- *
174
- * @param value
175
- * @returns {Equation}
176
- */
177
- public divide = (value: InputValue<Fraction>): this => {
178
- // Make sure we have a fraction.
179
- const F: Fraction = new Fraction(value)
180
-
181
- if (F.isZero()) {
182
- return this
183
- } else {
184
- return this.multiply(F.inverse())
185
- }
186
- }
187
-
188
- /**
189
- * Create an Equation using two polynoms.
190
- * Markdown *support* is cool
191
- * @param values
192
- * @param asNumeric
193
- */
194
- public evaluate(values: InputValue<Fraction> | literalType<number | Fraction>, asNumeric: boolean): boolean {
195
- // Evaluate the left and right part of the equation.
196
- // compare the results.
197
-
198
- // Evaluate the left and right part of the equation.
199
- const left = this.#left.evaluate(values, asNumeric)
200
- const right = this.#right.evaluate(values, asNumeric)
201
-
202
- // compare the results.
203
- if (asNumeric) {
204
- return left === right
205
- }
206
-
207
- return (left as Fraction).isEqual(right as Fraction)
208
- }
209
-
210
- // -----------------------------------------------
211
-
212
- /**
213
- * Determine if the equation contains a variable.
214
- * @param letter
215
- */
216
- public hasVariable = (letter: string): boolean => {
217
- return this.variables.includes(letter)
218
- }
219
-
220
- public isEqual(value: InputValue<Equation>): boolean {
221
- const equ = new Equation(value)
222
- return equ.left.isEqual(this.#left) && equ.right.isEqual(this.#right)
223
- }
224
-
225
- // -----------------------------------------------
226
- // Equations operations
227
-
228
- // Equations helpers
229
- public isEqualTo = (equ: Equation): boolean => {
230
- const p1 = equ.clone().moveLeft().left
231
- const p2 = this.clone().moveLeft().left
232
-
233
- // They are the same.
234
- return p1.isEqual(p2) || p1.isOppositeAt(p2)
235
- }
236
-
237
- public isLinearTo = (equ: Equation): boolean => {
238
- // Move all left.
239
- const p1 = equ.clone().moveLeft().simplify().left
240
- const p2 = this.clone().moveLeft().simplify().left
241
-
242
- // They are the same.
243
- return p1.isEqual(p2) || p1.isOppositeAt(p2)
244
- }
245
-
246
- /**
247
- * Determine if the equation contains more than one letter/variable.
248
- */
249
- public isMultiVariable = (): boolean => {
250
- return this.#left.isMultiVariable || this.#right.isMultiVariable
251
- }
252
-
253
- /**
254
- * Reorder the polynom to have only one letter on the left, the rest on the right.
255
- * @param letter
256
- */
257
- public isolate = (letter?: string): this | false => {
258
- // Determine if we can isolate the variables.
259
-
260
- // Both part of the equations must be of the first degree.
261
- if (!this.degree(letter).isOne()) {
262
- return false
263
- }
264
-
265
- // Modify the equation to isolate the asked variable.
266
- if (this.isMultiVariable()) {
267
- return false
268
- }
269
-
270
- // Isolate the letter.
271
- let mMove: Monom
272
- // Start by moving everything to the left.
273
- this.#left.subtract(this.#right)
274
- this.#right.zero()
275
- const values = [...this.#left.monoms]
276
- for (const m of values) {
277
- if (!m.hasVariable(letter)) {
278
- mMove = m.clone()
279
- this.#left.subtract(mMove)
280
- this.#right.subtract(mMove)
281
- }
282
- }
283
-
284
- // In theory, we should have only one item on the left.
285
- if (this.#left.length !== 1) {
286
- return false
287
- }
288
-
289
- const cMove: Fraction = this.#left.monoms[0].coefficient.clone()
290
- this.#left.divide(cMove)
291
- this.#right.divide(cMove)
292
- return this
293
- }
294
-
295
- // Getter and setter
296
- public get left(): Polynom {
297
- return this.#left
298
- }
299
-
300
- public set left(value: Polynom) {
301
- this.#left = value
302
- }
303
-
304
- // -----------------------------------------------
305
- public letters = (): string[] => {
306
- return [...new Set([...this.#left.variables, ...this.#right.variables])]
307
- }
308
-
309
- // -----------------------------------------------
310
- /**
311
- * Reorder will move all monoms containing a letter on the left, all the other on the right.
312
- */
313
- public moveLeft = (): this => {
314
- this.#left.subtract(this.#right)
315
- this.#right.zero()
316
- return this
317
- }
318
-
319
- /**
320
- * Multiple an equation by a fraction value.
321
- * @param value
322
- */
323
- public multiply = (value: InputValue<Fraction>): this => {
324
- // Make sure we have a fraction.
325
- const F: Fraction = new Fraction(value)
326
-
327
- // Multiply each part of the equation by the fraction
328
- this.#left.multiply(F)
329
- this.#right.multiply(F)
330
-
331
- // The sign of the inequality must be changed.
332
- if (this.#sign !== '=' && F.sign() === -1) {
333
- this.#reverseSign()
334
- }
335
-
336
- return this
337
- }
338
-
339
- public get numberOfVars(): number {
340
- return this.variables.length
341
- }
342
-
343
- public opposite = (): this => {
344
- this.#left = this.#left.opposite()
345
- this.#right = this.#right.opposite()
346
- return this
347
- }
348
-
349
- public pow(value: number): this {
350
- this.#left.pow(value)
351
- this.#right.pow(value)
352
- return this
353
- }
354
-
355
- public reduce(): this {
356
- // reduce means moving everything to the left
357
- // remove the fractions
358
- // simplify the equation
359
- // reorder the equation
360
- // start with a positive left part
361
-
362
- // Move all left. The right part is now zero.
363
- this.moveLeft()
364
-
365
- // Reduce the equation: simplify and reorder.
366
- this.#left.reduce()
367
-
368
- // Simplify the equation.
369
- this.simplify()
370
-
371
- // Make sure the first part is positive.
372
- if (this.#left.monoms[0].coefficient.isNegative()) {
373
- this.multiply(-1)
374
- }
375
-
376
- return this
377
- }
378
-
379
- public reorder = (allLeft?: boolean): this => {
380
- // Move all monoms of degree greater than 0 to the left.
381
- // and all zero degree monoms to the right.
382
- this.#left.subtract(this.#right)
383
- this.#right.zero()
384
- this.#left.reorder()
385
-
386
- // we have all left (so equal zero) : it's done !
387
- if (allLeft) {
388
- return this
389
- }
390
-
391
- // Fetch all zero degree monoms.
392
- this.#left.monoms
393
- .filter(m => m.degree().isZero())
394
- .forEach(m => {
395
- const move = m.clone()
396
- this.#left.subtract(move)
397
- this.#right.subtract(move)
398
- })
399
-
400
- // Reorder the left and right polynoms
401
- this.#left.reorder()
402
- this.#right.reorder()
403
- return this
404
- }
405
-
406
- // ------------------------------------------
407
- public replaceBy = (letter: string, P: Polynom): this => {
408
- this.#left.replaceBy(letter, P)
409
- this.#right.replaceBy(letter, P)
410
- return this
411
- }
412
-
413
- public get right(): Polynom {
414
- return this.#right
415
- }
416
-
417
- public set right(value: Polynom) {
418
- this.#right = value
419
- }
420
-
421
- // ------------------------------------------
422
- public get sign(): string {
423
- return this.#sign
424
- }
425
-
426
- public set sign(value: string) {
427
- // Set the sign value as formatted.
428
- this.#sign = this.#formatSign(value)
429
- }
430
-
431
- public get signAsTex(): string {
432
- if (this.#sign === '>=') {
433
- return '\\geq'
434
- }
435
-
436
- if (this.#sign === '<=') {
437
- return '\\leq'
438
- }
439
-
440
- return this.#sign
441
- }
442
-
443
- /**
444
- * Multiply by the lcm denominator and divide by the gcm numerators.
445
- */
446
- public simplify = (): this => {
447
- this.multiply(Numeric.lcm(...this.#left.getDenominators(), ...this.#right.getDenominators()))
448
- this.divide(Numeric.gcd(...this.#left.getNumerators(), ...this.#right.getNumerators()))
449
- return this
450
- }
451
-
452
- // -----------------------------------------------
453
- public solve = (): Solution[] => {
454
- const solver = new EquationSolver(this.clone())
455
- return solver.solve()
456
- }
457
-
458
- public split(): [Polynom, Polynom] {
459
- return [this.#left.clone(), this.#right.clone()]
460
- }
461
-
462
- public subtract(value: InputValue<Equation | Polynom>): this {
463
- if (value instanceof Equation) {
464
- this.#left.subtract(value.left)
465
- this.#right.subtract(value.right)
466
-
467
- return this
468
- }
469
-
470
- if (typeof value === 'string' &&
471
- !Equation.isEquationString(value)) {
472
- return this.subtract(new Equation(value))
473
- }
474
-
475
- const p = new Polynom(value)
476
- this.#left.subtract(p)
477
- this.#right.subtract(p)
478
-
479
- return this
480
- }
481
-
482
- public test = (values: literalType<Fraction>): boolean => {
483
- return (this.left.evaluate(values)).isEqual(this.right.evaluate(values))
484
- }
485
-
486
- public get variables(): string[] {
487
- return [...new Set(this.#right.variables.concat(this.#left.variables))]
488
- }
489
-
490
- #findSign = (equationString: string): string | false => {
491
- if (equationString.includes('geq')) {
492
- return (equationString.includes('\\geq')) ? '\\geq' : 'geq'
493
- } else if (equationString.includes('leq')) {
494
- return (equationString.includes('\\leq')) ? '\\leq' : 'leq'
495
- } else if (equationString.includes('>=')) {
496
- return '>='
497
- } else if (equationString.includes('=>')) {
498
- return '=>'
499
- } else if (equationString.includes('>')) {
500
- return '>'
501
- } else if (equationString.includes('<=')) {
502
- return '<='
503
- } else if (equationString.includes('=<')) {
504
- return '=<'
505
- } else if (equationString.includes('<')) {
506
- return '<'
507
- } else if (equationString.includes('=')) {
508
- return '='
509
- }
510
-
511
- throw new ParseError('The equation is not valid (no sign found)')
512
- }
513
-
514
- // -----------------------------------------------
515
- // Equations solving algorithms
516
- #formatSign = (signStr?: string): EQUATION_SIGN => {
517
- if (signStr === undefined) {
518
- return '='
519
- }
520
-
521
- if (signStr.includes('geq')) {
522
- return '>='
523
- } else if (signStr.includes('>=')) {
524
- return '>='
525
- } else if (signStr.includes('=>')) {
526
- return '>='
527
- } else if (signStr.includes('>')) {
528
- return '>'
529
- } else if (signStr.includes('leq')) {
530
- return '<='
531
- } else if (signStr.includes('<=')) {
532
- return '<='
533
- } else if (signStr.includes('=<')) {
534
- return '<='
535
- } else if (signStr.includes('<')) {
536
- return '<'
537
- } else {
538
- return '='
539
- }
540
- }
541
-
542
- #reverseSign = (): this => {
543
- if (this.#sign === '=') {
544
- return this
545
- }
546
-
547
- if (this.#sign.includes('<')) {
548
- this.#sign.replace('<', '>')
549
- return this
550
- }
551
- if (this.#sign.includes('>')) {
552
- this.#sign.replace('>', '<')
553
- return this
554
- }
555
-
556
- return this
557
- }
558
- }
1
+ import type {
2
+ EQUATION_SIGN,
3
+ IAlgebra,
4
+ IEquation,
5
+ InputAlgebra,
6
+ InputValue,
7
+ IPiMathObject,
8
+ literalType
9
+ } from "../pimath.interface"
10
+ import {Fraction} from "../coefficients"
11
+ import {Numeric} from "../numeric"
12
+ import {NotImplementedError, ParseError} from "../errors"
13
+ import {EquationSolver} from "./equationSolver"
14
+ import {Monom} from "./monom"
15
+ import {Polynom} from "./polynom"
16
+ import type {Solution} from "../analyze"
17
+
18
+ export class Equation implements IPiMathObject<Equation>,
19
+ IEquation<Equation>,
20
+ IAlgebra<Equation> {
21
+
22
+ // Left part of the equation
23
+ #left: Polynom
24
+ // Right part of the equation
25
+ #right: Polynom
26
+ // Signe of the equation
27
+ #sign: EQUATION_SIGN
28
+
29
+
30
+ constructor(equation: InputAlgebra<Polynom> | Equation)
31
+ constructor(left: InputAlgebra<Polynom>, right: InputAlgebra<Polynom>, sign?: EQUATION_SIGN)
32
+ constructor(left?: InputAlgebra<Polynom> | Equation, right?: InputAlgebra<Polynom>, sign?: EQUATION_SIGN) {
33
+ // Default equation
34
+ this.#left = new Polynom().zero()
35
+ this.#right = new Polynom().zero()
36
+ this.#sign = '='
37
+
38
+ // Only one value, it's an equation
39
+ if (left !== undefined && right === undefined) {
40
+ if (left instanceof Equation) {
41
+ return left.clone()
42
+ } else if (typeof left === 'string') {
43
+ // Parse the equation as a string.
44
+ this.parse(left)
45
+ }
46
+
47
+ } else if (left !== undefined && right !== undefined) {
48
+ // Two values, it's an equation with left and right polynoms.
49
+ this.left = new Polynom(left as InputAlgebra<Polynom>)
50
+ this.right = new Polynom(right)
51
+ }
52
+
53
+ if (sign !== undefined) {
54
+ this.sign = sign
55
+ }
56
+
57
+ return this
58
+ }
59
+
60
+ // ------------------------------------------
61
+ parse = (equationString: string): this => {
62
+ // Find the string separator
63
+ const strSign: string | false = this.#findSign(equationString)
64
+
65
+ if (strSign === false) {
66
+ throw new ParseError('The equation is not valid (no sign found)')
67
+ }
68
+
69
+ // The StrSign is found
70
+ const pStr: string[] = equationString.split(strSign)
71
+
72
+ return this.create(new Polynom(pStr[0]), new Polynom(pStr[1]), this.#formatSign(strSign))
73
+ }
74
+
75
+ copy(): this {
76
+ throw new NotImplementedError("copy() is not yet implemented for Equation")
77
+ }
78
+
79
+ clone = (): Equation => {
80
+ return new Equation(this.#left.clone(), this.#right.clone(), this.#sign)
81
+ }
82
+
83
+ get tex(): string {
84
+ return `${this.#left.tex}${this.signAsTex}${this.#right.tex}`
85
+ }
86
+
87
+ get display(): string {
88
+ return `${this.#left.display}${this.signAsTex}${this.#right.display}`
89
+ }
90
+
91
+ static isEquationString(equationString: string): boolean {
92
+ // The equation sign can be one of the following:
93
+ // =, <, >, <=, >=
94
+
95
+ return equationString.includes('=') ||
96
+ equationString.includes('<') ||
97
+ equationString.includes('>') ||
98
+ equationString.includes('<=') ||
99
+ equationString.includes('>=')
100
+ }
101
+
102
+ static makeSolutionsUnique(solutions: Solution[], sorted?: boolean): Solution[] {
103
+ const solutionAsTex: string[] = []
104
+ const uniqueSolutions = solutions.filter(sol => {
105
+ if (!solutionAsTex.includes(sol.tex)) {
106
+ solutionAsTex.push(sol.tex)
107
+ return true
108
+ } else {
109
+ return false
110
+ }
111
+ })
112
+
113
+ if (sorted === true) {
114
+ uniqueSolutions.sort((a, b) => a.value - b.value)
115
+ }
116
+
117
+ return uniqueSolutions
118
+ }
119
+
120
+ /**
121
+ * Add a value to the equation
122
+ * if value is an equation, add the left part to the left part of the equation
123
+ * and the right part to the right part of the equation
124
+ * if value is a string, try to create an equation
125
+ * if it fails, create a polynom and add it to the left and right part of the equation
126
+ * @param value | Polynom | Monom | Fraction | string | monom
127
+ */
128
+ add(value: InputValue<Equation | Polynom>): this {
129
+
130
+ if (value instanceof Equation) {
131
+ // add the left part of the equation
132
+ this.#left.add(value.left)
133
+ // add the right part of the equation
134
+ this.#right.add(value.right)
135
+
136
+ return this
137
+ }
138
+
139
+ if (typeof value === 'string' &&
140
+ !Equation.isEquationString(value)) {
141
+
142
+ return this.add(new Equation(value))
143
+ }
144
+
145
+ const p = new Polynom(value)
146
+ this.#left.add(p)
147
+ this.#right.add(p)
148
+
149
+ return this
150
+ }
151
+
152
+ create = (left: Polynom, right: Polynom, sign?: string): this => {
153
+ this.#left = left
154
+ this.#right = right
155
+ this.#sign = this.#formatSign(sign ?? "=")
156
+ return this
157
+ }
158
+
159
+ /**
160
+ * Get the degree of the equation
161
+ * @param letter
162
+ */
163
+ degree = (letter?: string): Fraction => {
164
+ return Fraction.max(this.#left.degree(letter), this.#right.degree(letter))
165
+ }
166
+
167
+ /**
168
+ * divide an equation by a given value (transformed as a fraction)
169
+ *
170
+ * ```
171
+ * 8x+10=6x \vert 2
172
+ * 4x+5=3x
173
+ * ```
174
+ *
175
+ * |>Alternatively with $3x-4$ maybe it's working ?
176
+ * $$\frac{3x}{5}$$
177
+ *
178
+ * @param value
179
+ * @returns {Equation}
180
+ */
181
+ divide = (value: InputValue<Fraction>): this => {
182
+ // Make sure we have a fraction.
183
+ const F: Fraction = new Fraction(value)
184
+
185
+ if (F.isZero()) {
186
+ return this
187
+ } else {
188
+ return this.multiply(F.inverse())
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Create an Equation using two polynoms.
194
+ * Markdown *support* is cool
195
+ * @param values
196
+ * @param asNumeric
197
+ */
198
+ evaluate(values: InputValue<Fraction> | literalType<number | Fraction>, asNumeric: boolean): boolean {
199
+ // Evaluate the left and right part of the equation.
200
+ // compare the results.
201
+
202
+ // Evaluate the left and right part of the equation.
203
+ const left = this.#left.evaluate(values, asNumeric)
204
+ const right = this.#right.evaluate(values, asNumeric)
205
+
206
+ // compare the results.
207
+ if (asNumeric) {
208
+ return left === right
209
+ }
210
+
211
+ return (left as Fraction).isEqual(right as Fraction)
212
+ }
213
+
214
+ // -----------------------------------------------
215
+
216
+ /**
217
+ * Determine if the equation contains a variable.
218
+ * @param letter
219
+ */
220
+ hasVariable = (letter: string): boolean => {
221
+ return this.variables.includes(letter)
222
+ }
223
+
224
+ isEqual(value: InputValue<Equation>): boolean {
225
+ const equ = new Equation(value)
226
+ return equ.left.isEqual(this.#left) && equ.right.isEqual(this.#right)
227
+ }
228
+
229
+ // -----------------------------------------------
230
+ // Equations operations
231
+
232
+ // Equations helpers
233
+ isEqualTo = (equ: Equation): boolean => {
234
+ const p1 = equ.clone().moveLeft().left
235
+ const p2 = this.clone().moveLeft().left
236
+
237
+ // They are the same.
238
+ return p1.isEqual(p2) || p1.isOppositeAt(p2)
239
+ }
240
+
241
+ isLinearTo = (equ: Equation): boolean => {
242
+ // Move all left.
243
+ const p1 = equ.clone().moveLeft().simplify().left
244
+ const p2 = this.clone().moveLeft().simplify().left
245
+
246
+ // They are the same.
247
+ return p1.isEqual(p2) || p1.isOppositeAt(p2)
248
+ }
249
+
250
+ /**
251
+ * Determine if the equation contains more than one letter/variable.
252
+ */
253
+ isMultiVariable = (): boolean => {
254
+ return this.#left.isMultiVariable || this.#right.isMultiVariable
255
+ }
256
+
257
+ /**
258
+ * Reorder the polynom to have only one letter on the left, the rest on the right.
259
+ * @param letter
260
+ */
261
+ isolate = (letter?: string): this | false => {
262
+ // Determine if we can isolate the variables.
263
+
264
+ // Both part of the equations must be of the first degree.
265
+ if (!this.degree(letter).isOne()) {
266
+ return false
267
+ }
268
+
269
+ // Modify the equation to isolate the asked variable.
270
+ if (this.isMultiVariable()) {
271
+ return false
272
+ }
273
+
274
+ // Isolate the letter.
275
+ let mMove: Monom
276
+ // Start by moving everything to the left.
277
+ this.#left.subtract(this.#right)
278
+ this.#right.zero()
279
+ const values = [...this.#left.monoms]
280
+ for (const m of values) {
281
+ if (!m.hasVariable(letter)) {
282
+ mMove = m.clone()
283
+ this.#left.subtract(mMove)
284
+ this.#right.subtract(mMove)
285
+ }
286
+ }
287
+
288
+ // In theory, we should have only one item on the left.
289
+ if (this.#left.length !== 1) {
290
+ return false
291
+ }
292
+
293
+ const cMove: Fraction = this.#left.monoms[0].coefficient.clone()
294
+ this.#left.divide(cMove)
295
+ this.#right.divide(cMove)
296
+ return this
297
+ }
298
+
299
+ // Getter and setter
300
+ get left(): Polynom {
301
+ return this.#left
302
+ }
303
+
304
+ set left(value: Polynom) {
305
+ this.#left = value
306
+ }
307
+
308
+ // -----------------------------------------------
309
+ letters = (): string[] => {
310
+ return [...new Set([...this.#left.variables, ...this.#right.variables])]
311
+ }
312
+
313
+ // -----------------------------------------------
314
+ /**
315
+ * Reorder will move all monoms containing a letter on the left, all the other on the right.
316
+ */
317
+ moveLeft = (): this => {
318
+ this.#left.subtract(this.#right)
319
+ this.#right.zero()
320
+ return this
321
+ }
322
+
323
+ /**
324
+ * Multiple an equation by a fraction value.
325
+ * @param value
326
+ */
327
+ multiply = (value: InputValue<Fraction>): this => {
328
+ // Make sure we have a fraction.
329
+ const F: Fraction = new Fraction(value)
330
+
331
+ // Multiply each part of the equation by the fraction
332
+ this.#left.multiply(F)
333
+ this.#right.multiply(F)
334
+
335
+ // The sign of the inequality must be changed.
336
+ if (this.#sign !== '=' && F.sign() === -1) {
337
+ this.#reverseSign()
338
+ }
339
+
340
+ return this
341
+ }
342
+
343
+ get numberOfVars(): number {
344
+ return this.variables.length
345
+ }
346
+
347
+ opposite = (): this => {
348
+ this.#left = this.#left.opposite()
349
+ this.#right = this.#right.opposite()
350
+ return this
351
+ }
352
+
353
+ pow(value: number): this {
354
+ this.#left.pow(value)
355
+ this.#right.pow(value)
356
+ return this
357
+ }
358
+
359
+ reduce(): this {
360
+ // reduce means moving everything to the left
361
+ // remove the fractions
362
+ // simplify the equation
363
+ // reorder the equation
364
+ // start with a positive left part
365
+
366
+ // Move all left. The right part is now zero.
367
+ this.moveLeft()
368
+
369
+ // Reduce the equation: simplify and reorder.
370
+ this.#left.reduce()
371
+
372
+ // Simplify the equation.
373
+ this.simplify()
374
+
375
+ // Make sure the first part is positive.
376
+ if (this.#left.monoms[0].coefficient.isNegative()) {
377
+ this.multiply(-1)
378
+ }
379
+
380
+ return this
381
+ }
382
+
383
+ reorder = (allLeft?: boolean): this => {
384
+ // Move all monoms of degree greater than 0 to the left.
385
+ // and all zero degree monoms to the right.
386
+ this.#left.subtract(this.#right)
387
+ this.#right.zero()
388
+ this.#left.reorder()
389
+
390
+ // we have all left (so equal zero) : it's done !
391
+ if (allLeft) {
392
+ return this
393
+ }
394
+
395
+ // Fetch all zero degree monoms.
396
+ this.#left.monoms
397
+ .filter(m => m.degree().isZero())
398
+ .forEach(m => {
399
+ const move = m.clone()
400
+ this.#left.subtract(move)
401
+ this.#right.subtract(move)
402
+ })
403
+
404
+ // Reorder the left and right polynoms
405
+ this.#left.reorder()
406
+ this.#right.reorder()
407
+ return this
408
+ }
409
+
410
+ // ------------------------------------------
411
+ replaceBy = (letter: string, P: Polynom): this => {
412
+ this.#left.replaceBy(letter, P)
413
+ this.#right.replaceBy(letter, P)
414
+ return this
415
+ }
416
+
417
+ get right(): Polynom {
418
+ return this.#right
419
+ }
420
+
421
+ set right(value: Polynom) {
422
+ this.#right = value
423
+ }
424
+
425
+ // ------------------------------------------
426
+ get sign(): string {
427
+ return this.#sign
428
+ }
429
+
430
+ set sign(value: string) {
431
+ // Set the sign value as formatted.
432
+ this.#sign = this.#formatSign(value)
433
+ }
434
+
435
+ get signAsTex(): string {
436
+ if (this.#sign === '>=') {
437
+ return '\\geq'
438
+ }
439
+
440
+ if (this.#sign === '<=') {
441
+ return '\\leq'
442
+ }
443
+
444
+ return this.#sign
445
+ }
446
+
447
+ /**
448
+ * Multiply by the lcm denominator and divide by the gcm numerators.
449
+ */
450
+ simplify = (): this => {
451
+ this.multiply(Numeric.lcm(...this.#left.getDenominators(), ...this.#right.getDenominators()))
452
+ this.divide(Numeric.gcd(...this.#left.getNumerators(), ...this.#right.getNumerators()))
453
+ return this
454
+ }
455
+
456
+ // -----------------------------------------------
457
+ solve = (): Solution[] => {
458
+ const solver = new EquationSolver(this.clone())
459
+ return solver.solve()
460
+ }
461
+
462
+ split(): [Polynom, Polynom] {
463
+ return [this.#left.clone(), this.#right.clone()]
464
+ }
465
+
466
+ subtract(value: InputValue<Equation | Polynom>): this {
467
+ if (value instanceof Equation) {
468
+ this.#left.subtract(value.left)
469
+ this.#right.subtract(value.right)
470
+
471
+ return this
472
+ }
473
+
474
+ if (typeof value === 'string' &&
475
+ !Equation.isEquationString(value)) {
476
+ return this.subtract(new Equation(value))
477
+ }
478
+
479
+ const p = new Polynom(value)
480
+ this.#left.subtract(p)
481
+ this.#right.subtract(p)
482
+
483
+ return this
484
+ }
485
+
486
+ test = (values: literalType<Fraction>): boolean => {
487
+ return (this.left.evaluate(values)).isEqual(this.right.evaluate(values))
488
+ }
489
+
490
+ get variables(): string[] {
491
+ return [...new Set(this.#right.variables.concat(this.#left.variables))]
492
+ }
493
+
494
+ #findSign = (equationString: string): string | false => {
495
+ if (equationString.includes('geq')) {
496
+ return (equationString.includes('\\geq')) ? '\\geq' : 'geq'
497
+ } else if (equationString.includes('leq')) {
498
+ return (equationString.includes('\\leq')) ? '\\leq' : 'leq'
499
+ } else if (equationString.includes('>=')) {
500
+ return '>='
501
+ } else if (equationString.includes('=>')) {
502
+ return '=>'
503
+ } else if (equationString.includes('>')) {
504
+ return '>'
505
+ } else if (equationString.includes('<=')) {
506
+ return '<='
507
+ } else if (equationString.includes('=<')) {
508
+ return '=<'
509
+ } else if (equationString.includes('<')) {
510
+ return '<'
511
+ } else if (equationString.includes('=')) {
512
+ return '='
513
+ }
514
+
515
+ throw new ParseError('The equation is not valid (no sign found)')
516
+ }
517
+
518
+ // -----------------------------------------------
519
+ // Equations solving algorithms
520
+ #formatSign = (signStr?: string): EQUATION_SIGN => {
521
+ if (signStr === undefined) {
522
+ return '='
523
+ }
524
+
525
+ if (signStr.includes('geq')) {
526
+ return '>='
527
+ } else if (signStr.includes('>=')) {
528
+ return '>='
529
+ } else if (signStr.includes('=>')) {
530
+ return '>='
531
+ } else if (signStr.includes('>')) {
532
+ return '>'
533
+ } else if (signStr.includes('leq')) {
534
+ return '<='
535
+ } else if (signStr.includes('<=')) {
536
+ return '<='
537
+ } else if (signStr.includes('=<')) {
538
+ return '<='
539
+ } else if (signStr.includes('<')) {
540
+ return '<'
541
+ } else {
542
+ return '='
543
+ }
544
+ }
545
+
546
+ #reverseSign = (): this => {
547
+ if (this.#sign === '=') {
548
+ return this
549
+ }
550
+
551
+ if (this.#sign.includes('<')) {
552
+ this.#sign.replace('<', '>')
553
+ return this
554
+ }
555
+ if (this.#sign.includes('>')) {
556
+ this.#sign.replace('>', '<')
557
+ return this
558
+ }
559
+
560
+ return this
561
+ }
562
+ }