pimath 0.2.14 → 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 +850 -1879
  2. package/dist/pimath.js.map +1 -1
  3. package/package.json +1 -1
  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,928 +1,932 @@
1
- /***
2
- * Monom class
3
- */
4
- import type {
5
- IAlgebra,
6
- IAnalyse,
7
- IExpression,
8
- InputAlgebra,
9
- InputValue,
10
- IPiMathObject,
11
- literalType
12
- } from "../pimath.interface"
13
- import {Fraction} from "../coefficients"
14
- import {Numeric} from "../numeric"
15
-
16
- import {ShutingYard, ShutingyardType, type Token} from "piexpression"
17
- import {MathError, NotImplementedError, ParseError} from "../errors"
18
-
19
- export class Monom implements IPiMathObject<Monom>, IExpression<Monom>, IAnalyse<Monom>, IAlgebra<Monom> {
20
- #coefficient: Fraction
21
- #literal: literalType<Fraction>
22
-
23
- constructor(value?: InputValue<Fraction>)
24
- constructor(value?: Monom)
25
- constructor(value?: InputAlgebra<Fraction>) {
26
- this.#coefficient = new Fraction().zero()
27
- this.#literal = {}
28
-
29
- if (value !== undefined) {
30
- // A string is given - try to parse the value.
31
- this.parse(value)
32
- }
33
-
34
- return this
35
- }
36
-
37
- // -----------------------------------------
38
- /**
39
- * Parse a string to a monom. The string may include fraction.
40
- * @param inputStr
41
- */
42
- public parse(inputStr: InputAlgebra<Monom>): this {
43
- // Initialize the monom
44
- this.#coefficient = new Fraction()
45
- this.#literal = {}
46
-
47
- if (inputStr instanceof Monom) {
48
- this.#coefficient = inputStr.#coefficient.clone()
49
-
50
- // Copy the literal parts
51
- this.#cloneLiteral(inputStr)
52
- return this
53
- }
54
-
55
- if (inputStr instanceof Fraction) {
56
- this.#coefficient = inputStr.clone()
57
- return this
58
- }
59
-
60
- if (typeof inputStr === 'number') {
61
- this.#coefficient = new Fraction(inputStr)
62
- return this
63
- }
64
-
65
-
66
- if (!isNaN(Number(inputStr))) {
67
- this.#coefficient = new Fraction(Number(inputStr))
68
- } else {
69
- this.#shutingYardToReducedMonom(inputStr)
70
- }
71
- return this
72
- }
73
-
74
- /**
75
- * Clone the current Monom.
76
- */
77
- public clone = (): Monom => {
78
- const F: Monom = new Monom()
79
-
80
- F.coefficient = this.#coefficient.clone()
81
-
82
- // Copy the literal parts.
83
- for (const k in this.#literal) {
84
- F.setLetter(k, this.#literal[k].clone())
85
- }
86
- return F
87
- }
88
-
89
- /**
90
- * Get the tex output of the monom
91
- */
92
- public get tex(): string {
93
- return this.#format('tex')
94
- }
95
-
96
- // Display getter
97
- /**
98
- * This display getter is to be used in the polynom display getter
99
- */
100
- public get display(): string {
101
- return this.#format('display')
102
- }
103
-
104
- public static gcd = (...monoms: Monom[]): Monom => {
105
- // All the monoms must be with natural powers...
106
- for (const m of monoms) {
107
- if (m.containsRationalPower()) {
108
- return new Monom().zero()
109
- }
110
- }
111
-
112
- const M = new Monom(),
113
- n: number = Numeric.gcd(...monoms.map(value => value.coefficient.numerator)),
114
- d: number = Numeric.lcm(...monoms.map(value => value.coefficient.denominator))
115
-
116
- // Get the coefficient.
117
- M.coefficient = new Fraction(n, d).reduce()
118
-
119
- // Set the literal parts - go through each monoms literal parts and get only the lowest degree of each letters.
120
- for (const m of monoms) {
121
- // Remove the inexistant letters from the resulting monom
122
- for (const letter in M.literal) {
123
- if (!(letter in m.literal)) {
124
- M.removeVariable(letter)
125
- }
126
- }
127
- for (const letter in m.literal) {
128
- if (!M.hasVariable(letter) && m.literal[letter].isStrictlyPositive()) {
129
- M.literal[letter] = m.literal[letter].clone()
130
- } else {
131
- M.literal[letter] = new Fraction(Math.min(m.literal[letter].value, M.literal[letter].value))
132
- }
133
- }
134
- }
135
-
136
- return M
137
- }
138
-
139
- /**
140
- * Multiply two monoms and return a NEW monom.
141
- * @param monoms
142
- */
143
- public static xMultiply = (...monoms: Monom[]): Monom => {
144
- const M = new Monom().one()
145
-
146
- for (const m of monoms) {
147
- M.multiply(m)
148
- }
149
-
150
- return M
151
- }
152
-
153
- /**
154
- * Add all similar monoms. If they aren't similar, they are simply skipped.
155
- * @param M (Monom[]) The monoms to add.
156
- */
157
- public add = (...M: InputAlgebra<Fraction>[]): this => {
158
- for (const m of M) {
159
- // If the value given is not a monom, create it.
160
- const mAsMonom = (!(m instanceof Monom)) ? new Monom(m) : m
161
-
162
- if (this.isSameAs(mAsMonom)) {
163
- if (this.isZero()) {
164
- this.#cloneLiteral(mAsMonom)
165
- }
166
-
167
- this.#coefficient.add(mAsMonom.coefficient)
168
- } else {
169
- throw new MathError('Add monom: ' + this.display + ' is not similar with ' + mAsMonom.display)
170
- }
171
- }
172
- return this
173
- }
174
-
175
- /**
176
- * Get the coefficient \\(k\\) of the Monom \\(k\\cdot x^{n}\\)
177
- * @returns {Fraction}
178
- */
179
- public get coefficient(): Fraction {
180
- return this.#coefficient
181
- }
182
-
183
- /**
184
- * Set the coefficient \\(k\\) value of the monom
185
- * @param {Fraction | number | string} F
186
- */
187
- public set coefficient(F: InputValue<Fraction>) {
188
- this.#coefficient = new Fraction(F)
189
- }
190
-
191
- public containsRationalPower = (): boolean => {
192
- return Object.values(this.#literal).some((value) => value.isRational())
193
- }
194
-
195
- /**
196
- * Get the degree of a monom. If no setLetter is given, the result will be the global degree.
197
- * @param letter (string) Letter to get to degree (power)
198
- */
199
- public degree = (letter?: string): Fraction => {
200
- if (this.variables.length === 0) {
201
- return new Fraction().zero()
202
- }
203
- if (letter === undefined) {
204
- // Not setLetter given -> we get the global monom degree (sum of all the letters).
205
- return Object.values(this.#literal).reduce((t, n) => t.clone().add(n))
206
- } else {
207
- // A setLetter is given -> get the corresponding power.
208
- return !this.hasVariable(letter) ? new Fraction().zero() : this.#literal[letter].clone()
209
- }
210
- }
211
-
212
- /**
213
- * Derivative the monom
214
- * @param letter
215
- */
216
- public derivative = (letter = 'x'): Monom => {
217
- // No setLetter given - assume it's the setLetter 'x'
218
- if (this.hasVariable(letter)) {
219
- const d = this.#literal[letter].clone(),
220
- dM = this.clone()
221
-
222
- // Subtract one to the degree.
223
- dM.#literal[letter].subtract(1)
224
-
225
- // Multiply the coefficient by the previous degree
226
- dM.#coefficient.multiply(new Fraction(d.clone()))
227
- return dM
228
- } else {
229
- return new Monom().zero()
230
- }
231
- }
232
-
233
- /**
234
- * Divide the current monoms by multiple monoms
235
- * @param M (Monom[])
236
- */
237
- public divide = (...M: InputAlgebra<Fraction>[]): this => {
238
- // Depending on the given value, choose the current item
239
- for (const m of M) {
240
- // If the value given is not a monom, create it.
241
- const mAsMonom = (!(m instanceof Monom)) ? new Monom(m) : m
242
-
243
- // Divide the coefficient
244
- this.#coefficient.divide(mAsMonom.coefficient)
245
-
246
- // Subtract the power values
247
- for (const letter in mAsMonom.literal) {
248
-
249
- this.#literal[letter] = this.hasVariable(letter) ?
250
- this.#literal[letter].subtract(mAsMonom.literal[letter]) :
251
- mAsMonom.literal[letter].clone().opposite()
252
-
253
-
254
- // If the power of a particular setLetter is zero, delete it from the literal part..
255
- if (this.#literal[letter].isZero()) {
256
- this.removeVariable(letter)
257
- }
258
- }
259
- }
260
- return this
261
- }
262
-
263
- public get dividers(): Monom[] {
264
- // Decompose only if the coefficient is a natural number
265
- if (!this.coefficient.isRelative()) {
266
- return [this.clone()]
267
- }
268
-
269
- // Decompose only if the power values are natural numbers.
270
- if (this.containsRationalPower()) {
271
- return [this.clone()]
272
- }
273
-
274
- // Security : do not do this if greater than 10000
275
- if (this.coefficient.numerator > 1000000) {
276
- return [this.clone()]
277
- }
278
-
279
- const dividers = Numeric.dividers(Math.abs(this.coefficient.numerator))
280
-
281
- // Decompose the literals parts.
282
- let literals: literalType<Fraction>[] = []
283
- for (const L in this.literal) {
284
- // L is the letter.
285
- literals = this.#getLiteralDividers(literals, L)
286
- }
287
-
288
- const monomDividers: Monom[] = []
289
- if (literals.length > 0 && dividers.length > 0) {
290
- for (const N of dividers) {
291
- for (const L of literals) {
292
- const M = new Monom()
293
- M.coefficient = new Fraction(N)
294
- M.literal = L
295
- monomDividers.push(M)
296
- }
297
- }
298
- } else if (dividers.length === 0) {
299
- for (const L of literals) {
300
- const M = new Monom()
301
- M.coefficient = new Fraction().one()
302
- M.literal = L
303
- monomDividers.push(M)
304
- }
305
- } else {
306
- for (const N of dividers) {
307
- const M = new Monom()
308
- M.coefficient = new Fraction(N)
309
- monomDividers.push(M)
310
- }
311
- }
312
-
313
- return monomDividers.length === 0 ? [new Monom().one()] : monomDividers
314
- }
315
-
316
- /**
317
- * Evaluate a monom. Each setLetter must be assigned to a Fraction.
318
- * @param values Dictionary of <setLetter: Fraction>
319
- * @param asNumeric
320
- */
321
- public evaluate = (values: literalType<number | Fraction> | InputValue<Fraction>, asNumeric?: boolean): Fraction | number => {
322
- // If as numeric return the numeric value
323
- if (asNumeric === true) {
324
- // Convert all values to numeric
325
- // If the value is a Fraction, convert it to a number
326
- if (values instanceof Fraction) {
327
- return this.#evaluateAsNumeric(values.value)
328
- }
329
-
330
- // If the value is a number, return the numeric value
331
- if (typeof values === 'number') {
332
- return this.#evaluateAsNumeric(values)
333
- }
334
-
335
- // If the value is an object, return the numeric value
336
- if (typeof values === 'object') {
337
- // Convert {[key:string]:Fraction} to {[key:string]:number}
338
- const tmpValues: literalType<number> = {}
339
- for (const L in values) {
340
- tmpValues[L] = new Fraction(values[L]).value
341
- }
342
-
343
- return this.#evaluateAsNumeric(tmpValues)
344
- }
345
- }
346
-
347
- // The answer must be a Fraction
348
- const r = this.coefficient.clone()
349
-
350
- if (typeof values === 'number' || values instanceof Fraction) {
351
- const tmpValues: literalType<Fraction> = {}
352
- tmpValues[this.variables[0]] = new Fraction(values)
353
- return this.evaluate(tmpValues)
354
- }
355
-
356
- if (typeof values === 'object') {
357
- if (this.variables.length === 0) {
358
- return this.coefficient
359
- }
360
-
361
- for (const L in this.#literal) {
362
- const value = new Fraction(values[L])
363
-
364
- r.multiply(value.pow(this.#literal[L]))
365
- }
366
- }
367
-
368
- return r
369
- }
370
-
371
- // -------------------------------------
372
- /**
373
- * Determine if a monom contains a setLetter in it's literal part
374
- * @param letter
375
- */
376
- public hasVariable = (letter?: string): boolean => {
377
- // The letter was not found
378
- return Object.hasOwn(this.#literal, letter ?? 'x')
379
- }
380
-
381
- public integrate(a: InputValue<Fraction>, b: InputValue<Fraction>, letter?: string): Fraction {
382
- const primitive = this.primitive(letter)
383
-
384
- return (primitive.evaluate(b, false) as Fraction)
385
- .subtract(primitive.evaluate(a, false) as Fraction)
386
- }
387
-
388
- public inverse = (): this => {
389
- this.#coefficient.inverse()
390
- for (const letter in this.#literal) {
391
- this.#literal[letter].opposite()
392
- }
393
- return this
394
- }
395
-
396
- public isDivisible = (div: Monom): boolean => {
397
- // For all variables (letters), the current monom must have a degree higher than the divider
398
- if (div.degree().isStrictlyPositive()) {
399
- for (const letter of div.variables) {
400
- if (!this.degree(letter).isGeq(div.degree(letter))) {
401
- return false
402
- }
403
- }
404
- }
405
-
406
- // If the coefficient is rational, we suppose we don't need to check the division by the coefficient.
407
- if (this.coefficient.isRational() || div.coefficient.isRational()) {
408
- return true
409
- }
410
-
411
- return this.coefficient.clone().divide(div.coefficient).isRelative()
412
- }
413
-
414
- /**
415
- * Determine if two monoms are equals
416
- * @param M
417
- */
418
- public isEqual = (M: Monom): boolean => {
419
- return this.isSameAs(M) && this.#coefficient.isEqual(M.coefficient)
420
- }
421
-
422
- public isLiteralSquare = (): boolean => {
423
- for (const letter in this.literal) {
424
- // A literal square must have a natural power
425
- if (this.literal[letter].isRational()) {
426
- return false
427
- }
428
-
429
- // The natural power must be even - if it's odd, the monom is not a square.
430
- if (this.literal[letter].isOdd()) {
431
- return false
432
- }
433
- }
434
-
435
- return true
436
- }
437
-
438
- /**
439
- * Determine if the monom is one
440
- */
441
- public isOne = (): boolean => {
442
- return this.#coefficient.value === 1 && this.variables.length === 0
443
- }
444
-
445
- /**
446
- * Determine if two monoms are similar
447
- * @param M
448
- */
449
- public isSameAs = (M: Monom): boolean => {
450
- // Get the list of all variables from both monoms.
451
- const M1: string[] = this.variables
452
- const M2: string[] = M.variables
453
-
454
- // Get the list of all variables from both monoms.
455
- const K: string[] = M1.concat(M2.filter((item) => !M1.includes(item)))
456
-
457
- // If one of the monom is zero, it is the same than the other.
458
- if (this.isZero() || M.isZero()) {
459
- return true
460
- }
461
-
462
- // Both monoms has no literal part.
463
- if (M1.length === 0 && M2.length === 0) {
464
- return true
465
- }
466
-
467
- // Both monoms must have the same variables
468
- if (M1.length !== M2.length) {
469
- return false
470
- }
471
-
472
- // To _compare, both must be different from zero.
473
- if (!this.isZero() && !M.isZero()) {
474
- for (const key of K) {
475
- // The variable is not available in one of the monom
476
- if (!this.hasVariable(key) || !M.hasVariable(key)) {
477
- return false
478
- }
479
-
480
- // The variable does not have the same power in each monoms.
481
- if (!this.#literal[key].isEqual(M.literal[key])) {
482
- return false
483
- }
484
- }
485
- }
486
-
487
- // All are positive check - the monoms are the sames.
488
- return true
489
- }
490
-
491
- public isSquare = (): boolean => {
492
- if (!this.coefficient.isSquare()) {
493
- return false
494
- }
495
- return this.isLiteralSquare()
496
- }
497
-
498
- /**
499
- * Determine if the monom is null
500
- */
501
- public isZero = (): boolean => {
502
- return this.#coefficient.value === 0
503
- }
504
-
505
- /**
506
- * Get the literal part of \\(x^{n_1}y^{n_2}\\) as dictionary \\[\\begin{array}{ll}x&=n_1\\\\y&=n_2\\end{array}\\]
507
- * @returns {literalType}
508
- */
509
- public get literal(): literalType<Fraction> {
510
- return this.#literal
511
- }
512
-
513
- /**
514
- * Set the literal part of the monom. Must be a dictionary {x: Fraction, y: Fraction, ...}
515
- * @param {literalType<Fraction>} L
516
- */
517
- public set literal(L: literalType<Fraction>) {
518
- this.#literal = L
519
- }
520
-
521
- /**
522
- * Set the literal part of the monom from a string
523
- * @param inputStr String like x^2y^3
524
- */
525
- public set literalStr(inputStr: string) {
526
- // Match all x^n
527
- for (const v of [...inputStr.matchAll(/([a-z])\^([+-]?[0-9]+)/g)]) {
528
- // Create the default letter entry if necessary.
529
- if (!(v[1] in this.#literal)) {
530
- this.#literal[v[1]] = new Fraction().zero()
531
- }
532
-
533
- // Add the new value.
534
- this.#literal[v[1]].add(+v[2])
535
- }
536
-
537
- // Match all x
538
- for (const v of [...inputStr.matchAll(/([a-z](?!\^))/g)]) {
539
- // Match all single letters
540
- if (!(v[1] in this.#literal)) {
541
- this.#literal[v[1]] = new Fraction().zero()
542
- }
543
-
544
- // Add one to the value.
545
- this.#literal[v[1]].add(1)
546
- }
547
- }
548
-
549
- /**
550
- * Multiple multiple monoms to the current monom
551
- * @param M (Monom[]) The monoms to multiply to.
552
- */
553
- public multiply = (...M: InputAlgebra<Fraction>[]): this => {
554
- for (const m of M) {
555
- // If the value given is not a monom, create it.
556
- const mAsMonom = (!(m instanceof Monom)) ? new Monom(m) : m
557
-
558
- // Multiply the coefficient.
559
- this.#coefficient.multiply(mAsMonom.coefficient)
560
-
561
- // Multiply the literal parts.
562
- for (const letter in mAsMonom.literal) {
563
- if (!this.hasVariable(letter)) {
564
- this.#literal[letter] = mAsMonom.literal[letter].clone()
565
- } else {
566
- this.#literal[letter].add(mAsMonom.literal[letter])
567
- }
568
- }
569
- }
570
- return this
571
- }
572
-
573
- /**
574
- * Create a one value monom
575
- */
576
- public one = (): this => {
577
- this.#coefficient = new Fraction().one()
578
- this.#literal = {}
579
- return this
580
- }
581
-
582
- /**
583
- * Get the opposite
584
- * Returns a monom.
585
- */
586
- public opposite = (): this => {
587
- this.#coefficient.opposite()
588
- return this
589
- }
590
-
591
- public get plotFunction(): string {
592
- return this.#format('plot')
593
- }
594
-
595
- /**
596
- * Get the pow of a monom.
597
- * @param nb (number) : Mathematical pow
598
- */
599
- public pow = (nb: number | Fraction): this => {
600
- this.#coefficient.pow(nb)
601
- for (const letter in this.#literal) {
602
- this.#literal[letter].multiply(nb)
603
- }
604
- return this
605
- }
606
-
607
- public primitive = (letter = 'x'): Monom => {
608
- // Zero monom
609
- const M = this.clone()
610
- let degree: Fraction
611
-
612
- if (M.hasVariable(letter)) {
613
- degree = M.degree(letter).clone().add(1)
614
- M.coefficient = M.coefficient.clone().divide(degree)
615
- M.setLetter(letter, degree)
616
- } else {
617
- // There is no letter.
618
-
619
- // The coefficient might be zero (=> x) or a number a (=> ax)
620
- if (M.coefficient.isZero()) {
621
- M.coefficient = new Fraction().one()
622
- }
623
- M.setLetter(letter, 1)
624
- }
625
-
626
- return M
627
- }
628
-
629
- public reduce = (): this => {
630
- // Reduce the coefficient
631
- this.coefficient.reduce()
632
-
633
- // Reduce the literal parts (removing null powers)
634
- for (const letter in this.#literal) {
635
- if (this.#literal[letter].isZero()) {
636
- this.removeVariable(letter)
637
- }
638
- }
639
- return this
640
- }
641
-
642
- public removeVariable(letter: string) {
643
- /* eslint-disable */
644
- delete this.#literal[letter]
645
- /* eslint-enable */
646
- }
647
-
648
- /**
649
- * Get the nth-root of the monom
650
- * @todo Not yet implemented
651
- */
652
- public root = (): this => {
653
- // TODO: implement nth-root for monoms
654
- throw new NotImplementedError('Method not implemented.')
655
- }
656
-
657
- /**
658
- * Set the power of a particular setLetter
659
- * @param letter (string) Letter to change
660
- * @param pow (number) Power of the setLetter (must be positive integer.
661
- */
662
- public setLetter = (letter: string, pow: InputValue<Fraction>): this => {
663
- if (!(pow instanceof Fraction)) {
664
- return this.setLetter(letter, new Fraction(pow))
665
- }
666
-
667
- // Set the power of the letter to zero => remove it
668
- if (this.hasVariable(letter) && pow.isZero()) {
669
- this.removeVariable(letter)
670
- return this
671
- }
672
-
673
-
674
- this.#literal[letter] = pow.clone()
675
-
676
- return this
677
- }
678
-
679
- /**
680
- * Return the square root of a monom
681
- */
682
- public sqrt = (): this => {
683
- if (this.isSquare()) {
684
- this.#coefficient.sqrt()
685
- for (const letter in this.#literal) {
686
- this.#literal[letter].divide(2)
687
- }
688
- }
689
-
690
- // TODO: throw an error if the monom is not a square ? Or allow the result to be a monom with rational powers ?
691
- return this
692
- }
693
-
694
- /**
695
- * Subtract multiple monoms
696
- * @param M (Monom[]) The monoms to subtract
697
- */
698
- public subtract = (...M: InputAlgebra<Fraction>[]): this => {
699
- for (const m of M) {
700
- // If the value given is not a monom, create it.
701
- const mAsMonom = (!(m instanceof Monom)) ? new Monom(m) : m
702
-
703
- if (this.isSameAs(mAsMonom)) {
704
- if (this.isZero()) {
705
- this.#cloneLiteral(mAsMonom)
706
- }
707
-
708
- this.#coefficient.add(mAsMonom.clone().coefficient.opposite())
709
- } else {
710
- throw new MathError('Subtract: Is not similar: ' + mAsMonom.display)
711
- }
712
- }
713
- return this
714
- }
715
-
716
- // Getter helpers.
717
- /**
718
- * Get the variables letters
719
- */
720
- public get variables(): string[] {
721
- // const M = this.clone().clean()
722
-
723
- const L: string[] = []
724
- Object.entries(this.literal).forEach(
725
- ([key, value]) => {
726
- if (!value.isZero()) {
727
- L.push(key)
728
- }
729
- })
730
- L.sort()
731
- return L
732
- // return Object.keys(M.literal)
733
- }
734
-
735
- /**
736
- * Create a zero value monom
737
- */
738
- public zero = (): this => {
739
- this.#coefficient = new Fraction().zero()
740
- this.#literal = {}
741
- return this
742
- }
743
-
744
- #cloneLiteral(inputStr: Monom) {
745
- for (const k in inputStr.literal) {
746
- this.#literal[k] = inputStr.literal[k].clone()
747
- }
748
- }
749
-
750
- #evaluateAsNumeric = (values: literalType<number | Fraction> | InputValue<Fraction>): number => {
751
- let r = this.coefficient.value
752
-
753
- if (typeof values === "number") {
754
- const tmpValues: literalType<number> = {}
755
- const key = this.variables[0]
756
- tmpValues[key] = values
757
-
758
- return this.#evaluateAsNumeric(tmpValues)
759
- }
760
-
761
- if (values instanceof Fraction) {
762
- const tmpValues: literalType<number> = {}
763
- tmpValues[this.variables[0]] = new Fraction(values).value
764
- return this.#evaluateAsNumeric(tmpValues)
765
- }
766
-
767
- if (typeof values === 'object') {
768
- if (this.variables.length === 0) {
769
- return this.coefficient.value
770
- }
771
-
772
- for (const L in this.#literal) {
773
- const v = values[L]
774
-
775
- if (v instanceof Fraction) {
776
- r *= v.value ** (this.#literal[L].value)
777
- } else {
778
- r *= v ** (this.#literal[L].value)
779
- }
780
- }
781
- }
782
-
783
- return r
784
- }
785
-
786
- #format = (type: 'tex' | 'display' | 'plot'): string => {
787
- let L = ''
788
- const letters = Object.keys(this.#literal).sort()
789
-
790
- for (const letter of letters) {
791
- if (this.#literal[letter].isNotZero()) {
792
- if (type === 'plot' && L !== '') { L += '*' }
793
- L += letter
794
- if (this.#literal[letter].isNotEqual(1)) {
795
- L += type === 'tex'
796
- ? `^{ ${this.#literal[letter].tfrac.tex} }`
797
- : `^(${this.#literal[letter].display})`
798
- }
799
- }
800
- }
801
-
802
- const coeffStr = type === 'tex' ? this.#coefficient.frac.tex : this.#coefficient.display
803
-
804
- if (L === '') {
805
- return this.#coefficient.value !== 0 ? coeffStr : '0'
806
- }
807
-
808
- if (this.#coefficient.value === 1) { return L }
809
- if (this.#coefficient.value === -1) { return `-${L}` }
810
- if (this.#coefficient.value === 0) { return '0' }
811
- return type === 'plot' ? `${coeffStr}*${L}` : `${coeffStr}${L}`
812
- }
813
-
814
- #getLiteralDividers(arr: literalType<Fraction>[], letter: string): literalType<Fraction>[] {
815
- const tmpList: Record<string, Fraction>[] = []
816
-
817
- // Be default, this.literal[letter] should be a rational number.
818
- for (let d = 0; d <= this.literal[letter].value; d++) {
819
- if (arr.length === 0) {
820
- const litt: literalType<Fraction> = {}
821
- litt[letter] = new Fraction(d)
822
- tmpList.push(litt)
823
- } else {
824
- for (const item of arr) {
825
- const litt: literalType<Fraction> = {}
826
- for (const currentLetter in item) {
827
- litt[currentLetter] = item[currentLetter]
828
- }
829
- litt[letter] = new Fraction(d)
830
- tmpList.push(litt)
831
- }
832
- }
833
- }
834
- return tmpList
835
- }
836
-
837
- #shutingYardAddToken = (stack: Monom[], element: Token): void => {
838
- let q1: Monom, q2: Monom, m: Monom, letter: string, pow: Fraction
839
-
840
- if (element.tokenType === ShutingyardType.COEFFICIENT) {
841
- stack.push(new Monom(new Fraction(element.token)))
842
-
843
- } else if (element.tokenType === ShutingyardType.VARIABLE) {
844
- const M = new Monom().one()
845
- M.setLetter(element.token, 1)
846
- stack.push(M.clone())
847
-
848
- } else if (element.tokenType === ShutingyardType.OPERATION) {
849
- switch (element.token) {
850
- case '-':
851
- // this should only happen for negative powers or for negative coefficient.
852
- q2 = (stack.pop()) ?? new Monom().zero()
853
- q1 = (stack.pop()) ?? new Monom().zero()
854
-
855
- stack.push(q1.subtract(q2))
856
-
857
- break
858
- case '*':
859
- // Get the last element in the stack
860
- q2 = (stack.pop()) ?? new Monom().one()
861
- q1 = (stack.pop()) ?? new Monom().one()
862
-
863
- stack.push(q1.multiply(q2))
864
- break
865
- case '/':
866
- // Get the last element in the stack
867
- q2 = (stack.pop()) ?? new Monom().one()
868
- q1 = (stack.pop()) ?? new Monom().one()
869
-
870
- stack.push(q1.divide(q2))
871
- break
872
- case '^': {
873
- // get the two last elements in the stack
874
- const poppedCoefficient = stack.pop()?.coefficient
875
- pow = poppedCoefficient ?? new Fraction().one()
876
- m = stack.pop() ?? new Monom().one()
877
-
878
- letter = m.variables[0]
879
-
880
- if (letter) {
881
- m.setLetter(letter, pow)
882
- }
883
-
884
- stack.push(m)
885
- // this.multiply(m.clone())
886
- break
887
- }
888
- }
889
- }
890
- }
891
-
892
- #shutingYardToReducedMonom = (inputStr: string): this => {
893
- // Get the RPN array of the current expression
894
- let SY: ShutingYard
895
- try {
896
- SY = new ShutingYard().parse(inputStr)
897
- } catch (e) {
898
- throw new ParseError(`Cannot parse "${inputStr}" as a Monom`, {cause: e})
899
- }
900
- const rpn: { token: string, tokenType: ShutingyardType }[] = SY.rpn
901
-
902
- const stack: Monom[] = []
903
-
904
- if (rpn.length === 0) {
905
- this.zero()
906
- return this
907
- } else if (rpn.length === 1) {
908
- const element = rpn[0]
909
-
910
- this.one()
911
- if (element.tokenType === ShutingyardType.COEFFICIENT) {
912
- this.coefficient = new Fraction(element.token)
913
- } else if (element.tokenType === ShutingyardType.VARIABLE) {
914
- this.setLetter(element.token, 1)
915
- }
916
- return this
917
- } else {
918
- // Reset the monom
919
- for (const element of rpn) {
920
- this.#shutingYardAddToken(stack, element)
921
- }
922
- }
923
-
924
- this.one()
925
- this.multiply(stack[0])
926
- return this
927
- }
928
- }
1
+ /***
2
+ * Monom class
3
+ */
4
+ import type {
5
+ IAlgebra,
6
+ IAnalyse,
7
+ IExpression,
8
+ InputAlgebra,
9
+ InputValue,
10
+ IPiMathObject,
11
+ literalType
12
+ } from "../pimath.interface"
13
+ import {Fraction} from "../coefficients"
14
+ import {Numeric} from "../numeric"
15
+
16
+ import {ShutingYard, ShutingyardType, type Token} from "piexpression"
17
+ import {MathError, NotImplementedError, ParseError} from "../errors"
18
+
19
+ export class Monom implements IPiMathObject<Monom>, IExpression<Monom>, IAnalyse<Monom>, IAlgebra<Monom> {
20
+ #coefficient: Fraction
21
+ #literal: literalType<Fraction>
22
+
23
+ constructor(value?: InputValue<Fraction>)
24
+ constructor(value?: Monom)
25
+ constructor(value?: InputAlgebra<Fraction>) {
26
+ this.#coefficient = new Fraction().zero()
27
+ this.#literal = {}
28
+
29
+ if (value !== undefined) {
30
+ // A string is given - try to parse the value.
31
+ this.parse(value)
32
+ }
33
+
34
+ return this
35
+ }
36
+
37
+ // -----------------------------------------
38
+ /**
39
+ * Parse a string to a monom. The string may include fraction.
40
+ * @param inputStr
41
+ */
42
+ parse(inputStr: InputAlgebra<Monom>): this {
43
+ // Initialize the monom
44
+ this.#coefficient = new Fraction()
45
+ this.#literal = {}
46
+
47
+ if (inputStr instanceof Monom) {
48
+ this.#coefficient = inputStr.#coefficient.clone()
49
+
50
+ // Copy the literal parts
51
+ this.#cloneLiteral(inputStr)
52
+ return this
53
+ }
54
+
55
+ if (inputStr instanceof Fraction) {
56
+ this.#coefficient = inputStr.clone()
57
+ return this
58
+ }
59
+
60
+ if (typeof inputStr === 'number') {
61
+ this.#coefficient = new Fraction(inputStr)
62
+ return this
63
+ }
64
+
65
+
66
+ if (!isNaN(Number(inputStr))) {
67
+ this.#coefficient = new Fraction(Number(inputStr))
68
+ } else {
69
+ this.#shutingYardToReducedMonom(inputStr)
70
+ }
71
+ return this
72
+ }
73
+
74
+ /**
75
+ * Clone the current Monom.
76
+ */
77
+ copy(): this {
78
+ throw new NotImplementedError("copy() is not yet implemented for Monom")
79
+ }
80
+
81
+ clone = (): Monom => {
82
+ const F: Monom = new Monom()
83
+
84
+ F.coefficient = this.#coefficient.clone()
85
+
86
+ // Copy the literal parts.
87
+ for (const k in this.#literal) {
88
+ F.setLetter(k, this.#literal[k].clone())
89
+ }
90
+ return F
91
+ }
92
+
93
+ /**
94
+ * Get the tex output of the monom
95
+ */
96
+ get tex(): string {
97
+ return this.#format('tex')
98
+ }
99
+
100
+ // Display getter
101
+ /**
102
+ * This display getter is to be used in the polynom display getter
103
+ */
104
+ get display(): string {
105
+ return this.#format('display')
106
+ }
107
+
108
+ static gcd = (...monoms: Monom[]): Monom => {
109
+ // All the monoms must be with natural powers...
110
+ for (const m of monoms) {
111
+ if (m.containsRationalPower()) {
112
+ return new Monom().zero()
113
+ }
114
+ }
115
+
116
+ const M = new Monom(),
117
+ n: number = Numeric.gcd(...monoms.map(value => value.coefficient.numerator)),
118
+ d: number = Numeric.lcm(...monoms.map(value => value.coefficient.denominator))
119
+
120
+ // Get the coefficient.
121
+ M.coefficient = new Fraction(n, d).reduce()
122
+
123
+ // Set the literal parts - go through each monoms literal parts and get only the lowest degree of each letters.
124
+ for (const m of monoms) {
125
+ // Remove the inexistant letters from the resulting monom
126
+ for (const letter in M.literal) {
127
+ if (!(letter in m.literal)) {
128
+ M.removeVariable(letter)
129
+ }
130
+ }
131
+ for (const letter in m.literal) {
132
+ if (!M.hasVariable(letter) && m.literal[letter].isStrictlyPositive()) {
133
+ M.literal[letter] = m.literal[letter].clone()
134
+ } else {
135
+ M.literal[letter] = new Fraction(Math.min(m.literal[letter].value, M.literal[letter].value))
136
+ }
137
+ }
138
+ }
139
+
140
+ return M
141
+ }
142
+
143
+ /**
144
+ * Multiply two monoms and return a NEW monom.
145
+ * @param monoms
146
+ */
147
+ static xMultiply = (...monoms: Monom[]): Monom => {
148
+ const M = new Monom().one()
149
+
150
+ for (const m of monoms) {
151
+ M.multiply(m)
152
+ }
153
+
154
+ return M
155
+ }
156
+
157
+ /**
158
+ * Add all similar monoms. If they aren't similar, they are simply skipped.
159
+ * @param M (Monom[]) The monoms to add.
160
+ */
161
+ add = (...M: InputAlgebra<Fraction>[]): this => {
162
+ for (const m of M) {
163
+ // If the value given is not a monom, create it.
164
+ const mAsMonom = (!(m instanceof Monom)) ? new Monom(m) : m
165
+
166
+ if (this.isSameAs(mAsMonom)) {
167
+ if (this.isZero()) {
168
+ this.#cloneLiteral(mAsMonom)
169
+ }
170
+
171
+ this.#coefficient.add(mAsMonom.coefficient)
172
+ } else {
173
+ throw new MathError('Add monom: ' + this.display + ' is not similar with ' + mAsMonom.display)
174
+ }
175
+ }
176
+ return this
177
+ }
178
+
179
+ /**
180
+ * Get the coefficient \\(k\\) of the Monom \\(k\\cdot x^{n}\\)
181
+ * @returns {Fraction}
182
+ */
183
+ get coefficient(): Fraction {
184
+ return this.#coefficient
185
+ }
186
+
187
+ /**
188
+ * Set the coefficient \\(k\\) value of the monom
189
+ * @param {Fraction | number | string} F
190
+ */
191
+ set coefficient(F: InputValue<Fraction>) {
192
+ this.#coefficient = new Fraction(F)
193
+ }
194
+
195
+ containsRationalPower = (): boolean => {
196
+ return Object.values(this.#literal).some((value) => value.hasFractionalPart())
197
+ }
198
+
199
+ /**
200
+ * Get the degree of a monom. If no setLetter is given, the result will be the global degree.
201
+ * @param letter (string) Letter to get to degree (power)
202
+ */
203
+ degree = (letter?: string): Fraction => {
204
+ if (this.variables.length === 0) {
205
+ return new Fraction().zero()
206
+ }
207
+ if (letter === undefined) {
208
+ // Not setLetter given -> we get the global monom degree (sum of all the letters).
209
+ return Object.values(this.#literal).reduce((t, n) => t.clone().add(n))
210
+ } else {
211
+ // A setLetter is given -> get the corresponding power.
212
+ return !this.hasVariable(letter) ? new Fraction().zero() : this.#literal[letter].clone()
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Derivative the monom
218
+ * @param letter
219
+ */
220
+ derivative = (letter = 'x'): Monom => {
221
+ // No setLetter given - assume it's the setLetter 'x'
222
+ if (this.hasVariable(letter)) {
223
+ const d = this.#literal[letter].clone(),
224
+ dM = this.clone()
225
+
226
+ // Subtract one to the degree.
227
+ dM.#literal[letter].subtract(1)
228
+
229
+ // Multiply the coefficient by the previous degree
230
+ dM.#coefficient.multiply(new Fraction(d.clone()))
231
+ return dM
232
+ } else {
233
+ return new Monom().zero()
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Divide the current monoms by multiple monoms
239
+ * @param M (Monom[])
240
+ */
241
+ divide = (...M: InputAlgebra<Fraction>[]): this => {
242
+ // Depending on the given value, choose the current item
243
+ for (const m of M) {
244
+ // If the value given is not a monom, create it.
245
+ const mAsMonom = (!(m instanceof Monom)) ? new Monom(m) : m
246
+
247
+ // Divide the coefficient
248
+ this.#coefficient.divide(mAsMonom.coefficient)
249
+
250
+ // Subtract the power values
251
+ for (const letter in mAsMonom.literal) {
252
+
253
+ this.#literal[letter] = this.hasVariable(letter) ?
254
+ this.#literal[letter].subtract(mAsMonom.literal[letter]) :
255
+ mAsMonom.literal[letter].clone().opposite()
256
+
257
+
258
+ // If the power of a particular setLetter is zero, delete it from the literal part..
259
+ if (this.#literal[letter].isZero()) {
260
+ this.removeVariable(letter)
261
+ }
262
+ }
263
+ }
264
+ return this
265
+ }
266
+
267
+ get dividers(): Monom[] {
268
+ // Decompose only if the coefficient is a natural number
269
+ if (!this.coefficient.isRelative()) {
270
+ return [this.clone()]
271
+ }
272
+
273
+ // Decompose only if the power values are natural numbers.
274
+ if (this.containsRationalPower()) {
275
+ return [this.clone()]
276
+ }
277
+
278
+ // Security : do not do this if greater than 10000
279
+ if (this.coefficient.numerator > 1000000) {
280
+ return [this.clone()]
281
+ }
282
+
283
+ const dividers = Numeric.dividers(Math.abs(this.coefficient.numerator))
284
+
285
+ // Decompose the literals parts.
286
+ let literals: literalType<Fraction>[] = []
287
+ for (const L in this.literal) {
288
+ // L is the letter.
289
+ literals = this.#getLiteralDividers(literals, L)
290
+ }
291
+
292
+ const monomDividers: Monom[] = []
293
+ if (literals.length > 0 && dividers.length > 0) {
294
+ for (const N of dividers) {
295
+ for (const L of literals) {
296
+ const M = new Monom()
297
+ M.coefficient = new Fraction(N)
298
+ M.literal = L
299
+ monomDividers.push(M)
300
+ }
301
+ }
302
+ } else if (dividers.length === 0) {
303
+ for (const L of literals) {
304
+ const M = new Monom()
305
+ M.coefficient = new Fraction().one()
306
+ M.literal = L
307
+ monomDividers.push(M)
308
+ }
309
+ } else {
310
+ for (const N of dividers) {
311
+ const M = new Monom()
312
+ M.coefficient = new Fraction(N)
313
+ monomDividers.push(M)
314
+ }
315
+ }
316
+
317
+ return monomDividers.length === 0 ? [new Monom().one()] : monomDividers
318
+ }
319
+
320
+ /**
321
+ * Evaluate a monom. Each setLetter must be assigned to a Fraction.
322
+ * @param values Dictionary of <setLetter: Fraction>
323
+ * @param asNumeric
324
+ */
325
+ evaluate = (values: literalType<number | Fraction> | InputValue<Fraction>, asNumeric?: boolean): Fraction | number => {
326
+ // If as numeric return the numeric value
327
+ if (asNumeric === true) {
328
+ // Convert all values to numeric
329
+ // If the value is a Fraction, convert it to a number
330
+ if (values instanceof Fraction) {
331
+ return this.#evaluateAsNumeric(values.value)
332
+ }
333
+
334
+ // If the value is a number, return the numeric value
335
+ if (typeof values === 'number') {
336
+ return this.#evaluateAsNumeric(values)
337
+ }
338
+
339
+ // If the value is an object, return the numeric value
340
+ if (typeof values === 'object') {
341
+ // Convert {[key:string]:Fraction} to {[key:string]:number}
342
+ const tmpValues: literalType<number> = {}
343
+ for (const L in values) {
344
+ tmpValues[L] = new Fraction(values[L]).value
345
+ }
346
+
347
+ return this.#evaluateAsNumeric(tmpValues)
348
+ }
349
+ }
350
+
351
+ // The answer must be a Fraction
352
+ const r = this.coefficient.clone()
353
+
354
+ if (typeof values === 'number' || values instanceof Fraction) {
355
+ const tmpValues: literalType<Fraction> = {}
356
+ tmpValues[this.variables[0]] = new Fraction(values)
357
+ return this.evaluate(tmpValues)
358
+ }
359
+
360
+ if (typeof values === 'object') {
361
+ if (this.variables.length === 0) {
362
+ return this.coefficient
363
+ }
364
+
365
+ for (const L in this.#literal) {
366
+ const value = new Fraction(values[L])
367
+
368
+ r.multiply(value.pow(this.#literal[L]))
369
+ }
370
+ }
371
+
372
+ return r
373
+ }
374
+
375
+ // -------------------------------------
376
+ /**
377
+ * Determine if a monom contains a setLetter in it's literal part
378
+ * @param letter
379
+ */
380
+ hasVariable = (letter?: string): boolean => {
381
+ // The letter was not found
382
+ return Object.hasOwn(this.#literal, letter ?? 'x')
383
+ }
384
+
385
+ integrate(a: InputValue<Fraction>, b: InputValue<Fraction>, letter?: string): Fraction {
386
+ const primitive = this.primitive(letter)
387
+
388
+ return (primitive.evaluate(b, false) as Fraction)
389
+ .subtract(primitive.evaluate(a, false) as Fraction)
390
+ }
391
+
392
+ inverse = (): this => {
393
+ this.#coefficient.inverse()
394
+ for (const letter in this.#literal) {
395
+ this.#literal[letter].opposite()
396
+ }
397
+ return this
398
+ }
399
+
400
+ isDivisible = (div: Monom): boolean => {
401
+ // For all variables (letters), the current monom must have a degree higher than the divider
402
+ if (div.degree().isStrictlyPositive()) {
403
+ for (const letter of div.variables) {
404
+ if (!this.degree(letter).isGeq(div.degree(letter))) {
405
+ return false
406
+ }
407
+ }
408
+ }
409
+
410
+ // If the coefficient has a fractional part, we suppose we don't need to check the division by the coefficient.
411
+ if (this.coefficient.hasFractionalPart() || div.coefficient.hasFractionalPart()) {
412
+ return true
413
+ }
414
+
415
+ return this.coefficient.clone().divide(div.coefficient).isRelative()
416
+ }
417
+
418
+ /**
419
+ * Determine if two monoms are equals
420
+ * @param M
421
+ */
422
+ isEqual = (M: Monom): boolean => {
423
+ return this.isSameAs(M) && this.#coefficient.isEqual(M.coefficient)
424
+ }
425
+
426
+ isLiteralSquare = (): boolean => {
427
+ for (const letter in this.literal) {
428
+ // A literal square must have a natural power
429
+ if (this.literal[letter].hasFractionalPart()) {
430
+ return false
431
+ }
432
+
433
+ // The natural power must be even - if it's odd, the monom is not a square.
434
+ if (this.literal[letter].isOdd()) {
435
+ return false
436
+ }
437
+ }
438
+
439
+ return true
440
+ }
441
+
442
+ /**
443
+ * Determine if the monom is one
444
+ */
445
+ isOne = (): boolean => {
446
+ return this.#coefficient.value === 1 && this.variables.length === 0
447
+ }
448
+
449
+ /**
450
+ * Determine if two monoms are similar
451
+ * @param M
452
+ */
453
+ isSameAs = (M: Monom): boolean => {
454
+ // Get the list of all variables from both monoms.
455
+ const M1: string[] = this.variables
456
+ const M2: string[] = M.variables
457
+
458
+ // Get the list of all variables from both monoms.
459
+ const K: string[] = M1.concat(M2.filter((item) => !M1.includes(item)))
460
+
461
+ // If one of the monom is zero, it is the same than the other.
462
+ if (this.isZero() || M.isZero()) {
463
+ return true
464
+ }
465
+
466
+ // Both monoms has no literal part.
467
+ if (M1.length === 0 && M2.length === 0) {
468
+ return true
469
+ }
470
+
471
+ // Both monoms must have the same variables
472
+ if (M1.length !== M2.length) {
473
+ return false
474
+ }
475
+
476
+ // To _compare, both must be different from zero.
477
+ if (!this.isZero() && !M.isZero()) {
478
+ for (const key of K) {
479
+ // The variable is not available in one of the monom
480
+ if (!this.hasVariable(key) || !M.hasVariable(key)) {
481
+ return false
482
+ }
483
+
484
+ // The variable does not have the same power in each monoms.
485
+ if (!this.#literal[key].isEqual(M.literal[key])) {
486
+ return false
487
+ }
488
+ }
489
+ }
490
+
491
+ // All are positive check - the monoms are the sames.
492
+ return true
493
+ }
494
+
495
+ isSquare = (): boolean => {
496
+ if (!this.coefficient.isSquare()) {
497
+ return false
498
+ }
499
+ return this.isLiteralSquare()
500
+ }
501
+
502
+ /**
503
+ * Determine if the monom is null
504
+ */
505
+ isZero = (): boolean => {
506
+ return this.#coefficient.value === 0
507
+ }
508
+
509
+ /**
510
+ * Get the literal part of \\(x^{n_1}y^{n_2}\\) as dictionary \\[\\begin{array}{ll}x&=n_1\\\\y&=n_2\\end{array}\\]
511
+ * @returns {literalType}
512
+ */
513
+ get literal(): literalType<Fraction> {
514
+ return this.#literal
515
+ }
516
+
517
+ /**
518
+ * Set the literal part of the monom. Must be a dictionary {x: Fraction, y: Fraction, ...}
519
+ * @param {literalType<Fraction>} L
520
+ */
521
+ set literal(L: literalType<Fraction>) {
522
+ this.#literal = L
523
+ }
524
+
525
+ /**
526
+ * Set the literal part of the monom from a string
527
+ * @param inputStr String like x^2y^3
528
+ */
529
+ set literalStr(inputStr: string) {
530
+ // Match all x^n
531
+ for (const v of [...inputStr.matchAll(/([a-z])\^([+-]?[0-9]+)/g)]) {
532
+ // Create the default letter entry if necessary.
533
+ if (!(v[1] in this.#literal)) {
534
+ this.#literal[v[1]] = new Fraction().zero()
535
+ }
536
+
537
+ // Add the new value.
538
+ this.#literal[v[1]].add(+v[2])
539
+ }
540
+
541
+ // Match all x
542
+ for (const v of [...inputStr.matchAll(/([a-z](?!\^))/g)]) {
543
+ // Match all single letters
544
+ if (!(v[1] in this.#literal)) {
545
+ this.#literal[v[1]] = new Fraction().zero()
546
+ }
547
+
548
+ // Add one to the value.
549
+ this.#literal[v[1]].add(1)
550
+ }
551
+ }
552
+
553
+ /**
554
+ * Multiple multiple monoms to the current monom
555
+ * @param M (Monom[]) The monoms to multiply to.
556
+ */
557
+ multiply = (...M: InputAlgebra<Fraction>[]): this => {
558
+ for (const m of M) {
559
+ // If the value given is not a monom, create it.
560
+ const mAsMonom = (!(m instanceof Monom)) ? new Monom(m) : m
561
+
562
+ // Multiply the coefficient.
563
+ this.#coefficient.multiply(mAsMonom.coefficient)
564
+
565
+ // Multiply the literal parts.
566
+ for (const letter in mAsMonom.literal) {
567
+ if (!this.hasVariable(letter)) {
568
+ this.#literal[letter] = mAsMonom.literal[letter].clone()
569
+ } else {
570
+ this.#literal[letter].add(mAsMonom.literal[letter])
571
+ }
572
+ }
573
+ }
574
+ return this
575
+ }
576
+
577
+ /**
578
+ * Create a one value monom
579
+ */
580
+ one = (): this => {
581
+ this.#coefficient = new Fraction().one()
582
+ this.#literal = {}
583
+ return this
584
+ }
585
+
586
+ /**
587
+ * Get the opposite
588
+ * Returns a monom.
589
+ */
590
+ opposite = (): this => {
591
+ this.#coefficient.opposite()
592
+ return this
593
+ }
594
+
595
+ get plotFunction(): string {
596
+ return this.#format('plot')
597
+ }
598
+
599
+ /**
600
+ * Get the pow of a monom.
601
+ * @param nb (number) : Mathematical pow
602
+ */
603
+ pow = (nb: number | Fraction): this => {
604
+ this.#coefficient.pow(nb)
605
+ for (const letter in this.#literal) {
606
+ this.#literal[letter].multiply(nb)
607
+ }
608
+ return this
609
+ }
610
+
611
+ primitive = (letter = 'x'): Monom => {
612
+ // Zero monom
613
+ const M = this.clone()
614
+ let degree: Fraction
615
+
616
+ if (M.hasVariable(letter)) {
617
+ degree = M.degree(letter).clone().add(1)
618
+ M.coefficient = M.coefficient.clone().divide(degree)
619
+ M.setLetter(letter, degree)
620
+ } else {
621
+ // There is no letter.
622
+
623
+ // The coefficient might be zero (=> x) or a number a (=> ax)
624
+ if (M.coefficient.isZero()) {
625
+ M.coefficient = new Fraction().one()
626
+ }
627
+ M.setLetter(letter, 1)
628
+ }
629
+
630
+ return M
631
+ }
632
+
633
+ reduce = (): this => {
634
+ // Reduce the coefficient
635
+ this.coefficient.reduce()
636
+
637
+ // Reduce the literal parts (removing null powers)
638
+ for (const letter in this.#literal) {
639
+ if (this.#literal[letter].isZero()) {
640
+ this.removeVariable(letter)
641
+ }
642
+ }
643
+ return this
644
+ }
645
+
646
+ removeVariable(letter: string) {
647
+ /* eslint-disable */
648
+ delete this.#literal[letter]
649
+ /* eslint-enable */
650
+ }
651
+
652
+ /**
653
+ * Get the nth-root of the monom
654
+ * @todo Not yet implemented
655
+ */
656
+ root = (): this => {
657
+ // TODO: implement nth-root for monoms
658
+ throw new NotImplementedError('Method not implemented.')
659
+ }
660
+
661
+ /**
662
+ * Set the power of a particular setLetter
663
+ * @param letter (string) Letter to change
664
+ * @param pow (number) Power of the setLetter (must be positive integer.
665
+ */
666
+ setLetter = (letter: string, pow: InputValue<Fraction>): this => {
667
+ if (!(pow instanceof Fraction)) {
668
+ return this.setLetter(letter, new Fraction(pow))
669
+ }
670
+
671
+ // Set the power of the letter to zero => remove it
672
+ if (this.hasVariable(letter) && pow.isZero()) {
673
+ this.removeVariable(letter)
674
+ return this
675
+ }
676
+
677
+
678
+ this.#literal[letter] = pow.clone()
679
+
680
+ return this
681
+ }
682
+
683
+ /**
684
+ * Return the square root of a monom
685
+ */
686
+ sqrt = (): this => {
687
+ if (this.isSquare()) {
688
+ this.#coefficient.sqrt()
689
+ for (const letter in this.#literal) {
690
+ this.#literal[letter].divide(2)
691
+ }
692
+ }
693
+
694
+ // TODO: throw an error if the monom is not a square ? Or allow the result to be a monom with rational powers ?
695
+ return this
696
+ }
697
+
698
+ /**
699
+ * Subtract multiple monoms
700
+ * @param M (Monom[]) The monoms to subtract
701
+ */
702
+ subtract = (...M: InputAlgebra<Fraction>[]): this => {
703
+ for (const m of M) {
704
+ // If the value given is not a monom, create it.
705
+ const mAsMonom = (!(m instanceof Monom)) ? new Monom(m) : m
706
+
707
+ if (this.isSameAs(mAsMonom)) {
708
+ if (this.isZero()) {
709
+ this.#cloneLiteral(mAsMonom)
710
+ }
711
+
712
+ this.#coefficient.add(mAsMonom.clone().coefficient.opposite())
713
+ } else {
714
+ throw new MathError('Subtract: Is not similar: ' + mAsMonom.display)
715
+ }
716
+ }
717
+ return this
718
+ }
719
+
720
+ // Getter helpers.
721
+ /**
722
+ * Get the variables letters
723
+ */
724
+ get variables(): string[] {
725
+ // const M = this.clone().clean()
726
+
727
+ const L: string[] = []
728
+ Object.entries(this.literal).forEach(
729
+ ([key, value]) => {
730
+ if (!value.isZero()) {
731
+ L.push(key)
732
+ }
733
+ })
734
+ L.sort()
735
+ return L
736
+ // return Object.keys(M.literal)
737
+ }
738
+
739
+ /**
740
+ * Create a zero value monom
741
+ */
742
+ zero = (): this => {
743
+ this.#coefficient = new Fraction().zero()
744
+ this.#literal = {}
745
+ return this
746
+ }
747
+
748
+ #cloneLiteral(inputStr: Monom) {
749
+ for (const k in inputStr.literal) {
750
+ this.#literal[k] = inputStr.literal[k].clone()
751
+ }
752
+ }
753
+
754
+ #evaluateAsNumeric = (values: literalType<number | Fraction> | InputValue<Fraction>): number => {
755
+ let r = this.coefficient.value
756
+
757
+ if (typeof values === "number") {
758
+ const tmpValues: literalType<number> = {}
759
+ const key = this.variables[0]
760
+ tmpValues[key] = values
761
+
762
+ return this.#evaluateAsNumeric(tmpValues)
763
+ }
764
+
765
+ if (values instanceof Fraction) {
766
+ const tmpValues: literalType<number> = {}
767
+ tmpValues[this.variables[0]] = new Fraction(values).value
768
+ return this.#evaluateAsNumeric(tmpValues)
769
+ }
770
+
771
+ if (typeof values === 'object') {
772
+ if (this.variables.length === 0) {
773
+ return this.coefficient.value
774
+ }
775
+
776
+ for (const L in this.#literal) {
777
+ const v = values[L]
778
+
779
+ if (v instanceof Fraction) {
780
+ r *= v.value ** (this.#literal[L].value)
781
+ } else {
782
+ r *= v ** (this.#literal[L].value)
783
+ }
784
+ }
785
+ }
786
+
787
+ return r
788
+ }
789
+
790
+ #format = (type: 'tex' | 'display' | 'plot'): string => {
791
+ let L = ''
792
+ const letters = Object.keys(this.#literal).sort()
793
+
794
+ for (const letter of letters) {
795
+ if (this.#literal[letter].isNotZero()) {
796
+ if (type === 'plot' && L !== '') { L += '*' }
797
+ L += letter
798
+ if (this.#literal[letter].isNotEqual(1)) {
799
+ L += type === 'tex'
800
+ ? `^{ ${this.#literal[letter].asTfrac.tex} }`
801
+ : `^(${this.#literal[letter].display})`
802
+ }
803
+ }
804
+ }
805
+
806
+ const coeffStr = type === 'tex' ? this.#coefficient.asFrac.tex : this.#coefficient.display
807
+
808
+ if (L === '') {
809
+ return this.#coefficient.value !== 0 ? coeffStr : '0'
810
+ }
811
+
812
+ if (this.#coefficient.value === 1) { return L }
813
+ if (this.#coefficient.value === -1) { return `-${L}` }
814
+ if (this.#coefficient.value === 0) { return '0' }
815
+ return type === 'plot' ? `${coeffStr}*${L}` : `${coeffStr}${L}`
816
+ }
817
+
818
+ #getLiteralDividers(arr: literalType<Fraction>[], letter: string): literalType<Fraction>[] {
819
+ const tmpList: Record<string, Fraction>[] = []
820
+
821
+ // Be default, this.literal[letter] should be a rational number.
822
+ for (let d = 0; d <= this.literal[letter].value; d++) {
823
+ if (arr.length === 0) {
824
+ const litt: literalType<Fraction> = {}
825
+ litt[letter] = new Fraction(d)
826
+ tmpList.push(litt)
827
+ } else {
828
+ for (const item of arr) {
829
+ const litt: literalType<Fraction> = {}
830
+ for (const currentLetter in item) {
831
+ litt[currentLetter] = item[currentLetter]
832
+ }
833
+ litt[letter] = new Fraction(d)
834
+ tmpList.push(litt)
835
+ }
836
+ }
837
+ }
838
+ return tmpList
839
+ }
840
+
841
+ #shutingYardAddToken = (stack: Monom[], element: Token): void => {
842
+ let q1: Monom, q2: Monom, m: Monom, letter: string, pow: Fraction
843
+
844
+ if (element.tokenType === ShutingyardType.COEFFICIENT) {
845
+ stack.push(new Monom(new Fraction(element.token)))
846
+
847
+ } else if (element.tokenType === ShutingyardType.VARIABLE) {
848
+ const M = new Monom().one()
849
+ M.setLetter(element.token, 1)
850
+ stack.push(M.clone())
851
+
852
+ } else if (element.tokenType === ShutingyardType.OPERATION) {
853
+ switch (element.token) {
854
+ case '-':
855
+ // this should only happen for negative powers or for negative coefficient.
856
+ q2 = (stack.pop()) ?? new Monom().zero()
857
+ q1 = (stack.pop()) ?? new Monom().zero()
858
+
859
+ stack.push(q1.subtract(q2))
860
+
861
+ break
862
+ case '*':
863
+ // Get the last element in the stack
864
+ q2 = (stack.pop()) ?? new Monom().one()
865
+ q1 = (stack.pop()) ?? new Monom().one()
866
+
867
+ stack.push(q1.multiply(q2))
868
+ break
869
+ case '/':
870
+ // Get the last element in the stack
871
+ q2 = (stack.pop()) ?? new Monom().one()
872
+ q1 = (stack.pop()) ?? new Monom().one()
873
+
874
+ stack.push(q1.divide(q2))
875
+ break
876
+ case '^': {
877
+ // get the two last elements in the stack
878
+ const poppedCoefficient = stack.pop()?.coefficient
879
+ pow = poppedCoefficient ?? new Fraction().one()
880
+ m = stack.pop() ?? new Monom().one()
881
+
882
+ letter = m.variables[0]
883
+
884
+ if (letter) {
885
+ m.setLetter(letter, pow)
886
+ }
887
+
888
+ stack.push(m)
889
+ // this.multiply(m.clone())
890
+ break
891
+ }
892
+ }
893
+ }
894
+ }
895
+
896
+ #shutingYardToReducedMonom = (inputStr: string): this => {
897
+ // Get the RPN array of the current expression
898
+ let SY: ShutingYard
899
+ try {
900
+ SY = new ShutingYard().parse(inputStr)
901
+ } catch (e) {
902
+ throw new ParseError(`Cannot parse "${inputStr}" as a Monom`, {cause: e})
903
+ }
904
+ const rpn: { token: string, tokenType: ShutingyardType }[] = SY.rpn
905
+
906
+ const stack: Monom[] = []
907
+
908
+ if (rpn.length === 0) {
909
+ this.zero()
910
+ return this
911
+ } else if (rpn.length === 1) {
912
+ const element = rpn[0]
913
+
914
+ this.one()
915
+ if (element.tokenType === ShutingyardType.COEFFICIENT) {
916
+ this.coefficient = new Fraction(element.token)
917
+ } else if (element.tokenType === ShutingyardType.VARIABLE) {
918
+ this.setLetter(element.token, 1)
919
+ }
920
+ return this
921
+ } else {
922
+ // Reset the monom
923
+ for (const element of rpn) {
924
+ this.#shutingYardAddToken(stack, element)
925
+ }
926
+ }
927
+
928
+ this.one()
929
+ this.multiply(stack[0])
930
+ return this
931
+ }
932
+ }