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