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,1229 +1,1233 @@
1
- /**
2
- * Polynom module contains everything necessary to handle polynoms.*
3
- */
4
- import {ShutingYard, ShutingyardType, type Token} from "piexpression"
5
- import {InvalidArgumentError, MathError, ParseError} from "../errors"
6
- import type {
7
- IAlgebra,
8
- IAnalyse,
9
- IExpression,
10
- InputAlgebra,
11
- InputValue,
12
- IPiMathObject,
13
- literalType,
14
- TABLE_OF_SIGNS,
15
- TABLE_OF_SIGNS_VALUES
16
- } from "../pimath.interface"
17
- import {Fraction} from "../coefficients"
18
- import {Numeric} from '../numeric'
19
- import {EquationSolver} from './equationSolver'
20
- import {Monom} from './monom'
21
- import {replace_in_array} from "../helpers"
22
- import {operation_pow} from "./operations"
23
- import {Solution} from "../analyze/solution"
24
-
25
-
26
- export type PolynomParsingType = InputValue<Polynom> | Monom
27
-
28
- export interface IEuclidean {
29
- quotient: Polynom,
30
- reminder: Polynom
31
- }
32
-
33
- /**
34
- * Polynom class can handle polynoms, reorder, resolve, ...
35
- * ```
36
- * let P = new Polynom('3x-4')
37
- * ```
38
- */
39
- export class Polynom implements IPiMathObject<Polynom>,
40
- IExpression<Polynom>,
41
- IAnalyse<Polynom>,
42
- IAlgebra<Polynom> {
43
-
44
- #defaultVariable = 'x'
45
- #factors: Polynom[]
46
- #monoms: Monom[]
47
- #roots: Solution[]
48
- #rootsCache = false
49
-
50
- constructor(value: InputValue<Fraction>)
51
- constructor(value: string)
52
- constructor(value: Monom)
53
- constructor(value: Polynom)
54
- constructor(...values: InputValue<Fraction>[])
55
- constructor(...values: InputAlgebra<Polynom>[])
56
- constructor(polynomString?: InputAlgebra<Polynom>, ...values: InputAlgebra<Fraction>[]) {
57
- this.#monoms = []
58
- this.#factors = []
59
- this.#roots = []
60
-
61
- if (polynomString !== undefined) {
62
- this.parse(polynomString, ...values)
63
- }
64
- return this
65
- }
66
-
67
-
68
- /**
69
- * Parse a string to a polynom.
70
- * @param inputStr
71
- * @param values
72
- */
73
- public parse(inputStr: PolynomParsingType, ...values: InputAlgebra<Monom>[]): this {
74
- // Reset the main variables.
75
- this.#monoms = []
76
- this.#factors = []
77
- this.#invalidateCache()
78
-
79
- if (typeof inputStr === 'string') {
80
- return this.#parseString(inputStr, ...values as InputValue<Fraction>[])
81
- } else if (
82
- (typeof inputStr === 'number' || inputStr instanceof Fraction || inputStr instanceof Monom)
83
- && (values.length === 0)
84
- ) {
85
- this.#monoms.push(new Monom(inputStr as Monom))
86
- } else if (inputStr instanceof Monom && values.length > 0) {
87
- this.#monoms.push(new Monom(inputStr))
88
- values.forEach(m => {
89
- this.#monoms.push(new Monom(m as Monom))
90
- })
91
- } else if (inputStr instanceof Polynom) {
92
- for (const m of inputStr.monoms) {
93
- this.#monoms.push(m.clone())
94
- }
95
- }
96
-
97
-
98
- return this
99
- }
100
-
101
- /**
102
- * Clone the polynom
103
- */
104
- public clone(): Polynom {
105
- const P = new Polynom()
106
- const M: Monom[] = []
107
- P.#defaultVariable = this.#defaultVariable
108
-
109
- for (const m of this.#monoms) {
110
- M.push(m.clone())
111
- }
112
-
113
- P.monoms = M
114
-
115
- return P
116
- }
117
-
118
- public get tex(): string {
119
- return this.#genDisplay('tex')
120
- }
121
-
122
- public get display(): string {
123
- return this.#genDisplay()
124
- }
125
-
126
- static xMultiply(...polynoms: InputValue<Polynom>[]): Polynom {
127
- const result = new Polynom().one()
128
-
129
- polynoms.forEach(P => {
130
- result.multiply(P)
131
- })
132
-
133
- return result
134
- }
135
-
136
- public add(...values: InputAlgebra<Polynom>[]): this {
137
-
138
- for (const value of values) {
139
- if (value instanceof Polynom) {
140
- this.#monoms = this.#monoms.concat(value.monoms.map(m => m.clone()))
141
- } else if (value instanceof Monom) {
142
- this.#monoms.push(value.clone())
143
- } else if (typeof value === "number" && Number.isSafeInteger(value)) {
144
- this.#monoms.push(new Monom(value.toString()))
145
- } else {
146
- this.#monoms.push(new Monom(value))
147
- }
148
- }
149
-
150
-
151
- return this.reduce()
152
- }
153
-
154
- public commonMonom(): Monom {
155
- const M = new Monom().one()
156
- const numerator: number = this.gcdNumerator()
157
- const denominator: number = this.gcdDenominator()
158
-
159
- M.coefficient = new Fraction(numerator, denominator)
160
- for (const L of this.variables) {
161
- // Initialize the setLetter with the max degree
162
- M.setLetter(L, this.degree(L))
163
- for (const m of this.#monoms) {
164
- M.setLetter(L, Fraction.min(m.degree(L), M.degree(L)))
165
- if (M.degree(L).isZero()) {
166
- break
167
- }
168
-
169
- }
170
- }
171
- return M
172
- }
173
-
174
- public degree(letter?: string): Fraction {
175
- let d: Fraction = new Fraction().zero()
176
- for (const m of this.#monoms) {
177
- d = Fraction.max(m.degree(letter).value, d)
178
- }
179
-
180
- return d
181
- }
182
-
183
- public derivative(letter?: string): Polynom {
184
- const dP = new Polynom()
185
-
186
- for (const m of this.#monoms) {
187
- dP.add(m.derivative(letter))
188
- }
189
-
190
- return dP.reduce()
191
- }
192
-
193
- public divide(value: InputAlgebra<Polynom>): this {
194
-
195
- if (value instanceof Fraction) {
196
- return this.#divideByFraction(value)
197
- } else if (typeof value === 'number' && Number.isSafeInteger(value)) {
198
- return this.#divideByInteger(value)
199
- } else if (value instanceof Monom) {
200
- return this.divide(new Polynom(value))
201
- } else if (value instanceof Polynom) {
202
- if (value.monoms.length === 1 && value.variables.length === 0) {
203
- return this.#divideByFraction(value.monoms[0].coefficient)
204
- } else {
205
- const {quotient, reminder} = this.euclidean(value)
206
- if (reminder.isZero()) {
207
- this.#monoms = quotient.monoms
208
- this.#invalidateCache()
209
- return this
210
- }
211
- }
212
- } else if (typeof value === 'string') {
213
- return this.divide(new Polynom(value))
214
- }
215
-
216
- throw new MathError(`Cannot divide by ${value as unknown as string}`)
217
- }
218
-
219
- public empty(): this {
220
- this.#monoms = []
221
- this.#invalidateCache()
222
- return this
223
- }
224
-
225
- /**
226
- * Divide the current polynom by another polynom.
227
- * @param P
228
- * returns {quotient: Polynom, reminder: Polynom}
229
- */
230
- public euclidean(P: Polynom): IEuclidean {
231
- const letter: string | undefined = P.variables[0]
232
- const quotient: Polynom = new Polynom().zero()
233
- const reminder: Polynom = this.clone().reorder(letter)
234
-
235
- // There is no variable - means it's a number
236
- if (P.variables.length === 0) {
237
- const q = this.clone().divide(P)
238
-
239
- return {
240
- quotient: q.reduce(),
241
- reminder: new Polynom().zero()
242
- }
243
- }
244
-
245
- if (!this.degree(letter).isNatural() || !P.degree(letter).isNatural()) {
246
- throw new MathError('Euclidean division requires integer degrees')
247
- }
248
-
249
- // Get at least a letter
250
- const maxMP: Monom = P.monomByDegree(undefined, letter)
251
- const degreeP: Fraction = P.degree(letter)
252
-
253
- let newM: Monom
254
-
255
- // Make the Euclidean division of the two polynoms.
256
- let MaxIteration = this.degree(letter).value - degreeP.value + 1
257
-
258
- while (reminder.degree(letter).isGeq(degreeP) && MaxIteration > 0) {
259
- MaxIteration--
260
-
261
- // Get the greatest monom divided by the max monom of the divider
262
- newM = reminder.monomByDegree(undefined, letter).clone().divide(maxMP)
263
-
264
- if (newM.isZero()) {
265
- continue
266
- }
267
-
268
- // Get the new quotient and reminder.
269
- quotient.add(newM)
270
- reminder.subtract(P.clone().multiply(newM)).reduce()
271
-
272
- // Check if the reminder is zero.
273
- if (newM.degree(letter).isZero()) {
274
- break
275
- }
276
- }
277
-
278
- quotient.reduce()
279
- reminder.reduce()
280
- return {quotient, reminder}
281
- }
282
-
283
- public evaluate(values: literalType<Fraction | number> | InputValue<Fraction>, asNumeric: true): number
284
- public evaluate(values: literalType<Fraction | number> | InputValue<Fraction>, asNumeric?: false): Fraction
285
- public evaluate(values: literalType<Fraction | number> | InputValue<Fraction>, asNumeric?: boolean): Fraction | number
286
- public evaluate(values: literalType<Fraction | number> | InputValue<Fraction>, asNumeric?: boolean): Fraction | number {
287
- // Return the numeric value without using Fraction
288
- if (asNumeric) {
289
- return this.#evaluateAsNumeric(values)
290
- }
291
-
292
- // Build the evaluated fraction
293
- const r = new Fraction().zero()
294
- this.#monoms.forEach(monom => {
295
- r.add(monom.evaluate(values, asNumeric))
296
- })
297
-
298
- return r
299
- }
300
-
301
- // -------------------------------------
302
- /**
303
- * Factorize a polynom and store the best results in factors.
304
- * @param _letter
305
- * TODO: Handle other letter than 'x'.
306
- */
307
- public factorize(_letter?: string): Polynom[] {
308
- this.#factors = []
309
-
310
- // There is only one monom - it's already considered as factor.
311
- if (this.monoms.length === 1) return [this.clone()]
312
-
313
- let P = this.clone().reorder()
314
-
315
- // Extract the common monom
316
- // 2x^3+6x^2 => 2x^2
317
- const M = P.commonMonom()
318
-
319
- // If the polynom starts with a negative monom, factorize it.
320
- if (P.monomByDegree().coefficient.isStrictlyNegative()) M.opposite()
321
-
322
- if (!M.isOne()) {
323
- this.#factors.push(new Polynom(M))
324
- P = P.euclidean(this.#factors[0]).quotient
325
- }
326
-
327
- const solutions = new EquationSolver(P).solve()
328
-
329
- if (solutions.length === 0) {
330
- if (this.#factors.length === 0) {
331
- // No common monom was extracted: the whole polynomial is irreducible
332
- this.#factors = [this.clone()]
333
- } else {
334
- // Common monom was extracted: P is the irreducible remaining part
335
- this.#factors.push(P)
336
- }
337
- return this.#factors
338
- }
339
-
340
- solutions.forEach(sol => {
341
- if (sol.exact && sol.root.isZero()) {
342
- for (let i = 0; i < sol.count; i++) {
343
- if (sol.fraction.isRational()) {
344
- this.#factors.push(new Polynom().fromCoefficients(sol.fraction.denominator, -sol.fraction.numerator))
345
- } else {
346
- this.#factors.push(new Polynom().fromCoefficients(1, sol.fraction.clone().opposite()))
347
- }
348
- }
349
- }
350
- })
351
-
352
- // Check if we have all factors.
353
- const fDegrees = this.#factors
354
- .map(f => f.degree().value)
355
- .reduce((a, b) => a + b, 0)
356
-
357
- if (fDegrees < this.degree().value) {
358
- // missing polynom.
359
- const fPolynom = Polynom.xMultiply(...this.#factors)
360
- this.#factors.push(this.clone().divide(fPolynom))
361
- }
362
-
363
- return this.#factors
364
- }
365
-
366
- get factors(): Polynom[] {
367
- if (this.#factors.length === 0 && !this.isZero()) {
368
- return this.factorize()
369
- }
370
-
371
- return this.#factors
372
- }
373
-
374
- public fromCoefficients(...values: InputValue<Fraction>[]): this {
375
-
376
- this.#monoms = []
377
- const letter = this.#defaultVariable ?? 'x'
378
- values.reverse().forEach((coeff, index) => {
379
- const monom = new Monom()
380
- monom.coefficient = new Fraction(coeff)
381
- monom.setLetter(letter, index)
382
-
383
- this.#monoms.push(monom)
384
- })
385
-
386
- this.#invalidateCache()
387
- return this.reorder()
388
- }
389
-
390
- public gcdDenominator(): number {
391
- return Numeric.gcd(...this.getDenominators())
392
- }
393
-
394
- public gcdNumerator(): number {
395
- return Numeric.gcd(...this.getNumerators())
396
- }
397
-
398
- public getCoefficients(): Fraction[] {
399
- if (!this.degree().isNatural()) {
400
- throw new MathError('getCoefficients() requires a polynomial with integer degrees')
401
- }
402
-
403
- // Assume there is only one letter.
404
- const orderedPolynom = this.clone().reorder()
405
-
406
- const length = this.degree().value + 1
407
- const coeffs = Array.from({length}, () => new Fraction(0))
408
-
409
- orderedPolynom.monoms.forEach(monom => {
410
- const index = length - monom.degree().value - 1
411
- coeffs[index] = monom.coefficient.clone()
412
- })
413
-
414
- return coeffs
415
- }
416
-
417
- // Next functions are used for for commonMonom, which is used in the factorize method.
418
- public getDenominators(): number[] {
419
- const denominators: number[] = []
420
- for (const m of this.#monoms) {
421
- denominators.push(m.coefficient.denominator)
422
- }
423
-
424
- return denominators
425
- }
426
-
427
- public getNumerators(): number[] {
428
- const numerators: number[] = []
429
- for (const m of this.#monoms) {
430
- numerators.push(m.coefficient.numerator)
431
- }
432
-
433
- return numerators
434
- }
435
-
436
- public getZeroes(): Solution[] {
437
- if (this.degree().isZero()) {
438
- return []
439
- }
440
-
441
- this.roots = new EquationSolver(this.clone()).solve()
442
- return this.roots
443
- }
444
-
445
- public hasVariable(letter: string): boolean {
446
- return this.variables.includes(letter)
447
- }
448
-
449
- public integrate(a: InputValue<Fraction>, b: InputValue<Fraction>, letter = 'x'): Fraction {
450
- const primitive = this.primitive(letter)
451
-
452
- const valuesA: literalType<Fraction> = {}
453
- const valuesB: literalType<Fraction> = {}
454
-
455
- valuesA[letter] = new Fraction(a)
456
- valuesB[letter] = new Fraction(b)
457
-
458
- return (primitive.evaluate(valuesB)).subtract(primitive.evaluate(valuesA))
459
- }
460
-
461
- public inverse(): Polynom | undefined {
462
- return undefined
463
- }
464
-
465
- public isDeveloped(polynomString: string): boolean {
466
- // Start by removing the parenthesis after a "power"
467
- const pString = polynomString.replaceAll(/\^\(([-0-9/]+)\)/g, '$1')
468
-
469
- // There is at least one parenthesis - it is not developed.
470
- if (pString.includes('(') || pString.includes(')')) {
471
- return false
472
- }
473
-
474
-
475
- // Build the polynom. A malformed string throws (ParseError) instead of
476
- // being silently treated as "not developed".
477
- const P = new Polynom(polynomString)
478
-
479
- // Both polynom aren't the same (once developed and reduced => they cannot be equivalent)
480
- if (!this.isEqual(P)) {
481
- return false
482
- }
483
-
484
-
485
- // Check that everything is completely developed. Actually, there are no parentheses... so it is fully developed
486
- return true
487
- }
488
-
489
- public isDividableBy(div: Polynom): boolean {
490
- // Quick evaluation.
491
- if (div.degree().isOne()) {
492
- const zero = div.getZeroes()[0]
493
-
494
- if (zero.exact) {
495
- return (this.evaluate(zero.fraction)).isZero()
496
- } else {
497
- return false
498
- }
499
-
500
- } else {
501
- const {reminder} = this.euclidean(div)
502
- return reminder.isZero()
503
- }
504
- }
505
-
506
- public isEqual(P: Polynom): boolean {
507
- return this.#compare(P, '=')
508
- }
509
-
510
- public get isMultiVariable(): boolean {
511
- // Determine if a monom has more than one variable.
512
- return this.#monoms.some(m => m.variables.length > 1)
513
- }
514
-
515
- public isOne(): boolean {
516
- return this.#monoms.length === 1 && this.#monoms[0].coefficient.isOne() && this.degree().isZero()
517
- }
518
-
519
- public isOppositeAt(P: Polynom): boolean {
520
- return this.#compare(P.clone().opposite(), '=')
521
- }
522
-
523
- public isSameAs(P: Polynom): boolean {
524
- return this.#compare(P, 'same')
525
- }
526
-
527
- public isZero(): boolean {
528
- return (this.#monoms.length === 1 && this.#monoms[0].coefficient.isZero()) || this.#monoms.length === 0
529
- }
530
-
531
- public lcmDenominator(): number {
532
- return Numeric.lcm(...this.getDenominators())
533
- }
534
-
535
- public lcmNumerator(): number {
536
- return Numeric.lcm(...this.getNumerators())
537
- }
538
-
539
- public get length() {
540
- return this.#monoms.length
541
- }
542
-
543
- public limitTo(value: InputValue<Fraction>, letter?: string): Fraction {
544
- const f = new Fraction(value)
545
-
546
- // Finite value: evaluate directly
547
- if (f.isFinite()) {
548
- const l = letter ?? this.variables[0] ?? 'x'
549
- return this.evaluate({[l]: f})
550
- }
551
-
552
- const M = this.monomByDegree(undefined, letter)
553
- const sign = M.coefficient.sign()
554
- const degree = M.degree(letter)
555
-
556
- // Constant polynomial
557
- if (degree.isZero()) {
558
- return M.coefficient.clone()
559
- }
560
-
561
- if (!degree.isStrictlyPositive()) {
562
- return new Fraction().zero()
563
- }
564
-
565
- // Limit at +∞: determined by the leading coefficient sign
566
- if (f.isPositive()) {
567
- return sign === 1
568
- ? new Fraction().infinite()
569
- : new Fraction().infinite().opposite()
570
- }
571
-
572
- // Limit at -∞: accounts for the parity of the degree
573
- const degreeIsEven = degree.value % 2 === 0
574
- const limitSign = degreeIsEven ? sign : -sign
575
- return limitSign === 1
576
- ? new Fraction().infinite()
577
- : new Fraction().infinite().opposite()
578
- }
579
-
580
- public monomByDegree(degree?: Fraction | number, letter?: string): Monom {
581
- // return the highest degree monom.
582
- if (degree === undefined) {
583
- return this.monomByDegree(this.degree(letter), letter)
584
- }
585
-
586
- // Reduce the polynom.
587
- const M = this.clone().reduce()
588
- for (const m of M.#monoms) {
589
- if (m.degree(letter).isEqual(degree)) {
590
- return m.clone()
591
- }
592
- }
593
-
594
- // Nothing was found - return the null monom.
595
- return new Monom().zero()
596
- }
597
-
598
- // Used in LinearSystem.tex
599
- public monomByLetter(letter: string): Monom {
600
- const M = this.clone().reduce()
601
- for (const m of M.#monoms) {
602
- if (m.hasVariable(letter)) {
603
- return m.clone()
604
- }
605
- }
606
-
607
- return new Monom().zero()
608
- }
609
-
610
- // ------------------------------------------
611
- public get monoms() {
612
- return this.#monoms
613
- }
614
-
615
- public set monoms(M: Monom[]) {
616
- this.#monoms = M
617
- this.#invalidateCache()
618
- }
619
-
620
- public monomsByDegree(degree?: number | Fraction, letter?: string): Monom[] {
621
- // return the highest degree monom.
622
- if (degree === undefined) {
623
- return this.monomsByDegree(this.degree(letter), letter)
624
- }
625
-
626
- // Reduce the polynom.
627
- const Ms: Monom[] = []
628
-
629
- const M = this.clone().reduce()
630
- for (const m of M.#monoms) {
631
- if (m.degree(letter).isEqual(degree)) {
632
- Ms.push(m.clone())
633
- }
634
- }
635
-
636
- return Ms
637
- }
638
-
639
- public multiply(value: InputAlgebra<Polynom>): this {
640
-
641
- if (value instanceof Polynom) {
642
- return this.#multiplyByPolynom(value)
643
- }
644
-
645
- if (value instanceof Fraction) {
646
- return this.#multiplyByFraction(value)
647
- }
648
-
649
- if (value instanceof Monom) {
650
- return this.#multiplyByMonom(value)
651
- }
652
-
653
- if (Number.isSafeInteger(value) && typeof value === 'number') {
654
- return this.#multiplyByInteger(value)
655
- }
656
-
657
- if (typeof value === 'string') {
658
- try {
659
- const k = new Fraction(value)
660
- return this.#multiplyByFraction(k)
661
- } catch (e) {
662
- throw new MathError('Cannot multiply by this value.', {cause: e})
663
- }
664
- }
665
-
666
- // Something went wrong...
667
- throw new MathError(`Cannot multiply by this value: ${value as unknown as string}`)
668
- }
669
-
670
- public get numberOfVars(): number {
671
- return this.variables.length
672
- }
673
-
674
- public one(): this {
675
- this.#monoms = []
676
- this.#monoms.push(new Monom().one())
677
- this.#invalidateCache()
678
- return this
679
- }
680
-
681
- // ------------------------------------------
682
- public opposite(): this {
683
- this.#monoms = this.#monoms.map(m => m.opposite())
684
- this.#invalidateCache()
685
- return this
686
- }
687
-
688
- public get plotFunction(): string {
689
- return this.#genDisplay('tex', false, false, true)
690
- }
691
-
692
- public pow(nb: number): Polynom {
693
- return operation_pow(this as Polynom, nb).reduce()
694
- }
695
-
696
- public primitive(letter?: string): Polynom {
697
- const dP = new Polynom()
698
-
699
- for (const m of this.#monoms) {
700
- dP.add(m.primitive(letter))
701
- }
702
-
703
- return dP
704
- }
705
-
706
- public reduce(): this {
707
- // Reduce the polynom
708
-
709
- // Group the monoms by similarity
710
- let i = 0
711
- while (i < this.#monoms.length) {
712
- for (let j = i + 1; j < this.#monoms.length; j++) {
713
- if (this.#monoms[i].isSameAs(this.#monoms[j])) {
714
- this.#monoms[i].add(this.#monoms[j])
715
- this.#monoms.splice(j, 1)
716
- if (this.#monoms[i].isZero()) {
717
- this.#monoms[i] = new Monom().zero()
718
- }
719
-
720
- j--
721
- }
722
- }
723
-
724
- i++
725
- }
726
-
727
- // Remove all null monoms
728
- this.#monoms = this.#monoms.filter((m) => {
729
- return !m.coefficient.isZero()
730
- })
731
-
732
- // Reduce all monoms coefficient.
733
- for (const m of this.#monoms) {
734
- m.coefficient.reduce()
735
- }
736
-
737
- if (this.length === 0) {
738
- return this.zero()
739
- }
740
-
741
- this.#invalidateCache()
742
- return this.reorder()
743
- }
744
-
745
- public reorder(letter = 'x', revert = false): this {
746
- const otherLetters = this.variables.filter(x => x !== letter)
747
- this.#monoms.sort(function (a, b) {
748
- const da = a.degree(letter).value
749
- const db = b.degree(letter).value
750
-
751
- // Values are different
752
- if (da !== db) {
753
- return revert ? da - db : db - da
754
- }
755
-
756
- // if values are equals, check other letters - it must be reverted in that case !
757
- if (otherLetters.length > 0) {
758
- for (const L of otherLetters) {
759
- const da = a.degree(L).value
760
- const db = b.degree(L).value
761
-
762
- // Values are different
763
- if (da !== db) {
764
- return revert ? da - db : db - da
765
- }
766
- }
767
- }
768
-
769
-
770
- return 0
771
- })
772
-
773
- return this
774
- }
775
-
776
- /**
777
- * Replace a variable (letter) by a polynom.
778
- * @param letter
779
- * @param P
780
- */
781
- public replaceBy(letter: string, P: Polynom): this {
782
- let pow: Fraction
783
- const resultPolynom: Polynom = new Polynom().zero()
784
-
785
- for (const m of this.monoms) {
786
- if (!m.hasVariable(letter) || m.literal[letter].isZero()) {
787
- resultPolynom.add(m.clone())
788
- } else {
789
- // We have found a variable to replace.
790
-
791
- // Get the power.
792
- pow = m.literal[letter].clone()
793
-
794
- // Remove the variable from the monom
795
- m.removeVariable(letter)
796
-
797
- // Add the new monom to the result polynom
798
- if (pow.isStrictlyNegative()) {
799
- throw new MathError(`Cannot replace variable "${letter}" with negative power in a Polynom`)
800
- }
801
- resultPolynom.add(P.clone().pow(pow.numerator).multiply(m))
802
- }
803
- }
804
-
805
-
806
- // Reduce the monoms
807
- this.#monoms = resultPolynom.reduce().monoms
808
- this.#invalidateCache()
809
- return this
810
- }
811
-
812
- // ------------------------------------------
813
-
814
- public root(): Polynom {
815
- throw new MathError('Cannot take the root from a polynom')
816
- }
817
-
818
- get roots(): Solution[] {
819
- return this.#rootsCache ? this.#roots : this.getZeroes()
820
- }
821
-
822
- set roots(value: Solution[]) {
823
- this.#rootsCache = true
824
- this.#roots = value
825
- }
826
-
827
- public setVariable(value: string): this {
828
- this.#defaultVariable = value
829
-
830
- return this
831
- }
832
-
833
- public sqrt(): Polynom {
834
- throw new MathError('Cannot take the square root from a polynom')
835
- }
836
-
837
- public subtract(...values: InputAlgebra<Polynom>[]): this {
838
- for (const value of values) {
839
- if (value instanceof Polynom) {
840
- this.add(value.clone().opposite())
841
- } else if (value instanceof Monom) {
842
- this.#monoms.push(value.clone().opposite())
843
- } else {
844
- this.#monoms.push(new Monom(value).opposite())
845
- }
846
- }
847
-
848
-
849
- return this.reduce()
850
- }
851
-
852
- public tableOfSigns(): TABLE_OF_SIGNS {
853
- // returns ['+-', 'd|t|z', '+-']...
854
-
855
- // global roots from eventually Polyfactor. Allows to add "extra column".
856
- const roots: Solution[] = this.roots
857
-
858
- // Build the table os sign length and default values
859
- // The signs looks like: ['+', 't', '+', 't', '+', 't', '+']
860
- let signs: TABLE_OF_SIGNS_VALUES[] = new Array(2 * roots.length + 1)
861
- .fill('')
862
- .map((_x, index) => {
863
- return index % 2 === 0 ? '' : 'z'
864
- })
865
-
866
-
867
- if (signs.length === 1) {
868
- // The polynom is a constant or has not roots
869
- const [a] = this.getCoefficients().map(x => x.value)
870
- signs = replace_in_array(signs, '', a > 0 ? '+' : '-')
871
- } else if (this.degree().isOne()) {
872
- // First degree: ax+b
873
- const [a] = this.getCoefficients().map(x => x.value)
874
-
875
- // Get the index of the zero.
876
- signs[0] = a > 0 ? '-' : '+'
877
- signs[1] = 'z'
878
- signs[2] = a > 0 ? '+' : '-'
879
- } else {
880
- const testingRoots = [
881
- roots[0].value - 1,
882
- ...roots.map((_root, index) => {
883
- return index === roots.length - 1 ?
884
- roots[index].value + 1 :
885
- (roots[index].value + roots[index + 1].value) / 2
886
- })
887
- ]
888
-
889
- testingRoots.forEach((test, index) => {
890
- const sign = this.evaluate({[this.variables[0] ?? 'x']: test}, true)
891
- signs[index * 2] = sign > 0 ? '+' : '-'
892
- })
893
- }
894
-
895
- return {roots, signs}
896
- }
897
-
898
- public get value(): number | undefined {
899
- if (this.degree().isZero()) {
900
- return this.monoms[0]?.coefficient.value ?? 0
901
- }
902
-
903
- return undefined
904
- }
905
-
906
- public get variables(): string[] {
907
- return [...new Set(this.#monoms.flatMap(m => m.variables))].sort()
908
- }
909
-
910
- /**
911
- * Set the polynom to zero.
912
- * @returns {this}
913
- */
914
- public zero(): this {
915
- this.#monoms = []
916
- this.#monoms.push(new Monom().zero())
917
- this.#invalidateCache()
918
- return this
919
- }
920
-
921
- public get zeroes(): Solution[] {
922
- return this.getZeroes()
923
- }
924
-
925
- #compare(P: Polynom, sign?: string): boolean {
926
- sign ??= '='
927
-
928
- // Create clone version to reduce them without altering the original polynoms.
929
- const cP1 = this.clone().reduce().reorder()
930
- const cP2 = P.clone().reduce().reorder()
931
-
932
- switch (sign) {
933
- case '=':
934
- // They must have the isSame length and the isSame degree
935
- if (cP1.length !== cP2.length || !cP1.degree().isEqual(cP2.degree())) {
936
- return false
937
- }
938
-
939
- // Check if the coefficients are the isSame.
940
- return cP1.monoms
941
- .every((m1, index) => m1.isEqual(cP2.monoms[index]))
942
-
943
- case 'same':
944
- // They must have the same length and the same degree
945
- if (cP1.length !== cP2.length || !cP1.degree().isEqual(cP2.degree())) {
946
- return false
947
- }
948
-
949
- return cP1.monoms
950
- .every((m1, index) => m1.isSameAs(cP2.monoms[index]))
951
-
952
- default:
953
- return false
954
- }
955
- }
956
-
957
- #divideByFraction(F: Fraction): this {
958
- for (const m of this.#monoms) {
959
- m.coefficient.divide(F)
960
- }
961
-
962
- this.#invalidateCache()
963
- return this
964
- }
965
-
966
- #divideByInteger(nb: number): this {
967
- const nbF = new Fraction(nb)
968
- for (const m of this.#monoms) {
969
- m.coefficient.divide(nbF)
970
- }
971
-
972
- this.#invalidateCache()
973
- return this
974
- }
975
-
976
- #evaluateAsNumeric(values: literalType<number | Fraction> | InputValue<Fraction>): number {
977
- let r = 0
978
- this.#monoms.forEach(monom => {
979
- r += monom.evaluate(values, true) as number
980
- })
981
-
982
- return r
983
- }
984
-
985
- #genDisplay(output?: string, forceSign?: boolean, wrapParentheses?: boolean, withAllMultiplicationSign?: boolean): string {
986
- let P = ''
987
-
988
- for (const k of this.#monoms) {
989
- if (k.coefficient.value === 0) {
990
- continue
991
- }
992
-
993
- // The monom to be displayed
994
- let m
995
- if (withAllMultiplicationSign) {
996
- m = k.plotFunction
997
- } else {
998
- m = (output === 'tex') ? k.tex : k.display
999
- }
1000
-
1001
- P += `${(k.coefficient.sign() === 1 && (P !== '' || forceSign === true)) ? '+' : ''}${m}`
1002
- }
1003
-
1004
- if (wrapParentheses === true && this.length > 1) {
1005
- if (output === 'tex') {
1006
- P = `\\left( ${P} \\right)`
1007
- } else {
1008
- P = `(${P})`
1009
- }
1010
- }
1011
-
1012
-
1013
- if (P === '') {
1014
- P = '0'
1015
- }
1016
-
1017
- return P
1018
- }
1019
-
1020
- #invalidateCache(): void {
1021
- this.#rootsCache = false
1022
- this.#factors = []
1023
- }
1024
-
1025
- #multiplyByFraction(F: Fraction): this {
1026
- for (const m of this.#monoms) {
1027
- m.coefficient.multiply(F)
1028
- }
1029
-
1030
- return this.reduce()
1031
- }
1032
-
1033
- #multiplyByInteger(nb: number): this {
1034
- return this.#multiplyByFraction(new Fraction(nb))
1035
- }
1036
-
1037
- #multiplyByMonom(M: Monom): this {
1038
- for (const m of this.#monoms) {
1039
- m.multiply(M)
1040
- }
1041
-
1042
- return this.reduce()
1043
- }
1044
-
1045
- #multiplyByPolynom(P: Polynom): this {
1046
- const M: Monom[] = []
1047
- for (const m1 of this.#monoms) {
1048
- for (const m2 of P.monoms) {
1049
- M.push(Monom.xMultiply(m1, m2))
1050
- }
1051
- }
1052
-
1053
- this.#monoms = M
1054
- return this.reduce()
1055
- }
1056
-
1057
- #parseString(inputStr: string, ...values: InputValue<Fraction>[]): this {
1058
- if (values.length === 0) {
1059
- // Parse the polynom using the shutting yard algorithm
1060
- if (inputStr !== '' && !isNaN(Number(inputStr))) {
1061
- this.empty()
1062
- // It's a simple number.
1063
- const m = new Monom(Number(inputStr))
1064
- // m.coefficient = new Fraction(inputStr);
1065
- // m.literalStr = '';
1066
- this.add(m)
1067
- return this
1068
- }
1069
-
1070
- // Parse the string.
1071
- return this.#shutingYardToReducedPolynom(inputStr)
1072
- } else if (/^[a-z]+$/.test(inputStr)) {
1073
- // We assume the inputStr contains only letters.
1074
- this.empty()
1075
-
1076
- const fractions = values.map(x => new Fraction(x))
1077
-
1078
- // Multiple setLetter version
1079
- if (inputStr.length > 1) {
1080
- const letters = inputStr.split('')
1081
-
1082
- if (fractions.length > letters.length + 1) {
1083
- throw new InvalidArgumentError(`Too many values: ${letters.length} letters but ${fractions.length} values provided`)
1084
- }
1085
-
1086
- let i = 0
1087
-
1088
- for (const F of fractions) {
1089
- const m = new Monom()
1090
- m.coefficient = F.clone()
1091
- m.literalStr = letters[i] ?? ''
1092
- this.add(m)
1093
- i++
1094
- }
1095
- }
1096
- // Single setLetter version
1097
- else {
1098
- let n = fractions.length - 1
1099
- for (const F of fractions) {
1100
- const m = new Monom()
1101
- m.coefficient = F.clone()
1102
- m.literalStr = `${inputStr}^${n}`
1103
- this.add(m)
1104
- n--
1105
- }
1106
- }
1107
- return this
1108
- } else {
1109
- return this.zero()
1110
- }
1111
-
1112
- }
1113
-
1114
- #shutingYardAddToken(stack: Polynom[], element: Token): void {
1115
- switch (element.tokenType) {
1116
- case ShutingyardType.COEFFICIENT:
1117
- stack.push(new Polynom(element.token))
1118
- break
1119
-
1120
- case ShutingyardType.VARIABLE:
1121
- stack.push(new Polynom().add(new Monom(element.token)))
1122
- break
1123
-
1124
- case ShutingyardType.CONSTANT:
1125
- throw new ParseError('Unsupported CONSTANT token in Polynom parser')
1126
-
1127
- case ShutingyardType.OPERATION:
1128
- if (stack.length >= 2) {
1129
- const b = stack.pop()
1130
- const a = stack.pop()
1131
-
1132
- // Check if the polynoms are not undefined.
1133
- if (a === undefined || b === undefined) {
1134
- throw new ParseError('Unexpected undefined operand in Polynom parser stack')
1135
- }
1136
-
1137
- if (element.token === '+') {
1138
- stack.push(a.add(b))
1139
- } else if (element.token === '-') {
1140
- stack.push(a.subtract(b))
1141
- } else if (element.token === '*') {
1142
- stack.push(a.multiply(b))
1143
- } else if (element.token === '/') {
1144
- if (b.degree().isStrictlyPositive()) {
1145
- throw new MathError('Cannot divide a Polynom by another Polynom of degree > 0')
1146
- } else {
1147
- // a.divide(b.monoms[0].coefficient)
1148
- stack.push(a.divide(b.monoms[0].coefficient))
1149
- }
1150
- } else if (element.token === '^') {
1151
- if (b.degree().isStrictlyPositive()) {
1152
- throw new MathError('Cannot elevate a polynom with another polynom !')
1153
- } else if (b.monoms[0].coefficient.isNatural())
1154
- // Natural (positive) power
1155
- {
1156
- stack.push(a.pow(b.monoms[0].coefficient.value))
1157
- } else {
1158
- // Only allow power if the previous polynom is only a monom, without coefficient.
1159
- if (a.monoms.length === 1 && a.monoms[0].coefficient.isOne()) {
1160
- for (const letter in a.monoms[0].literal) {
1161
- a.monoms[0].literal[letter].multiply(b.monoms[0].coefficient)
1162
- }
1163
-
1164
- stack.push(a)
1165
- } else {
1166
- throw new MathError('Cannot raise a multi-monom Polynom to a fractional power')
1167
- }
1168
- }
1169
- }
1170
-
1171
- } else if (element.token === '-') {
1172
- const a = stack.pop()
1173
- if (a) {
1174
- stack.push(a.opposite())
1175
- }
1176
- } else {
1177
- throw new ParseError("Error parsing the polynom")
1178
- }
1179
-
1180
-
1181
- break
1182
-
1183
- case ShutingyardType.MONOM:
1184
- // Should never appear.
1185
- throw new ParseError('Unexpected MONOM token in polynom parser')
1186
-
1187
- case ShutingyardType.FUNCTION:
1188
- // Should never appear.
1189
- throw new ParseError(`Unsupported function token "${element.token}" in polynom parser`)
1190
- }
1191
-
1192
-
1193
- }
1194
-
1195
- /**
1196
- * Main parse using a shutting yard class
1197
- * @param inputStr
1198
- */
1199
- #shutingYardToReducedPolynom(inputStr: string): this {
1200
- // Get the RPN array of the current expression
1201
- let SY: ShutingYard
1202
- try {
1203
- SY = new ShutingYard().parse(inputStr)
1204
- } catch (e) {
1205
- throw new ParseError(`Cannot parse "${inputStr}" as a Polynom`, {cause: e})
1206
- }
1207
- const rpn: { token: string, tokenType: ShutingyardType }[] = SY.rpn
1208
-
1209
- // New version for reducing shuting yard.
1210
- this.zero()
1211
-
1212
- const stack: Polynom[] = []
1213
-
1214
- // Loop through the each element of the RPN
1215
- for (const element of rpn) {
1216
- this.#shutingYardAddToken(stack, element)
1217
- }
1218
-
1219
-
1220
- if (stack.length === 1) {
1221
- this.add(stack[0])
1222
- }
1223
-
1224
-
1225
- return this.reorder()
1226
- }
1227
-
1228
-
1229
- }
1
+ /**
2
+ * Polynom module contains everything necessary to handle polynoms.*
3
+ */
4
+ import {ShutingYard, ShutingyardType, type Token} from "piexpression"
5
+ import {InvalidArgumentError, MathError, NotImplementedError, ParseError} from "../errors"
6
+ import type {
7
+ IAlgebra,
8
+ IAnalyse,
9
+ IExpression,
10
+ InputAlgebra,
11
+ InputValue,
12
+ IPiMathObject,
13
+ literalType,
14
+ TABLE_OF_SIGNS,
15
+ TABLE_OF_SIGNS_VALUES
16
+ } from "../pimath.interface"
17
+ import {Fraction} from "../coefficients"
18
+ import {Numeric} from '../numeric'
19
+ import {EquationSolver} from './equationSolver'
20
+ import {Monom} from './monom'
21
+ import {replace_in_array} from "../helpers"
22
+ import {operation_pow} from "./operations"
23
+ import {Solution} from "../analyze/solution"
24
+
25
+
26
+ export type PolynomParsingType = InputValue<Polynom> | Monom
27
+
28
+ export interface IEuclidean {
29
+ quotient: Polynom,
30
+ reminder: Polynom
31
+ }
32
+
33
+ /**
34
+ * Polynom class can handle polynoms, reorder, resolve, ...
35
+ * ```
36
+ * let P = new Polynom('3x-4')
37
+ * ```
38
+ */
39
+ export class Polynom implements IPiMathObject<Polynom>,
40
+ IExpression<Polynom>,
41
+ IAnalyse<Polynom>,
42
+ IAlgebra<Polynom> {
43
+
44
+ #defaultVariable = 'x'
45
+ #factors: Polynom[]
46
+ #monoms: Monom[]
47
+ #roots: Solution[]
48
+ #rootsCache = false
49
+
50
+ constructor(value: InputValue<Fraction>)
51
+ constructor(value: string)
52
+ constructor(value: Monom)
53
+ constructor(value: Polynom)
54
+ constructor(...values: InputValue<Fraction>[])
55
+ constructor(...values: InputAlgebra<Polynom>[])
56
+ constructor(polynomString?: InputAlgebra<Polynom>, ...values: InputAlgebra<Fraction>[]) {
57
+ this.#monoms = []
58
+ this.#factors = []
59
+ this.#roots = []
60
+
61
+ if (polynomString !== undefined) {
62
+ this.parse(polynomString, ...values)
63
+ }
64
+ return this
65
+ }
66
+
67
+
68
+ /**
69
+ * Parse a string to a polynom.
70
+ * @param inputStr
71
+ * @param values
72
+ */
73
+ parse(inputStr: PolynomParsingType, ...values: InputAlgebra<Monom>[]): this {
74
+ // Reset the main variables.
75
+ this.#monoms = []
76
+ this.#factors = []
77
+ this.#invalidateCache()
78
+
79
+ if (typeof inputStr === 'string') {
80
+ return this.#parseString(inputStr, ...values as InputValue<Fraction>[])
81
+ } else if (
82
+ (typeof inputStr === 'number' || inputStr instanceof Fraction || inputStr instanceof Monom)
83
+ && (values.length === 0)
84
+ ) {
85
+ this.#monoms.push(new Monom(inputStr as Monom))
86
+ } else if (inputStr instanceof Monom && values.length > 0) {
87
+ this.#monoms.push(new Monom(inputStr))
88
+ values.forEach(m => {
89
+ this.#monoms.push(new Monom(m as Monom))
90
+ })
91
+ } else if (inputStr instanceof Polynom) {
92
+ for (const m of inputStr.monoms) {
93
+ this.#monoms.push(m.clone())
94
+ }
95
+ }
96
+
97
+
98
+ return this
99
+ }
100
+
101
+ /**
102
+ * Clone the polynom
103
+ */
104
+ copy(): this {
105
+ throw new NotImplementedError("copy() is not yet implemented for Polynom")
106
+ }
107
+
108
+ clone(): Polynom {
109
+ const P = new Polynom()
110
+ const M: Monom[] = []
111
+ P.#defaultVariable = this.#defaultVariable
112
+
113
+ for (const m of this.#monoms) {
114
+ M.push(m.clone())
115
+ }
116
+
117
+ P.monoms = M
118
+
119
+ return P
120
+ }
121
+
122
+ get tex(): string {
123
+ return this.#genDisplay('tex')
124
+ }
125
+
126
+ get display(): string {
127
+ return this.#genDisplay()
128
+ }
129
+
130
+ static xMultiply(...polynoms: InputValue<Polynom>[]): Polynom {
131
+ const result = new Polynom().one()
132
+
133
+ polynoms.forEach(P => {
134
+ result.multiply(P)
135
+ })
136
+
137
+ return result
138
+ }
139
+
140
+ add(...values: InputAlgebra<Polynom>[]): this {
141
+
142
+ for (const value of values) {
143
+ if (value instanceof Polynom) {
144
+ this.#monoms = this.#monoms.concat(value.monoms.map(m => m.clone()))
145
+ } else if (value instanceof Monom) {
146
+ this.#monoms.push(value.clone())
147
+ } else if (typeof value === "number" && Number.isSafeInteger(value)) {
148
+ this.#monoms.push(new Monom(value.toString()))
149
+ } else {
150
+ this.#monoms.push(new Monom(value))
151
+ }
152
+ }
153
+
154
+
155
+ return this.reduce()
156
+ }
157
+
158
+ commonMonom(): Monom {
159
+ const M = new Monom().one()
160
+ const numerator: number = this.gcdNumerator()
161
+ const denominator: number = this.gcdDenominator()
162
+
163
+ M.coefficient = new Fraction(numerator, denominator)
164
+ for (const L of this.variables) {
165
+ // Initialize the setLetter with the max degree
166
+ M.setLetter(L, this.degree(L))
167
+ for (const m of this.#monoms) {
168
+ M.setLetter(L, Fraction.min(m.degree(L), M.degree(L)))
169
+ if (M.degree(L).isZero()) {
170
+ break
171
+ }
172
+
173
+ }
174
+ }
175
+ return M
176
+ }
177
+
178
+ degree(letter?: string): Fraction {
179
+ let d: Fraction = new Fraction().zero()
180
+ for (const m of this.#monoms) {
181
+ d = Fraction.max(m.degree(letter).value, d)
182
+ }
183
+
184
+ return d
185
+ }
186
+
187
+ derivative(letter?: string): Polynom {
188
+ const dP = new Polynom()
189
+
190
+ for (const m of this.#monoms) {
191
+ dP.add(m.derivative(letter))
192
+ }
193
+
194
+ return dP.reduce()
195
+ }
196
+
197
+ divide(value: InputAlgebra<Polynom>): this {
198
+
199
+ if (value instanceof Fraction) {
200
+ return this.#divideByFraction(value)
201
+ } else if (typeof value === 'number' && Number.isSafeInteger(value)) {
202
+ return this.#divideByInteger(value)
203
+ } else if (value instanceof Monom) {
204
+ return this.divide(new Polynom(value))
205
+ } else if (value instanceof Polynom) {
206
+ if (value.monoms.length === 1 && value.variables.length === 0) {
207
+ return this.#divideByFraction(value.monoms[0].coefficient)
208
+ } else {
209
+ const {quotient, reminder} = this.euclidean(value)
210
+ if (reminder.isZero()) {
211
+ this.#monoms = quotient.monoms
212
+ this.#invalidateCache()
213
+ return this
214
+ }
215
+ }
216
+ } else if (typeof value === 'string') {
217
+ return this.divide(new Polynom(value))
218
+ }
219
+
220
+ throw new MathError(`Cannot divide by ${value as unknown as string}`)
221
+ }
222
+
223
+ empty(): this {
224
+ this.#monoms = []
225
+ this.#invalidateCache()
226
+ return this
227
+ }
228
+
229
+ /**
230
+ * Divide the current polynom by another polynom.
231
+ * @param P
232
+ * returns {quotient: Polynom, reminder: Polynom}
233
+ */
234
+ euclidean(P: Polynom): IEuclidean {
235
+ const letter: string | undefined = P.variables[0]
236
+ const quotient: Polynom = new Polynom().zero()
237
+ const reminder: Polynom = this.clone().reorder(letter)
238
+
239
+ // There is no variable - means it's a number
240
+ if (P.variables.length === 0) {
241
+ const q = this.clone().divide(P)
242
+
243
+ return {
244
+ quotient: q.reduce(),
245
+ reminder: new Polynom().zero()
246
+ }
247
+ }
248
+
249
+ if (!this.degree(letter).isNatural() || !P.degree(letter).isNatural()) {
250
+ throw new MathError('Euclidean division requires integer degrees')
251
+ }
252
+
253
+ // Get at least a letter
254
+ const maxMP: Monom = P.monomByDegree(undefined, letter)
255
+ const degreeP: Fraction = P.degree(letter)
256
+
257
+ let newM: Monom
258
+
259
+ // Make the Euclidean division of the two polynoms.
260
+ let MaxIteration = this.degree(letter).value - degreeP.value + 1
261
+
262
+ while (reminder.degree(letter).isGeq(degreeP) && MaxIteration > 0) {
263
+ MaxIteration--
264
+
265
+ // Get the greatest monom divided by the max monom of the divider
266
+ newM = reminder.monomByDegree(undefined, letter).clone().divide(maxMP)
267
+
268
+ if (newM.isZero()) {
269
+ continue
270
+ }
271
+
272
+ // Get the new quotient and reminder.
273
+ quotient.add(newM)
274
+ reminder.subtract(P.clone().multiply(newM)).reduce()
275
+
276
+ // Check if the reminder is zero.
277
+ if (newM.degree(letter).isZero()) {
278
+ break
279
+ }
280
+ }
281
+
282
+ quotient.reduce()
283
+ reminder.reduce()
284
+ return {quotient, reminder}
285
+ }
286
+
287
+ evaluate(values: literalType<Fraction | number> | InputValue<Fraction>, asNumeric: true): number
288
+ evaluate(values: literalType<Fraction | number> | InputValue<Fraction>, asNumeric?: false): Fraction
289
+ evaluate(values: literalType<Fraction | number> | InputValue<Fraction>, asNumeric?: boolean): Fraction | number
290
+ evaluate(values: literalType<Fraction | number> | InputValue<Fraction>, asNumeric?: boolean): Fraction | number {
291
+ // Return the numeric value without using Fraction
292
+ if (asNumeric) {
293
+ return this.#evaluateAsNumeric(values)
294
+ }
295
+
296
+ // Build the evaluated fraction
297
+ const r = new Fraction().zero()
298
+ this.#monoms.forEach(monom => {
299
+ r.add(monom.evaluate(values, asNumeric))
300
+ })
301
+
302
+ return r
303
+ }
304
+
305
+ // -------------------------------------
306
+ /**
307
+ * Factorize a polynom and store the best results in factors.
308
+ * @param _letter
309
+ * TODO: Handle other letter than 'x'.
310
+ */
311
+ factorize(_letter?: string): Polynom[] {
312
+ this.#factors = []
313
+
314
+ // There is only one monom - it's already considered as factor.
315
+ if (this.monoms.length === 1) return [this.clone()]
316
+
317
+ let P = this.clone().reorder()
318
+
319
+ // Extract the common monom
320
+ // 2x^3+6x^2 => 2x^2
321
+ const M = P.commonMonom()
322
+
323
+ // If the polynom starts with a negative monom, factorize it.
324
+ if (P.monomByDegree().coefficient.isStrictlyNegative()) M.opposite()
325
+
326
+ if (!M.isOne()) {
327
+ this.#factors.push(new Polynom(M))
328
+ P = P.euclidean(this.#factors[0]).quotient
329
+ }
330
+
331
+ const solutions = new EquationSolver(P).solve()
332
+
333
+ if (solutions.length === 0) {
334
+ if (this.#factors.length === 0) {
335
+ // No common monom was extracted: the whole polynomial is irreducible
336
+ this.#factors = [this.clone()]
337
+ } else {
338
+ // Common monom was extracted: P is the irreducible remaining part
339
+ this.#factors.push(P)
340
+ }
341
+ return this.#factors
342
+ }
343
+
344
+ solutions.forEach(sol => {
345
+ if (sol.exact && sol.root.isZero()) {
346
+ for (let i = 0; i < sol.count; i++) {
347
+ if (sol.fraction.hasFractionalPart()) {
348
+ this.#factors.push(new Polynom().fromCoefficients(sol.fraction.denominator, -sol.fraction.numerator))
349
+ } else {
350
+ this.#factors.push(new Polynom().fromCoefficients(1, sol.fraction.clone().opposite()))
351
+ }
352
+ }
353
+ }
354
+ })
355
+
356
+ // Check if we have all factors.
357
+ const fDegrees = this.#factors
358
+ .map(f => f.degree().value)
359
+ .reduce((a, b) => a + b, 0)
360
+
361
+ if (fDegrees < this.degree().value) {
362
+ // missing polynom.
363
+ const fPolynom = Polynom.xMultiply(...this.#factors)
364
+ this.#factors.push(this.clone().divide(fPolynom))
365
+ }
366
+
367
+ return this.#factors
368
+ }
369
+
370
+ get factors(): Polynom[] {
371
+ if (this.#factors.length === 0 && !this.isZero()) {
372
+ return this.factorize()
373
+ }
374
+
375
+ return this.#factors
376
+ }
377
+
378
+ fromCoefficients(...values: InputValue<Fraction>[]): this {
379
+
380
+ this.#monoms = []
381
+ const letter = this.#defaultVariable ?? 'x'
382
+ values.reverse().forEach((coeff, index) => {
383
+ const monom = new Monom()
384
+ monom.coefficient = new Fraction(coeff)
385
+ monom.setLetter(letter, index)
386
+
387
+ this.#monoms.push(monom)
388
+ })
389
+
390
+ this.#invalidateCache()
391
+ return this.reorder()
392
+ }
393
+
394
+ gcdDenominator(): number {
395
+ return Numeric.gcd(...this.getDenominators())
396
+ }
397
+
398
+ gcdNumerator(): number {
399
+ return Numeric.gcd(...this.getNumerators())
400
+ }
401
+
402
+ getCoefficients(): Fraction[] {
403
+ if (!this.degree().isNatural()) {
404
+ throw new MathError('getCoefficients() requires a polynomial with integer degrees')
405
+ }
406
+
407
+ // Assume there is only one letter.
408
+ const orderedPolynom = this.clone().reorder()
409
+
410
+ const length = this.degree().value + 1
411
+ const coeffs = Array.from({length}, () => new Fraction(0))
412
+
413
+ orderedPolynom.monoms.forEach(monom => {
414
+ const index = length - monom.degree().value - 1
415
+ coeffs[index] = monom.coefficient.clone()
416
+ })
417
+
418
+ return coeffs
419
+ }
420
+
421
+ // Next functions are used for for commonMonom, which is used in the factorize method.
422
+ getDenominators(): number[] {
423
+ const denominators: number[] = []
424
+ for (const m of this.#monoms) {
425
+ denominators.push(m.coefficient.denominator)
426
+ }
427
+
428
+ return denominators
429
+ }
430
+
431
+ getNumerators(): number[] {
432
+ const numerators: number[] = []
433
+ for (const m of this.#monoms) {
434
+ numerators.push(m.coefficient.numerator)
435
+ }
436
+
437
+ return numerators
438
+ }
439
+
440
+ getZeroes(): Solution[] {
441
+ if (this.degree().isZero()) {
442
+ return []
443
+ }
444
+
445
+ this.roots = new EquationSolver(this.clone()).solve()
446
+ return this.roots
447
+ }
448
+
449
+ hasVariable(letter: string): boolean {
450
+ return this.variables.includes(letter)
451
+ }
452
+
453
+ integrate(a: InputValue<Fraction>, b: InputValue<Fraction>, letter = 'x'): Fraction {
454
+ const primitive = this.primitive(letter)
455
+
456
+ const valuesA: literalType<Fraction> = {}
457
+ const valuesB: literalType<Fraction> = {}
458
+
459
+ valuesA[letter] = new Fraction(a)
460
+ valuesB[letter] = new Fraction(b)
461
+
462
+ return (primitive.evaluate(valuesB)).subtract(primitive.evaluate(valuesA))
463
+ }
464
+
465
+ inverse(): Polynom | undefined {
466
+ return undefined
467
+ }
468
+
469
+ isDeveloped(polynomString: string): boolean {
470
+ // Start by removing the parenthesis after a "power"
471
+ const pString = polynomString.replaceAll(/\^\(([-0-9/]+)\)/g, '$1')
472
+
473
+ // There is at least one parenthesis - it is not developed.
474
+ if (pString.includes('(') || pString.includes(')')) {
475
+ return false
476
+ }
477
+
478
+
479
+ // Build the polynom. A malformed string throws (ParseError) instead of
480
+ // being silently treated as "not developed".
481
+ const P = new Polynom(polynomString)
482
+
483
+ // Both polynom aren't the same (once developed and reduced => they cannot be equivalent)
484
+ if (!this.isEqual(P)) {
485
+ return false
486
+ }
487
+
488
+
489
+ // Check that everything is completely developed. Actually, there are no parentheses... so it is fully developed
490
+ return true
491
+ }
492
+
493
+ isDividableBy(div: Polynom): boolean {
494
+ // Quick evaluation.
495
+ if (div.degree().isOne()) {
496
+ const zero = div.getZeroes()[0]
497
+
498
+ if (zero.exact) {
499
+ return (this.evaluate(zero.fraction)).isZero()
500
+ } else {
501
+ return false
502
+ }
503
+
504
+ } else {
505
+ const {reminder} = this.euclidean(div)
506
+ return reminder.isZero()
507
+ }
508
+ }
509
+
510
+ isEqual(P: Polynom): boolean {
511
+ return this.#compare(P, '=')
512
+ }
513
+
514
+ get isMultiVariable(): boolean {
515
+ // Determine if a monom has more than one variable.
516
+ return this.#monoms.some(m => m.variables.length > 1)
517
+ }
518
+
519
+ isOne(): boolean {
520
+ return this.#monoms.length === 1 && this.#monoms[0].coefficient.isOne() && this.degree().isZero()
521
+ }
522
+
523
+ isOppositeAt(P: Polynom): boolean {
524
+ return this.#compare(P.clone().opposite(), '=')
525
+ }
526
+
527
+ isSameAs(P: Polynom): boolean {
528
+ return this.#compare(P, 'same')
529
+ }
530
+
531
+ isZero(): boolean {
532
+ return (this.#monoms.length === 1 && this.#monoms[0].coefficient.isZero()) || this.#monoms.length === 0
533
+ }
534
+
535
+ lcmDenominator(): number {
536
+ return Numeric.lcm(...this.getDenominators())
537
+ }
538
+
539
+ lcmNumerator(): number {
540
+ return Numeric.lcm(...this.getNumerators())
541
+ }
542
+
543
+ get length() {
544
+ return this.#monoms.length
545
+ }
546
+
547
+ limitTo(value: InputValue<Fraction>, letter?: string): Fraction {
548
+ const f = new Fraction(value)
549
+
550
+ // Finite value: evaluate directly
551
+ if (f.isFinite()) {
552
+ const l = letter ?? this.variables[0] ?? 'x'
553
+ return this.evaluate({[l]: f})
554
+ }
555
+
556
+ const M = this.monomByDegree(undefined, letter)
557
+ const sign = M.coefficient.sign()
558
+ const degree = M.degree(letter)
559
+
560
+ // Constant polynomial
561
+ if (degree.isZero()) {
562
+ return M.coefficient.clone()
563
+ }
564
+
565
+ if (!degree.isStrictlyPositive()) {
566
+ return new Fraction().zero()
567
+ }
568
+
569
+ // Limit at +∞: determined by the leading coefficient sign
570
+ if (f.isPositive()) {
571
+ return sign === 1
572
+ ? new Fraction().infinite()
573
+ : new Fraction().infinite().opposite()
574
+ }
575
+
576
+ // Limit at -∞: accounts for the parity of the degree
577
+ const degreeIsEven = degree.value % 2 === 0
578
+ const limitSign = degreeIsEven ? sign : -sign
579
+ return limitSign === 1
580
+ ? new Fraction().infinite()
581
+ : new Fraction().infinite().opposite()
582
+ }
583
+
584
+ monomByDegree(degree?: Fraction | number, letter?: string): Monom {
585
+ // return the highest degree monom.
586
+ if (degree === undefined) {
587
+ return this.monomByDegree(this.degree(letter), letter)
588
+ }
589
+
590
+ // Reduce the polynom.
591
+ const M = this.clone().reduce()
592
+ for (const m of M.#monoms) {
593
+ if (m.degree(letter).isEqual(degree)) {
594
+ return m.clone()
595
+ }
596
+ }
597
+
598
+ // Nothing was found - return the null monom.
599
+ return new Monom().zero()
600
+ }
601
+
602
+ // Used in LinearSystem.tex
603
+ monomByLetter(letter: string): Monom {
604
+ const M = this.clone().reduce()
605
+ for (const m of M.#monoms) {
606
+ if (m.hasVariable(letter)) {
607
+ return m.clone()
608
+ }
609
+ }
610
+
611
+ return new Monom().zero()
612
+ }
613
+
614
+ // ------------------------------------------
615
+ get monoms() {
616
+ return this.#monoms
617
+ }
618
+
619
+ set monoms(M: Monom[]) {
620
+ this.#monoms = M
621
+ this.#invalidateCache()
622
+ }
623
+
624
+ monomsByDegree(degree?: number | Fraction, letter?: string): Monom[] {
625
+ // return the highest degree monom.
626
+ if (degree === undefined) {
627
+ return this.monomsByDegree(this.degree(letter), letter)
628
+ }
629
+
630
+ // Reduce the polynom.
631
+ const Ms: Monom[] = []
632
+
633
+ const M = this.clone().reduce()
634
+ for (const m of M.#monoms) {
635
+ if (m.degree(letter).isEqual(degree)) {
636
+ Ms.push(m.clone())
637
+ }
638
+ }
639
+
640
+ return Ms
641
+ }
642
+
643
+ multiply(value: InputAlgebra<Polynom>): this {
644
+
645
+ if (value instanceof Polynom) {
646
+ return this.#multiplyByPolynom(value)
647
+ }
648
+
649
+ if (value instanceof Fraction) {
650
+ return this.#multiplyByFraction(value)
651
+ }
652
+
653
+ if (value instanceof Monom) {
654
+ return this.#multiplyByMonom(value)
655
+ }
656
+
657
+ if (Number.isSafeInteger(value) && typeof value === 'number') {
658
+ return this.#multiplyByInteger(value)
659
+ }
660
+
661
+ if (typeof value === 'string') {
662
+ try {
663
+ const k = new Fraction(value)
664
+ return this.#multiplyByFraction(k)
665
+ } catch (e) {
666
+ throw new MathError('Cannot multiply by this value.', {cause: e})
667
+ }
668
+ }
669
+
670
+ // Something went wrong...
671
+ throw new MathError(`Cannot multiply by this value: ${value as unknown as string}`)
672
+ }
673
+
674
+ get numberOfVars(): number {
675
+ return this.variables.length
676
+ }
677
+
678
+ one(): this {
679
+ this.#monoms = []
680
+ this.#monoms.push(new Monom().one())
681
+ this.#invalidateCache()
682
+ return this
683
+ }
684
+
685
+ // ------------------------------------------
686
+ opposite(): this {
687
+ this.#monoms = this.#monoms.map(m => m.opposite())
688
+ this.#invalidateCache()
689
+ return this
690
+ }
691
+
692
+ get plotFunction(): string {
693
+ return this.#genDisplay('tex', false, false, true)
694
+ }
695
+
696
+ pow(nb: number): Polynom {
697
+ return operation_pow(this as Polynom, nb).reduce()
698
+ }
699
+
700
+ primitive(letter?: string): Polynom {
701
+ const dP = new Polynom()
702
+
703
+ for (const m of this.#monoms) {
704
+ dP.add(m.primitive(letter))
705
+ }
706
+
707
+ return dP
708
+ }
709
+
710
+ reduce(): this {
711
+ // Reduce the polynom
712
+
713
+ // Group the monoms by similarity
714
+ let i = 0
715
+ while (i < this.#monoms.length) {
716
+ for (let j = i + 1; j < this.#monoms.length; j++) {
717
+ if (this.#monoms[i].isSameAs(this.#monoms[j])) {
718
+ this.#monoms[i].add(this.#monoms[j])
719
+ this.#monoms.splice(j, 1)
720
+ if (this.#monoms[i].isZero()) {
721
+ this.#monoms[i] = new Monom().zero()
722
+ }
723
+
724
+ j--
725
+ }
726
+ }
727
+
728
+ i++
729
+ }
730
+
731
+ // Remove all null monoms
732
+ this.#monoms = this.#monoms.filter((m) => {
733
+ return !m.coefficient.isZero()
734
+ })
735
+
736
+ // Reduce all monoms coefficient.
737
+ for (const m of this.#monoms) {
738
+ m.coefficient.reduce()
739
+ }
740
+
741
+ if (this.length === 0) {
742
+ return this.zero()
743
+ }
744
+
745
+ this.#invalidateCache()
746
+ return this.reorder()
747
+ }
748
+
749
+ reorder(letter = 'x', revert = false): this {
750
+ const otherLetters = this.variables.filter(x => x !== letter)
751
+ this.#monoms.sort(function (a, b) {
752
+ const da = a.degree(letter).value
753
+ const db = b.degree(letter).value
754
+
755
+ // Values are different
756
+ if (da !== db) {
757
+ return revert ? da - db : db - da
758
+ }
759
+
760
+ // if values are equals, check other letters - it must be reverted in that case !
761
+ if (otherLetters.length > 0) {
762
+ for (const L of otherLetters) {
763
+ const da = a.degree(L).value
764
+ const db = b.degree(L).value
765
+
766
+ // Values are different
767
+ if (da !== db) {
768
+ return revert ? da - db : db - da
769
+ }
770
+ }
771
+ }
772
+
773
+
774
+ return 0
775
+ })
776
+
777
+ return this
778
+ }
779
+
780
+ /**
781
+ * Replace a variable (letter) by a polynom.
782
+ * @param letter
783
+ * @param P
784
+ */
785
+ replaceBy(letter: string, P: Polynom): this {
786
+ let pow: Fraction
787
+ const resultPolynom: Polynom = new Polynom().zero()
788
+
789
+ for (const m of this.monoms) {
790
+ if (!m.hasVariable(letter) || m.literal[letter].isZero()) {
791
+ resultPolynom.add(m.clone())
792
+ } else {
793
+ // We have found a variable to replace.
794
+
795
+ // Get the power.
796
+ pow = m.literal[letter].clone()
797
+
798
+ // Remove the variable from the monom
799
+ m.removeVariable(letter)
800
+
801
+ // Add the new monom to the result polynom
802
+ if (pow.isStrictlyNegative()) {
803
+ throw new MathError(`Cannot replace variable "${letter}" with negative power in a Polynom`)
804
+ }
805
+ resultPolynom.add(P.clone().pow(pow.numerator).multiply(m))
806
+ }
807
+ }
808
+
809
+
810
+ // Reduce the monoms
811
+ this.#monoms = resultPolynom.reduce().monoms
812
+ this.#invalidateCache()
813
+ return this
814
+ }
815
+
816
+ // ------------------------------------------
817
+
818
+ root(): Polynom {
819
+ throw new MathError('Cannot take the root from a polynom')
820
+ }
821
+
822
+ get roots(): Solution[] {
823
+ return this.#rootsCache ? this.#roots : this.getZeroes()
824
+ }
825
+
826
+ set roots(value: Solution[]) {
827
+ this.#rootsCache = true
828
+ this.#roots = value
829
+ }
830
+
831
+ setVariable(value: string): this {
832
+ this.#defaultVariable = value
833
+
834
+ return this
835
+ }
836
+
837
+ sqrt(): Polynom {
838
+ throw new MathError('Cannot take the square root from a polynom')
839
+ }
840
+
841
+ subtract(...values: InputAlgebra<Polynom>[]): this {
842
+ for (const value of values) {
843
+ if (value instanceof Polynom) {
844
+ this.add(value.clone().opposite())
845
+ } else if (value instanceof Monom) {
846
+ this.#monoms.push(value.clone().opposite())
847
+ } else {
848
+ this.#monoms.push(new Monom(value).opposite())
849
+ }
850
+ }
851
+
852
+
853
+ return this.reduce()
854
+ }
855
+
856
+ tableOfSigns(): TABLE_OF_SIGNS {
857
+ // returns ['+-', 'd|t|z', '+-']...
858
+
859
+ // global roots from eventually Polyfactor. Allows to add "extra column".
860
+ const roots: Solution[] = this.roots
861
+
862
+ // Build the table os sign length and default values
863
+ // The signs looks like: ['+', 't', '+', 't', '+', 't', '+']
864
+ let signs: TABLE_OF_SIGNS_VALUES[] = new Array(2 * roots.length + 1)
865
+ .fill('')
866
+ .map((_x, index) => {
867
+ return index % 2 === 0 ? '' : 'z'
868
+ })
869
+
870
+
871
+ if (signs.length === 1) {
872
+ // The polynom is a constant or has not roots
873
+ const [a] = this.getCoefficients().map(x => x.value)
874
+ signs = replace_in_array(signs, '', a > 0 ? '+' : '-')
875
+ } else if (this.degree().isOne()) {
876
+ // First degree: ax+b
877
+ const [a] = this.getCoefficients().map(x => x.value)
878
+
879
+ // Get the index of the zero.
880
+ signs[0] = a > 0 ? '-' : '+'
881
+ signs[1] = 'z'
882
+ signs[2] = a > 0 ? '+' : '-'
883
+ } else {
884
+ const testingRoots = [
885
+ roots[0].value - 1,
886
+ ...roots.map((_root, index) => {
887
+ return index === roots.length - 1 ?
888
+ roots[index].value + 1 :
889
+ (roots[index].value + roots[index + 1].value) / 2
890
+ })
891
+ ]
892
+
893
+ testingRoots.forEach((test, index) => {
894
+ const sign = this.evaluate({[this.variables[0] ?? 'x']: test}, true)
895
+ signs[index * 2] = sign > 0 ? '+' : '-'
896
+ })
897
+ }
898
+
899
+ return {roots, signs}
900
+ }
901
+
902
+ get value(): number | undefined {
903
+ if (this.degree().isZero()) {
904
+ return this.monoms[0]?.coefficient.value ?? 0
905
+ }
906
+
907
+ return undefined
908
+ }
909
+
910
+ get variables(): string[] {
911
+ return [...new Set(this.#monoms.flatMap(m => m.variables))].sort()
912
+ }
913
+
914
+ /**
915
+ * Set the polynom to zero.
916
+ * @returns {this}
917
+ */
918
+ zero(): this {
919
+ this.#monoms = []
920
+ this.#monoms.push(new Monom().zero())
921
+ this.#invalidateCache()
922
+ return this
923
+ }
924
+
925
+ get zeroes(): Solution[] {
926
+ return this.getZeroes()
927
+ }
928
+
929
+ #compare(P: Polynom, sign?: string): boolean {
930
+ sign ??= '='
931
+
932
+ // Create clone version to reduce them without altering the original polynoms.
933
+ const cP1 = this.clone().reduce().reorder()
934
+ const cP2 = P.clone().reduce().reorder()
935
+
936
+ switch (sign) {
937
+ case '=':
938
+ // They must have the isSame length and the isSame degree
939
+ if (cP1.length !== cP2.length || !cP1.degree().isEqual(cP2.degree())) {
940
+ return false
941
+ }
942
+
943
+ // Check if the coefficients are the isSame.
944
+ return cP1.monoms
945
+ .every((m1, index) => m1.isEqual(cP2.monoms[index]))
946
+
947
+ case 'same':
948
+ // They must have the same length and the same degree
949
+ if (cP1.length !== cP2.length || !cP1.degree().isEqual(cP2.degree())) {
950
+ return false
951
+ }
952
+
953
+ return cP1.monoms
954
+ .every((m1, index) => m1.isSameAs(cP2.monoms[index]))
955
+
956
+ default:
957
+ return false
958
+ }
959
+ }
960
+
961
+ #divideByFraction(F: Fraction): this {
962
+ for (const m of this.#monoms) {
963
+ m.coefficient.divide(F)
964
+ }
965
+
966
+ this.#invalidateCache()
967
+ return this
968
+ }
969
+
970
+ #divideByInteger(nb: number): this {
971
+ const nbF = new Fraction(nb)
972
+ for (const m of this.#monoms) {
973
+ m.coefficient.divide(nbF)
974
+ }
975
+
976
+ this.#invalidateCache()
977
+ return this
978
+ }
979
+
980
+ #evaluateAsNumeric(values: literalType<number | Fraction> | InputValue<Fraction>): number {
981
+ let r = 0
982
+ this.#monoms.forEach(monom => {
983
+ r += monom.evaluate(values, true) as number
984
+ })
985
+
986
+ return r
987
+ }
988
+
989
+ #genDisplay(output?: string, forceSign?: boolean, wrapParentheses?: boolean, withAllMultiplicationSign?: boolean): string {
990
+ let P = ''
991
+
992
+ for (const k of this.#monoms) {
993
+ if (k.coefficient.value === 0) {
994
+ continue
995
+ }
996
+
997
+ // The monom to be displayed
998
+ let m
999
+ if (withAllMultiplicationSign) {
1000
+ m = k.plotFunction
1001
+ } else {
1002
+ m = (output === 'tex') ? k.tex : k.display
1003
+ }
1004
+
1005
+ P += `${(k.coefficient.sign() === 1 && (P !== '' || forceSign === true)) ? '+' : ''}${m}`
1006
+ }
1007
+
1008
+ if (wrapParentheses === true && this.length > 1) {
1009
+ if (output === 'tex') {
1010
+ P = `\\left( ${P} \\right)`
1011
+ } else {
1012
+ P = `(${P})`
1013
+ }
1014
+ }
1015
+
1016
+
1017
+ if (P === '') {
1018
+ P = '0'
1019
+ }
1020
+
1021
+ return P
1022
+ }
1023
+
1024
+ #invalidateCache(): void {
1025
+ this.#rootsCache = false
1026
+ this.#factors = []
1027
+ }
1028
+
1029
+ #multiplyByFraction(F: Fraction): this {
1030
+ for (const m of this.#monoms) {
1031
+ m.coefficient.multiply(F)
1032
+ }
1033
+
1034
+ return this.reduce()
1035
+ }
1036
+
1037
+ #multiplyByInteger(nb: number): this {
1038
+ return this.#multiplyByFraction(new Fraction(nb))
1039
+ }
1040
+
1041
+ #multiplyByMonom(M: Monom): this {
1042
+ for (const m of this.#monoms) {
1043
+ m.multiply(M)
1044
+ }
1045
+
1046
+ return this.reduce()
1047
+ }
1048
+
1049
+ #multiplyByPolynom(P: Polynom): this {
1050
+ const M: Monom[] = []
1051
+ for (const m1 of this.#monoms) {
1052
+ for (const m2 of P.monoms) {
1053
+ M.push(Monom.xMultiply(m1, m2))
1054
+ }
1055
+ }
1056
+
1057
+ this.#monoms = M
1058
+ return this.reduce()
1059
+ }
1060
+
1061
+ #parseString(inputStr: string, ...values: InputValue<Fraction>[]): this {
1062
+ if (values.length === 0) {
1063
+ // Parse the polynom using the shutting yard algorithm
1064
+ if (inputStr !== '' && !isNaN(Number(inputStr))) {
1065
+ this.empty()
1066
+ // It's a simple number.
1067
+ const m = new Monom(Number(inputStr))
1068
+ // m.coefficient = new Fraction(inputStr);
1069
+ // m.literalStr = '';
1070
+ this.add(m)
1071
+ return this
1072
+ }
1073
+
1074
+ // Parse the string.
1075
+ return this.#shutingYardToReducedPolynom(inputStr)
1076
+ } else if (/^[a-z]+$/.test(inputStr)) {
1077
+ // We assume the inputStr contains only letters.
1078
+ this.empty()
1079
+
1080
+ const fractions = values.map(x => new Fraction(x))
1081
+
1082
+ // Multiple setLetter version
1083
+ if (inputStr.length > 1) {
1084
+ const letters = inputStr.split('')
1085
+
1086
+ if (fractions.length > letters.length + 1) {
1087
+ throw new InvalidArgumentError(`Too many values: ${letters.length} letters but ${fractions.length} values provided`)
1088
+ }
1089
+
1090
+ let i = 0
1091
+
1092
+ for (const F of fractions) {
1093
+ const m = new Monom()
1094
+ m.coefficient = F.clone()
1095
+ m.literalStr = letters[i] ?? ''
1096
+ this.add(m)
1097
+ i++
1098
+ }
1099
+ }
1100
+ // Single setLetter version
1101
+ else {
1102
+ let n = fractions.length - 1
1103
+ for (const F of fractions) {
1104
+ const m = new Monom()
1105
+ m.coefficient = F.clone()
1106
+ m.literalStr = `${inputStr}^${n}`
1107
+ this.add(m)
1108
+ n--
1109
+ }
1110
+ }
1111
+ return this
1112
+ } else {
1113
+ return this.zero()
1114
+ }
1115
+
1116
+ }
1117
+
1118
+ #shutingYardAddToken(stack: Polynom[], element: Token): void {
1119
+ switch (element.tokenType) {
1120
+ case ShutingyardType.COEFFICIENT:
1121
+ stack.push(new Polynom(element.token))
1122
+ break
1123
+
1124
+ case ShutingyardType.VARIABLE:
1125
+ stack.push(new Polynom().add(new Monom(element.token)))
1126
+ break
1127
+
1128
+ case ShutingyardType.CONSTANT:
1129
+ throw new ParseError('Unsupported CONSTANT token in Polynom parser')
1130
+
1131
+ case ShutingyardType.OPERATION:
1132
+ if (stack.length >= 2) {
1133
+ const b = stack.pop()
1134
+ const a = stack.pop()
1135
+
1136
+ // Check if the polynoms are not undefined.
1137
+ if (a === undefined || b === undefined) {
1138
+ throw new ParseError('Unexpected undefined operand in Polynom parser stack')
1139
+ }
1140
+
1141
+ if (element.token === '+') {
1142
+ stack.push(a.add(b))
1143
+ } else if (element.token === '-') {
1144
+ stack.push(a.subtract(b))
1145
+ } else if (element.token === '*') {
1146
+ stack.push(a.multiply(b))
1147
+ } else if (element.token === '/') {
1148
+ if (b.degree().isStrictlyPositive()) {
1149
+ throw new MathError('Cannot divide a Polynom by another Polynom of degree > 0')
1150
+ } else {
1151
+ // a.divide(b.monoms[0].coefficient)
1152
+ stack.push(a.divide(b.monoms[0].coefficient))
1153
+ }
1154
+ } else if (element.token === '^') {
1155
+ if (b.degree().isStrictlyPositive()) {
1156
+ throw new MathError('Cannot elevate a polynom with another polynom !')
1157
+ } else if (b.monoms[0].coefficient.isNatural())
1158
+ // Natural (positive) power
1159
+ {
1160
+ stack.push(a.pow(b.monoms[0].coefficient.value))
1161
+ } else {
1162
+ // Only allow power if the previous polynom is only a monom, without coefficient.
1163
+ if (a.monoms.length === 1 && a.monoms[0].coefficient.isOne()) {
1164
+ for (const letter in a.monoms[0].literal) {
1165
+ a.monoms[0].literal[letter].multiply(b.monoms[0].coefficient)
1166
+ }
1167
+
1168
+ stack.push(a)
1169
+ } else {
1170
+ throw new MathError('Cannot raise a multi-monom Polynom to a fractional power')
1171
+ }
1172
+ }
1173
+ }
1174
+
1175
+ } else if (element.token === '-') {
1176
+ const a = stack.pop()
1177
+ if (a) {
1178
+ stack.push(a.opposite())
1179
+ }
1180
+ } else {
1181
+ throw new ParseError("Error parsing the polynom")
1182
+ }
1183
+
1184
+
1185
+ break
1186
+
1187
+ case ShutingyardType.MONOM:
1188
+ // Should never appear.
1189
+ throw new ParseError('Unexpected MONOM token in polynom parser')
1190
+
1191
+ case ShutingyardType.FUNCTION:
1192
+ // Should never appear.
1193
+ throw new ParseError(`Unsupported function token "${element.token}" in polynom parser`)
1194
+ }
1195
+
1196
+
1197
+ }
1198
+
1199
+ /**
1200
+ * Main parse using a shutting yard class
1201
+ * @param inputStr
1202
+ */
1203
+ #shutingYardToReducedPolynom(inputStr: string): this {
1204
+ // Get the RPN array of the current expression
1205
+ let SY: ShutingYard
1206
+ try {
1207
+ SY = new ShutingYard().parse(inputStr)
1208
+ } catch (e) {
1209
+ throw new ParseError(`Cannot parse "${inputStr}" as a Polynom`, {cause: e})
1210
+ }
1211
+ const rpn: { token: string, tokenType: ShutingyardType }[] = SY.rpn
1212
+
1213
+ // New version for reducing shuting yard.
1214
+ this.zero()
1215
+
1216
+ const stack: Polynom[] = []
1217
+
1218
+ // Loop through the each element of the RPN
1219
+ for (const element of rpn) {
1220
+ this.#shutingYardAddToken(stack, element)
1221
+ }
1222
+
1223
+
1224
+ if (stack.length === 1) {
1225
+ this.add(stack[0])
1226
+ }
1227
+
1228
+
1229
+ return this.reorder()
1230
+ }
1231
+
1232
+
1233
+ }