pimath 0.1.39 → 0.2.0
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 +3127 -2865
- package/dist/pimath.js.map +1 -1
- package/package.json +16 -12
- package/src/algebra/equation.ts +558 -0
- package/src/algebra/equationSolver.ts +488 -0
- package/src/algebra/factor.ts +338 -0
- package/src/algebra/index.ts +11 -0
- package/src/algebra/linearSystem.ts +439 -0
- package/src/algebra/logicalset.ts +255 -0
- package/src/algebra/matrix.ts +474 -0
- package/src/algebra/monom.ts +977 -0
- package/src/algebra/operations.ts +23 -0
- package/src/algebra/polyFactor.ts +668 -0
- package/src/algebra/polynom.ts +1247 -0
- package/src/analyze/index.ts +4 -0
- package/src/analyze/solution.ts +178 -0
- package/src/analyze/tableOfSigns.ts +30 -0
- package/src/coefficients/fraction.ts +718 -0
- package/src/coefficients/index.ts +4 -0
- package/src/coefficients/root.ts +346 -0
- package/src/geometry/TupleN.ts +128 -0
- package/src/geometry/circle.ts +456 -0
- package/src/geometry/geomMath.ts +71 -0
- package/src/geometry/index.ts +11 -0
- package/src/geometry/line.ts +653 -0
- package/src/geometry/line3.ts +211 -0
- package/src/geometry/plane3.ts +179 -0
- package/src/geometry/point.ts +104 -0
- package/src/geometry/sphere3.ts +214 -0
- package/src/geometry/triangle.ts +482 -0
- package/src/geometry/vector.ts +225 -0
- package/src/helpers.ts +35 -0
- package/src/index.ts +61 -0
- package/src/numeric.ts +196 -0
- package/src/pimath.interface.ts +162 -0
- package/src/randomization/algebra/rndEquation.ts +41 -0
- package/src/randomization/algebra/rndMonom.ts +39 -0
- package/src/randomization/algebra/rndPolynom.ts +100 -0
- package/src/randomization/coefficient/rndFraction.ts +38 -0
- package/src/randomization/geometry/rndCircle.ts +27 -0
- package/src/randomization/geometry/rndLine.ts +35 -0
- package/src/randomization/geometry/rndLine3.ts +27 -0
- package/src/randomization/geometry/rndVector.ts +63 -0
- package/src/randomization/random.ts +89 -0
- package/src/randomization/rndHelpers.ts +102 -0
- package/src/randomization/rndTypes.ts +67 -0
- package/types/algebra/equation.d.ts +18 -17
- package/types/algebra/equation.d.ts.map +1 -1
- package/types/algebra/equationSolver.d.ts +7 -3
- package/types/algebra/equationSolver.d.ts.map +1 -1
- package/types/algebra/factor.d.ts +1 -1
- package/types/algebra/factor.d.ts.map +1 -1
- package/types/algebra/linearSystem.d.ts +23 -6
- package/types/algebra/linearSystem.d.ts.map +1 -1
- package/types/algebra/logicalset.d.ts +1 -1
- package/types/algebra/logicalset.d.ts.map +1 -1
- package/types/algebra/monom.d.ts +1 -6
- package/types/algebra/monom.d.ts.map +1 -1
- package/types/algebra/operations.d.ts.map +1 -1
- package/types/algebra/polyFactor.d.ts +9 -4
- package/types/algebra/polyFactor.d.ts.map +1 -1
- package/types/algebra/polynom.d.ts +10 -7
- package/types/algebra/polynom.d.ts.map +1 -1
- package/types/analyze/index.d.ts +2 -0
- package/types/analyze/index.d.ts.map +1 -0
- package/types/analyze/solution.d.ts +27 -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/fraction.d.ts +14 -12
- package/types/coefficients/fraction.d.ts.map +1 -1
- package/types/coefficients/index.d.ts +1 -1
- package/types/coefficients/index.d.ts.map +1 -1
- package/types/coefficients/root.d.ts +41 -0
- package/types/coefficients/root.d.ts.map +1 -0
- package/types/geometry/TupleAbstract.d.ts +22 -0
- package/types/geometry/TupleAbstract.d.ts.map +1 -0
- package/types/geometry/TupleN.d.ts +24 -0
- package/types/geometry/TupleN.d.ts.map +1 -0
- package/types/geometry/circle.d.ts +26 -17
- package/types/geometry/circle.d.ts.map +1 -1
- package/types/geometry/geomMath.d.ts +2 -1
- package/types/geometry/geomMath.d.ts.map +1 -1
- package/types/geometry/index.d.ts.map +1 -1
- package/types/geometry/line.d.ts +21 -30
- package/types/geometry/line.d.ts.map +1 -1
- package/types/geometry/line3.d.ts +19 -19
- package/types/geometry/line3.d.ts.map +1 -1
- package/types/geometry/matrix.d.ts +11 -11
- package/types/geometry/plane3.d.ts +9 -9
- package/types/geometry/plane3.d.ts.map +1 -1
- package/types/geometry/point.d.ts +12 -7
- package/types/geometry/point.d.ts.map +1 -1
- package/types/geometry/triangle.d.ts +68 -23
- package/types/geometry/triangle.d.ts.map +1 -1
- package/types/geometry/vector.d.ts +24 -44
- package/types/geometry/vector.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 +6 -4
- 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 +38 -44
- package/types/pimath.interface.d.ts.map +1 -1
- package/types/randomization/algebra/rndPolynom.d.ts.map +1 -1
- package/types/randomization/coefficient/rndFraction.d.ts +1 -1
- package/types/randomization/coefficient/rndFraction.d.ts.map +1 -1
- package/types/randomization/geometry/rndLine.d.ts.map +1 -1
- package/types/randomization/random.d.ts +3 -2
- package/types/randomization/random.d.ts.map +1 -1
- package/types/randomization/rndTypes.d.ts +15 -10
- package/types/randomization/rndTypes.d.ts.map +1 -1
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import type {IAlgebra, IEquation, InputValue, IPiMathObject, literalType} from "../pimath.interface"
|
|
2
|
+
import {Fraction} from "../coefficients"
|
|
3
|
+
import {Equation} from "./equation"
|
|
4
|
+
import {Monom} from "./monom"
|
|
5
|
+
import {Polynom} from "./polynom"
|
|
6
|
+
import {Numeric} from "../numeric"
|
|
7
|
+
import type {Solution} from "../analyze/solution"
|
|
8
|
+
|
|
9
|
+
export class LinearSystem implements IPiMathObject<LinearSystem>,
|
|
10
|
+
IEquation<LinearSystem>,
|
|
11
|
+
IAlgebra<LinearSystem> {
|
|
12
|
+
|
|
13
|
+
#equations: Equation[]
|
|
14
|
+
// Solve steps for TeX output.
|
|
15
|
+
#steps: string[] = []
|
|
16
|
+
// Determine the letters in the linear asSystem, usually ['x', 'y']
|
|
17
|
+
#variables: string[]
|
|
18
|
+
|
|
19
|
+
constructor(...values: (string | Equation)[]) {
|
|
20
|
+
this.#equations = []
|
|
21
|
+
this.#variables = []
|
|
22
|
+
|
|
23
|
+
if (values.length > 0) {
|
|
24
|
+
this.parse(...values)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return this
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public parse = (...equations: (string | Equation)[]): this => {
|
|
31
|
+
// make the original equations
|
|
32
|
+
this.#equations = equations.map(value => new Equation(value))
|
|
33
|
+
|
|
34
|
+
// get the letters.
|
|
35
|
+
this.#findLetters()
|
|
36
|
+
return this
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public clone = (): LinearSystem => {
|
|
40
|
+
return new LinearSystem()
|
|
41
|
+
.parse(...this.#equations.map(equ => equ.clone()))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public get tex(): string {
|
|
45
|
+
// Build the array of values.
|
|
46
|
+
// Reorder
|
|
47
|
+
// This clone the asSystem :!!!
|
|
48
|
+
//TODO: Avoid cloning this linear asSystem
|
|
49
|
+
const LS = this.clone().reorder()
|
|
50
|
+
|
|
51
|
+
return this.buildTex(LS.equations)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get display() {
|
|
55
|
+
// TODO : LinearSystem - display: implement the display of the linear asSystem
|
|
56
|
+
return this.tex + 'as display'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public static fromMatrix(
|
|
60
|
+
matrix: InputValue<Fraction>[][],
|
|
61
|
+
letters = 'xyz'): LinearSystem {
|
|
62
|
+
// Check that each row has the same number of columns
|
|
63
|
+
const cols = matrix[0].length
|
|
64
|
+
if (matrix.some(row => row.length !== cols)) {
|
|
65
|
+
throw new Error("All rows must have the same number of columns")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Determine the default letters. The number of letters are cols-1
|
|
69
|
+
const vars = letters.split('')
|
|
70
|
+
.splice(0, cols - 1)
|
|
71
|
+
|
|
72
|
+
// Create a new LinearSystem
|
|
73
|
+
return new LinearSystem(
|
|
74
|
+
...matrix.map(row => {
|
|
75
|
+
const P = new Polynom(vars.join(''), ...row)
|
|
76
|
+
return new Equation(P, 0)
|
|
77
|
+
})
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public add(value: InputValue<LinearSystem | Equation | Polynom>, index?: number): this {
|
|
83
|
+
if (value instanceof LinearSystem) {
|
|
84
|
+
const length = value.equations.length
|
|
85
|
+
if (length !== this.#equations.length) {
|
|
86
|
+
throw new Error("The number of equations must be the same")
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < length; i++) {
|
|
90
|
+
this.#equations[i].add(value.equations[i])
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
if (index === undefined || index < 0 || index >= this.#equations.length) {
|
|
94
|
+
throw new Error("Index out of range")
|
|
95
|
+
}
|
|
96
|
+
const equ = new Equation(value)
|
|
97
|
+
this.#equations[index].add(equ)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return this
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public buildTex = (equations: Equation[], operators?: (string[])[]): string => {
|
|
104
|
+
let equStr: string[]
|
|
105
|
+
let m: Monom
|
|
106
|
+
let letters: string[] = []
|
|
107
|
+
const equArray: string[] = []
|
|
108
|
+
|
|
109
|
+
// Get the letters from the linear asSystem
|
|
110
|
+
for (const equ of equations) {
|
|
111
|
+
letters = letters.concat(equ.letters())
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
letters = [...new Set(letters)]
|
|
115
|
+
letters.sort()
|
|
116
|
+
|
|
117
|
+
for (let i = 0; i < equations.length; i++) {
|
|
118
|
+
const equ = equations[i]
|
|
119
|
+
|
|
120
|
+
equStr = []
|
|
121
|
+
for (const L of letters) {
|
|
122
|
+
m = equ.left.monomByLetter(L)
|
|
123
|
+
|
|
124
|
+
if (equStr.length === 0) {
|
|
125
|
+
equStr.push(m.isZero() ? '' : m.tex)
|
|
126
|
+
} else {
|
|
127
|
+
equStr.push(m.isZero() ? '' : ((m.coefficient.sign() === 1) ? '+' : '') + m.tex)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Add the equal sign
|
|
132
|
+
equStr.push('=')
|
|
133
|
+
|
|
134
|
+
// Add the right hand part of the equation (should be only a number, because it has been reordered)
|
|
135
|
+
equStr.push(equ.right.tex)
|
|
136
|
+
|
|
137
|
+
// Add the operations if existing
|
|
138
|
+
if (operators?.[i] !== undefined) {
|
|
139
|
+
// add extra space at the end of the equation
|
|
140
|
+
equStr[equStr.length - 1] = equStr[equStr.length - 1] + ' \\phantom{\\quad}'
|
|
141
|
+
for (const o of operators[i]) {
|
|
142
|
+
equStr.push(`\\ \\cdot\\ ${o.startsWith('-') ? "\\left(" + o + "\\right)" : o}`)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Add to the list.
|
|
147
|
+
equArray.push(equStr.join('&'))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let operatorsColumns = 0
|
|
151
|
+
if (operators !== undefined && operators.length > 0) {
|
|
152
|
+
operatorsColumns = operators[0].length
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return `\\left\\{\\begin{array}{${"r".repeat(letters.length)}cl ${"|l".repeat(operatorsColumns)}}${equArray.join('\\\\ ')}\\end{array}\\right.`
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
public degree(letter?: string): Fraction {
|
|
159
|
+
return Fraction.max(...this.#equations.map(equ => equ.degree(letter)))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ------------------------------------------
|
|
163
|
+
public get equations(): Equation[] {
|
|
164
|
+
return this.#equations
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public set equations(value) {
|
|
168
|
+
this.#equations = value
|
|
169
|
+
|
|
170
|
+
// update the variables.
|
|
171
|
+
this.#findLetters()
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public evaluate(values: InputValue<Fraction> | literalType<number | Fraction>, asNumeric?: boolean): number | Fraction {
|
|
175
|
+
throw new Error("Method not implemented.")
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public hasVariable(letter: string): boolean {
|
|
179
|
+
return this.#variables.includes(letter)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public isEqual(value: LinearSystem): boolean {
|
|
183
|
+
return this.equations.every((equ, index) => equ.isEqual(value.equations[index]))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public get isSolvable(): boolean {
|
|
187
|
+
const V = this.variables
|
|
188
|
+
|
|
189
|
+
// TODO: in some case, it is possible to resolve systems if there isn't the isSame number of vars and equations
|
|
190
|
+
if (V.length !== this.#equations.length) {
|
|
191
|
+
return false
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//TODO: Must check if two equations isn't a linear combination of the others ?
|
|
195
|
+
|
|
196
|
+
return true
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public get matrix(): [Fraction[][], Fraction[]] {
|
|
200
|
+
//TODO: use Matrix class
|
|
201
|
+
return this.#makeMatrix()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public mergeEquations(equation1: { id: number, factor: InputValue<Fraction> }, equation2: {
|
|
205
|
+
id: number,
|
|
206
|
+
factor: number
|
|
207
|
+
}): Equation {
|
|
208
|
+
// Set and clone the equations.
|
|
209
|
+
const eq1multiplied = this.equations[equation1.id].clone().multiply(equation1.factor)
|
|
210
|
+
const eq2multiplied = this.equations[equation2.id].clone().multiply(equation2.factor)
|
|
211
|
+
|
|
212
|
+
// Add both equations together.
|
|
213
|
+
return eq1multiplied.add(eq2multiplied)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public multiply(value: InputValue<Fraction> | InputValue<Fraction>[], index?: number): this {
|
|
217
|
+
// Multiply the asSystem by a number
|
|
218
|
+
// the value can be an array of numbers
|
|
219
|
+
// the value can be a number and the index of the equation to multiply
|
|
220
|
+
if (Array.isArray(value)) {
|
|
221
|
+
if (value.length !== this.#equations.length) {
|
|
222
|
+
throw new Error("The number of values must be the same as the number of equations")
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for (let i = 0; i < value.length; i++) {
|
|
226
|
+
this.#equations[i].multiply(value[i])
|
|
227
|
+
}
|
|
228
|
+
return this
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (index === undefined || index < 0 || index >= this.#equations.length) {
|
|
232
|
+
throw new Error("Index out of range")
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
this.#equations[index].multiply(value)
|
|
236
|
+
|
|
237
|
+
return this
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public reduce(): this {
|
|
241
|
+
// reduce all equations at once.
|
|
242
|
+
this.equations.forEach(equ=>equ.reduce())
|
|
243
|
+
return this
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ------------------------------------------
|
|
247
|
+
public reorder = (): this => {
|
|
248
|
+
for (const E of this.#equations) {
|
|
249
|
+
E.reorder()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return this
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
solve(): Solution[] {
|
|
256
|
+
// TODO : à retravailler, car ce n'est ni l'endroit, ni l'intérêt de l'avoir ici.
|
|
257
|
+
// 1. search in the equations if a variable has two same or opposite value = candidate for merging
|
|
258
|
+
// 2. if 1 is false, search for a variable that has coefficient one
|
|
259
|
+
// 3. if 2 is false, search for a variable that has a coefficient multiple of another.
|
|
260
|
+
// 4. if 3 is false, multiply both lines.
|
|
261
|
+
// => merge the equations and cycle.
|
|
262
|
+
const output: string[] = [this.tex]
|
|
263
|
+
|
|
264
|
+
const LS = this.clone()
|
|
265
|
+
|
|
266
|
+
while (LS.variables.length>1){
|
|
267
|
+
const letter = LS.variables[LS.variables.length-1]
|
|
268
|
+
const emptyLS = new LinearSystem()
|
|
269
|
+
const factors = LS.solve_compute_factors(letter).slice(0, LS.variables.length-1)
|
|
270
|
+
factors.forEach(factor=> {
|
|
271
|
+
emptyLS.equations.push(LS.mergeEquations(...factor))
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
LS.equations = emptyLS.equations
|
|
275
|
+
|
|
276
|
+
output.push(LS.tex)
|
|
277
|
+
|
|
278
|
+
// add the same but with a reduced value.
|
|
279
|
+
LS.reduce()
|
|
280
|
+
output.push(LS.tex)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return []
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
public solveMatrix = (): Fraction[] => {
|
|
287
|
+
const [matrix, vector] = this.matrix
|
|
288
|
+
// Solve the matrix
|
|
289
|
+
|
|
290
|
+
// Make the augmented matrix (matrix + vector)
|
|
291
|
+
const augmentedMatrix: Fraction[][] = matrix.map((row, index) => [...row, vector[index]])
|
|
292
|
+
|
|
293
|
+
// Reduce the matrix
|
|
294
|
+
for (let i = 0; i < matrix.length; i++) {
|
|
295
|
+
// Find the pivot (the first non-zero element in the row)
|
|
296
|
+
let pivot = augmentedMatrix[i][i].clone()
|
|
297
|
+
if (pivot.isZero()) {
|
|
298
|
+
// throw new Error('Divide by zero !')
|
|
299
|
+
// Search a line below that would add it.
|
|
300
|
+
const row_to_add = augmentedMatrix
|
|
301
|
+
.find((row, index) => {
|
|
302
|
+
return index > i && !row[i].isZero()
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
if (row_to_add) {
|
|
306
|
+
augmentedMatrix[i].forEach((value, index) => value.add(row_to_add[index]))
|
|
307
|
+
pivot = augmentedMatrix[i][i].clone()
|
|
308
|
+
} else {
|
|
309
|
+
throw new Error('Unsolvable...')
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Normalize the row: divide all elements by the pivot
|
|
316
|
+
// the pivot is now 1
|
|
317
|
+
augmentedMatrix[i] = augmentedMatrix[i].map(x => x.divide(pivot))
|
|
318
|
+
|
|
319
|
+
// reduce the other rows using the pivot.
|
|
320
|
+
for (let j = 0; j < matrix.length; j++) {
|
|
321
|
+
if (j === i) {
|
|
322
|
+
continue
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const factor = augmentedMatrix[j][i].clone().opposite()
|
|
326
|
+
for (let k = 0; k < augmentedMatrix[j].length; k++) {
|
|
327
|
+
augmentedMatrix[j][k].add(augmentedMatrix[i][k].clone().multiply(factor))
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Check if the asSystem is undetermined (no solution or infinite solutions)
|
|
331
|
+
// the j line must not be all zeros
|
|
332
|
+
// the last element must be zero => the asSystem is undetermined
|
|
333
|
+
// the last element must not be zero => the asSystem is impossible
|
|
334
|
+
if (augmentedMatrix[j].slice(0, augmentedMatrix[j].length - 1).every(x => x.isZero())) {
|
|
335
|
+
if (augmentedMatrix[j][augmentedMatrix[j].length - 1].isZero()) {
|
|
336
|
+
return [new Fraction().infinite()]
|
|
337
|
+
} else {
|
|
338
|
+
return []
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return augmentedMatrix.map(x => x[x.length - 1])
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
solve_compute_factors(letter: string):
|
|
348
|
+
[{ id: number, factor: number }, { id: number, factor: number }][] {
|
|
349
|
+
// when solving, every monoms with a variable is on the left !
|
|
350
|
+
// and every coefficient are relative numbers.
|
|
351
|
+
const result: [{ id: number, factor: number }, { id: number, factor: number }][] = []
|
|
352
|
+
const coefficients = this.equations.map(equ => equ.left.monomByLetter(letter).coefficient.value)
|
|
353
|
+
|
|
354
|
+
// search for a factor
|
|
355
|
+
coefficients.forEach((reference, index) => {
|
|
356
|
+
for (let i = index + 1; i < coefficients.length; i++) {
|
|
357
|
+
const lcm = Numeric.lcm(reference, coefficients[i])
|
|
358
|
+
|
|
359
|
+
const sign = reference < 0 ? -1 : 1
|
|
360
|
+
result.push([
|
|
361
|
+
{
|
|
362
|
+
id: index, factor: sign * lcm / reference
|
|
363
|
+
}, {
|
|
364
|
+
id: i, factor: -sign * lcm / coefficients[i]
|
|
365
|
+
}])
|
|
366
|
+
}
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
// Sort the value: prefer the smallest absolute values (1/-1, 2/-2, ...)
|
|
370
|
+
return result.sort((a, b) => {
|
|
371
|
+
return (Math.abs(a[0].factor) + Math.abs(a[1].factor)) - (Math.abs(b[0].factor) + Math.abs(b[1].factor))
|
|
372
|
+
})
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
public subtract(value: InputValue<LinearSystem | Equation | Polynom>, index?: number): this {
|
|
376
|
+
if (value instanceof LinearSystem) {
|
|
377
|
+
const length = value.equations.length
|
|
378
|
+
if (length !== this.#equations.length) {
|
|
379
|
+
throw new Error("The number of equations must be the same")
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
for (let i = 0; i < length; i++) {
|
|
383
|
+
this.#equations[i].subtract(value.equations[i])
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
386
|
+
if (index === undefined || index < 0 || index >= this.#equations.length) {
|
|
387
|
+
throw new Error("Index out of range")
|
|
388
|
+
}
|
|
389
|
+
const equ = new Equation(value)
|
|
390
|
+
this.#equations[index].subtract(equ)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return this
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
public get variables(): string[] {
|
|
397
|
+
return this.#variables
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
public set variables(value: string | string[]) {
|
|
401
|
+
const vars = (typeof value === "string") ? value.split('') : [...value]
|
|
402
|
+
vars.sort()
|
|
403
|
+
this.#variables = vars
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
#findLetters = (): this => {
|
|
407
|
+
this.#variables = this.#equations.reduce((acc: string[], equ) => {
|
|
408
|
+
return [...new Set([...acc, ...equ.variables])]
|
|
409
|
+
}, [])
|
|
410
|
+
|
|
411
|
+
this.#variables.sort()
|
|
412
|
+
return this
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
#makeMatrix = (): [Fraction[][], Fraction[]] => {
|
|
416
|
+
// Make the matrix
|
|
417
|
+
const matrix: Fraction[][] = []
|
|
418
|
+
const vector: Fraction[] = []
|
|
419
|
+
|
|
420
|
+
for (const E of this.#equations) {
|
|
421
|
+
const row: Fraction[] = []
|
|
422
|
+
|
|
423
|
+
const equ = E.clone().reorder()
|
|
424
|
+
for (const L of this.variables) {
|
|
425
|
+
const m = equ.left.monomByLetter(L)
|
|
426
|
+
row.push(m.coefficient)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Add the "no letter part"
|
|
430
|
+
vector.push(equ.right.monoms[0].coefficient)
|
|
431
|
+
|
|
432
|
+
// Add to the matrix
|
|
433
|
+
matrix.push(row)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return [matrix, vector]
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polynom module contains everything necessary to handle polynoms.
|
|
3
|
+
* @module Logicalset
|
|
4
|
+
*/
|
|
5
|
+
import {ShutingYard, ShutingyardMode} from "piexpression"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Polynom class can handle polynoms, reorder, resolve, ...
|
|
9
|
+
*/
|
|
10
|
+
export class LogicalSet {
|
|
11
|
+
#rpn: { token: string, tokenType: string }[]
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @param {string} value (optional) Default polynom to parse on class creation
|
|
16
|
+
*/
|
|
17
|
+
constructor(value?: string) {
|
|
18
|
+
this.#rpn = []
|
|
19
|
+
|
|
20
|
+
if (value !== undefined) {
|
|
21
|
+
this.parse(value)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return this
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
parse = (value: string): this => {
|
|
28
|
+
// Parse the updated value to the shutingyard algorithm
|
|
29
|
+
this.#rpn = new ShutingYard(ShutingyardMode.SET)
|
|
30
|
+
.parse(value)
|
|
31
|
+
.rpn
|
|
32
|
+
|
|
33
|
+
return this
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get tex(): string {
|
|
37
|
+
const varStack: { token: string, tokenType: string }[] = []
|
|
38
|
+
|
|
39
|
+
for (const token of this.#rpn) {
|
|
40
|
+
if (token.tokenType === 'variable') {
|
|
41
|
+
varStack.push(token)
|
|
42
|
+
} else {
|
|
43
|
+
switch (token.token) {
|
|
44
|
+
case '&':
|
|
45
|
+
if (varStack.length >= 2) {
|
|
46
|
+
const second = varStack.pop(),
|
|
47
|
+
first = varStack.pop()
|
|
48
|
+
|
|
49
|
+
if (second && first) {
|
|
50
|
+
if (first.tokenType === 'mix') {
|
|
51
|
+
first.token = `( ${first.token} )`
|
|
52
|
+
}
|
|
53
|
+
if (second.tokenType === 'mix') {
|
|
54
|
+
second.token = `( ${second.token} )`
|
|
55
|
+
}
|
|
56
|
+
varStack.push({token: `${first.token} \\cap ${second.token}`, tokenType: 'mix'})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
break
|
|
60
|
+
case '|':
|
|
61
|
+
if (varStack.length >= 2) {
|
|
62
|
+
const second = varStack.pop(),
|
|
63
|
+
first = varStack.pop()
|
|
64
|
+
|
|
65
|
+
if (second && first) {
|
|
66
|
+
if (first.tokenType === 'mix') {
|
|
67
|
+
first.token = `( ${first.token} )`
|
|
68
|
+
}
|
|
69
|
+
if (second.tokenType === 'mix') {
|
|
70
|
+
second.token = `( ${second.token} )`
|
|
71
|
+
}
|
|
72
|
+
varStack.push({token: `${first.token} \\cup ${second.token}`, tokenType: 'mix'})
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
break
|
|
76
|
+
case '-':
|
|
77
|
+
if (varStack.length >= 2) {
|
|
78
|
+
const second = varStack.pop(),
|
|
79
|
+
first = varStack.pop()
|
|
80
|
+
|
|
81
|
+
if (second && first) {
|
|
82
|
+
if (first.tokenType === 'mix') {
|
|
83
|
+
first.token = `( ${first.token} )`
|
|
84
|
+
}
|
|
85
|
+
if (second.tokenType === 'mix') {
|
|
86
|
+
second.token = `( ${second.token} )`
|
|
87
|
+
}
|
|
88
|
+
varStack.push({token: `${first.token} \\setminus ${second.token}`, tokenType: 'mix'})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
break
|
|
92
|
+
case '!':
|
|
93
|
+
if (varStack.length >= 1) {
|
|
94
|
+
const first = varStack.pop()
|
|
95
|
+
|
|
96
|
+
if (first) {
|
|
97
|
+
varStack.push({token: `\\overline{ ${first.token} }`, tokenType: 'variable'})
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
break
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return varStack[0].token
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
evaluate(values: Record<string, boolean>): boolean {
|
|
109
|
+
// Add missing key(s) and set them as false by default.
|
|
110
|
+
this.variables.forEach(key => {
|
|
111
|
+
if (!Object.hasOwn(values, key)) {
|
|
112
|
+
values[key] = false
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const stack: boolean[] = []
|
|
117
|
+
for (const token of this.#rpn) {
|
|
118
|
+
if (token.tokenType === 'variable') {
|
|
119
|
+
stack.push(values[token.token])
|
|
120
|
+
} else if (token.tokenType === 'operation') {
|
|
121
|
+
if (token.token === '!') {
|
|
122
|
+
// need only one item from stack
|
|
123
|
+
if (stack.length >= 1) {
|
|
124
|
+
const a = stack.pop()
|
|
125
|
+
stack.push(!a)
|
|
126
|
+
} else {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
// All other operations needs two items from stack
|
|
131
|
+
const a = stack.pop()
|
|
132
|
+
const b = stack.pop()
|
|
133
|
+
if (a !== undefined && b !== undefined) {
|
|
134
|
+
switch (token.token) {
|
|
135
|
+
case "&":
|
|
136
|
+
stack.push(a && b)
|
|
137
|
+
break
|
|
138
|
+
case "|":
|
|
139
|
+
stack.push(a || b)
|
|
140
|
+
break
|
|
141
|
+
case "-":
|
|
142
|
+
return false
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
} else {
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return stack.length === 1 && stack[0]
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get rpn(): { token: string, tokenType: string }[] {
|
|
156
|
+
return this.#rpn
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
get variables(): string[] {
|
|
160
|
+
return this.#rpn
|
|
161
|
+
.filter(value => value.tokenType === 'variable')
|
|
162
|
+
.map(value => value.token)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
vennAB(): string[] {
|
|
166
|
+
return this.#evaluateAsVenn({
|
|
167
|
+
A: ['A', 'AB'],
|
|
168
|
+
B: ['B', 'AB']
|
|
169
|
+
},
|
|
170
|
+
['A', 'B', 'AB', 'E']
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
vennABC(): string[] {
|
|
175
|
+
return this.#evaluateAsVenn({
|
|
176
|
+
A: ['A', 'AB', 'AC', 'ABC'],
|
|
177
|
+
B: ['B', 'AB', 'BC', 'ABC'],
|
|
178
|
+
C: ['C', 'AC', 'BC', 'ABC']
|
|
179
|
+
},
|
|
180
|
+
['A', 'B', 'C', 'AB', 'AC', 'BC', 'ABC', 'E']
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#evaluateAsVenn(tokenSets: Record<string, string[] | undefined>, reference?: string[]): string[] {
|
|
185
|
+
const varStack: (Set<string>)[] = []
|
|
186
|
+
|
|
187
|
+
let referenceSet: Set<string>
|
|
188
|
+
if (reference === undefined) {
|
|
189
|
+
referenceSet = new Set()
|
|
190
|
+
for (const key in tokenSets) {
|
|
191
|
+
referenceSet = new Set([
|
|
192
|
+
...referenceSet,
|
|
193
|
+
...(tokenSets[key] ?? [])
|
|
194
|
+
])
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
referenceSet = new Set(reference)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
for (const token of this.#rpn) {
|
|
201
|
+
if (token.tokenType === 'variable') {
|
|
202
|
+
// The variable has no token - assume it's empty.
|
|
203
|
+
if (tokenSets[token.token] === undefined) {
|
|
204
|
+
varStack.push(new Set())
|
|
205
|
+
} else {
|
|
206
|
+
varStack.push(new Set(tokenSets[token.token]))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
} else {
|
|
210
|
+
switch (token.token) {
|
|
211
|
+
case '&':
|
|
212
|
+
if (varStack.length >= 2) {
|
|
213
|
+
const second = varStack.pop(),
|
|
214
|
+
first = varStack.pop()
|
|
215
|
+
|
|
216
|
+
if (first && second) {
|
|
217
|
+
varStack.push(new Set([...first].filter(x => second.has(x))))
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
break
|
|
221
|
+
case '|':
|
|
222
|
+
if (varStack.length >= 2) {
|
|
223
|
+
const second = varStack.pop(),
|
|
224
|
+
first = varStack.pop()
|
|
225
|
+
if (first && second) {
|
|
226
|
+
varStack.push(new Set([...first, ...second]))
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
break
|
|
230
|
+
case '-':
|
|
231
|
+
if (varStack.length >= 2) {
|
|
232
|
+
const second = varStack.pop(),
|
|
233
|
+
first = varStack.pop()
|
|
234
|
+
|
|
235
|
+
if (first && second) {
|
|
236
|
+
varStack.push(new Set([...first].filter(x => !second.has(x))))
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
break
|
|
240
|
+
case '!':
|
|
241
|
+
if (varStack.length >= 1) {
|
|
242
|
+
const first = varStack.pop()
|
|
243
|
+
|
|
244
|
+
if (first) {
|
|
245
|
+
varStack.push(new Set([...referenceSet].filter(x => !first.has(x))))
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
break
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return [...varStack[0]].sort()
|
|
254
|
+
}
|
|
255
|
+
}
|