slangmath 1.0.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/ARCHITECTURE.MD +366 -0
- package/LICENSE +21 -0
- package/README.md +953 -0
- package/package.json +23 -0
- package/slang-advanced.js +559 -0
- package/slang-basic.js +766 -0
- package/slang-cache.js +519 -0
- package/slang-convertor.js +652 -0
- package/slang-errors.js +454 -0
- package/slang-extended.js +501 -0
- package/slang-helpers.js +284 -0
- package/slang-math.js +3 -0
package/slang-basic.js
ADDED
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SLaNg (Saad Language for Analytical Numerics and Geometry) - Math Library
|
|
3
|
+
* Enhanced with full denominator support and improved calculus operations
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// DEEP COPY UTILITIES
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Deep clone an equation to avoid mutation
|
|
13
|
+
*/
|
|
14
|
+
function deepClone(obj) {
|
|
15
|
+
return JSON.parse(JSON.stringify(obj));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// TERM CREATION UTILITIES
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create a term with coefficient and optional variables
|
|
24
|
+
* @param {number} coeff - Coefficient
|
|
25
|
+
* @param {Object} vars - Variables object like {x: 2, y: 1}
|
|
26
|
+
*/
|
|
27
|
+
function createTerm(coeff, vars = {}) {
|
|
28
|
+
const term = { coeff };
|
|
29
|
+
if (Object.keys(vars).length > 0) {
|
|
30
|
+
term.var = { ...vars };
|
|
31
|
+
}
|
|
32
|
+
return term;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create a fraction with polynomial numerator and denominator
|
|
37
|
+
* @param {Array} numiTerms - Array of terms for numerator
|
|
38
|
+
* @param {Array|number} denoTerms - Array of terms for denominator OR simple number
|
|
39
|
+
*
|
|
40
|
+
*: Full support for polynomial denominators
|
|
41
|
+
*/
|
|
42
|
+
function createFraction(numiTerms, denoTerms = 1) {
|
|
43
|
+
const fraction = {
|
|
44
|
+
numi: { terms: deepClone(numiTerms) }
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Support both old (number) and new (polynomial) denominators
|
|
48
|
+
if (typeof denoTerms === 'number') {
|
|
49
|
+
fraction.deno = denoTerms;
|
|
50
|
+
} else if (Array.isArray(denoTerms)) {
|
|
51
|
+
fraction.deno = { terms: deepClone(denoTerms) };
|
|
52
|
+
} else if (denoTerms.terms) {
|
|
53
|
+
fraction.deno = { terms: deepClone(denoTerms.terms) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return fraction;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if denominator is a simple number
|
|
61
|
+
*/
|
|
62
|
+
function hasSimpleDenominator(fraction) {
|
|
63
|
+
return typeof fraction.deno === 'number';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get GCD of two numbers for simplification
|
|
68
|
+
*/
|
|
69
|
+
function gcd(a, b) {
|
|
70
|
+
a = Math.abs(a);
|
|
71
|
+
b = Math.abs(b);
|
|
72
|
+
while (b) {
|
|
73
|
+
[a, b] = [b, a % b];
|
|
74
|
+
}
|
|
75
|
+
return a || 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// EVALUATION FUNCTIONS
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Evaluate a term at given variable values
|
|
84
|
+
* @param {Object} term - A single term
|
|
85
|
+
* @param {Object} values - Variable values like {x: 2, y: 3}
|
|
86
|
+
*/
|
|
87
|
+
function evaluateTerm(term, values) {
|
|
88
|
+
let result = term.coeff;
|
|
89
|
+
|
|
90
|
+
if (term.var) {
|
|
91
|
+
for (let [variable, power] of Object.entries(term.var)) {
|
|
92
|
+
if (values[variable] === undefined) {
|
|
93
|
+
throw new Error(`Variable ${variable} not provided`);
|
|
94
|
+
}
|
|
95
|
+
result *= Math.pow(values[variable], power);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Evaluate polynomial (array of terms)
|
|
104
|
+
*/
|
|
105
|
+
function evaluatePolynomial(polynomial, values) {
|
|
106
|
+
let sum = 0;
|
|
107
|
+
for (let term of polynomial.terms) {
|
|
108
|
+
sum += evaluateTerm(term, values);
|
|
109
|
+
}
|
|
110
|
+
return sum;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Evaluate a fraction (numerator / denominator)
|
|
115
|
+
*: Supports polynomial denominators
|
|
116
|
+
*/
|
|
117
|
+
function evaluateFraction(fraction, values) {
|
|
118
|
+
const numeratorSum = evaluatePolynomial(fraction.numi, values);
|
|
119
|
+
|
|
120
|
+
if (hasSimpleDenominator(fraction)) {
|
|
121
|
+
return numeratorSum / fraction.deno;
|
|
122
|
+
} else {
|
|
123
|
+
const denominatorSum = evaluatePolynomial(fraction.deno, values);
|
|
124
|
+
return numeratorSum / denominatorSum;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Evaluate a product (array of fractions multiplied together)
|
|
130
|
+
*/
|
|
131
|
+
function evaluateProduct(product, values) {
|
|
132
|
+
let result = 1;
|
|
133
|
+
for (let fraction of product) {
|
|
134
|
+
result *= evaluateFraction(fraction, values);
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Evaluate entire equation (sum of products)
|
|
141
|
+
*/
|
|
142
|
+
function evaluateEquation(equation, values) {
|
|
143
|
+
let result = 0;
|
|
144
|
+
for (let product of equation) {
|
|
145
|
+
result += evaluateProduct(product, values);
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ============================================================================
|
|
151
|
+
// INTEGRATION FUNCTIONS - ENHANCED
|
|
152
|
+
// ============================================================================
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Integrate a single term with respect to a variable
|
|
156
|
+
* Power rule: ∫ c*x^n dx = c/(n+1) * x^(n+1)
|
|
157
|
+
*/
|
|
158
|
+
function integrateTerm(term, indvar) {
|
|
159
|
+
const newTerm = deepClone(term);
|
|
160
|
+
|
|
161
|
+
// Get current power of the variable (0 if not present)
|
|
162
|
+
const power = newTerm.var?.[indvar] ?? 0;
|
|
163
|
+
|
|
164
|
+
// Check for special case: x^(-1) -> ln|x| (not handled symbolically yet)
|
|
165
|
+
if (power === -1) {
|
|
166
|
+
throw new Error('Integration of 1/x requires logarithm (not yet implemented)');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Apply power rule
|
|
170
|
+
newTerm.coeff = newTerm.coeff / (power + 1);
|
|
171
|
+
|
|
172
|
+
// Increment power
|
|
173
|
+
if (!newTerm.var) {
|
|
174
|
+
newTerm.var = {};
|
|
175
|
+
}
|
|
176
|
+
newTerm.var[indvar] = power + 1;
|
|
177
|
+
|
|
178
|
+
return newTerm;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Integrate a polynomial
|
|
183
|
+
*/
|
|
184
|
+
function integratePolynomial(polynomial, indvar) {
|
|
185
|
+
return {
|
|
186
|
+
terms: polynomial.terms.map(term => integrateTerm(term, indvar))
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Integrate a fraction
|
|
192
|
+
*: Improved handling of different denominator types
|
|
193
|
+
*/
|
|
194
|
+
function integrateFraction(fraction, indvar) {
|
|
195
|
+
if (hasSimpleDenominator(fraction)) {
|
|
196
|
+
// Simple case: polynomial / constant
|
|
197
|
+
return {
|
|
198
|
+
numi: integratePolynomial(fraction.numi, indvar),
|
|
199
|
+
deno: fraction.deno
|
|
200
|
+
};
|
|
201
|
+
} else {
|
|
202
|
+
// Complex case: polynomial / polynomial
|
|
203
|
+
// This requires more advanced techniques
|
|
204
|
+
|
|
205
|
+
// Check if it's a simple substitution case
|
|
206
|
+
if (isSimpleSubstitutionCase(fraction, indvar)) {
|
|
207
|
+
return integrateBySubstitution(fraction, indvar);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Otherwise, try partial fractions or numerical methods
|
|
211
|
+
throw new Error(
|
|
212
|
+
'Integration of complex rational functions requires partial fractions or numerical methods. ' +
|
|
213
|
+
'Use numericalIntegrateFraction() instead.'
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Check if fraction can be integrated by simple substitution
|
|
220
|
+
* e.g., ∫ 2x/(x²+1) dx where numerator is derivative of denominator
|
|
221
|
+
*/
|
|
222
|
+
function isSimpleSubstitutionCase(fraction, indvar) {
|
|
223
|
+
if (hasSimpleDenominator(fraction)) return false;
|
|
224
|
+
|
|
225
|
+
// Differentiate denominator
|
|
226
|
+
const denoDeriv = differentiatePolynomial(fraction.deno, indvar);
|
|
227
|
+
|
|
228
|
+
// Check if numerator is a constant multiple of denominator derivative
|
|
229
|
+
if (fraction.numi.terms.length !== denoDeriv.terms.length) return false;
|
|
230
|
+
|
|
231
|
+
// More sophisticated check would go here
|
|
232
|
+
return false; // Conservative for now
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Definite integration of a term
|
|
237
|
+
* Evaluates ∫[lower to upper] term dx
|
|
238
|
+
*/
|
|
239
|
+
function definiteIntegrateTerm(term, lower, upper, indvar) {
|
|
240
|
+
const integratedTerm = integrateTerm(term, indvar);
|
|
241
|
+
|
|
242
|
+
// Get power of integration variable in the integrated term
|
|
243
|
+
const intPower = integratedTerm.var?.[indvar] ?? 0;
|
|
244
|
+
|
|
245
|
+
// Calculate the coefficient multiplier from bounds
|
|
246
|
+
const upperValue = Math.pow(upper, intPower);
|
|
247
|
+
const lowerValue = Math.pow(lower, intPower);
|
|
248
|
+
const boundsDiff = upperValue - lowerValue;
|
|
249
|
+
|
|
250
|
+
// Create result term
|
|
251
|
+
const resultTerm = deepClone(integratedTerm);
|
|
252
|
+
resultTerm.coeff = resultTerm.coeff * boundsDiff;
|
|
253
|
+
|
|
254
|
+
// Remove the integration variable
|
|
255
|
+
if (resultTerm.var) {
|
|
256
|
+
delete resultTerm.var[indvar];
|
|
257
|
+
if (Object.keys(resultTerm.var).length === 0) {
|
|
258
|
+
delete resultTerm.var;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return resultTerm;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Definite integration of a polynomial
|
|
267
|
+
*/
|
|
268
|
+
function definiteIntegratePolynomial(polynomial, lower, upper, indvar) {
|
|
269
|
+
return {
|
|
270
|
+
terms: polynomial.terms.map(term =>
|
|
271
|
+
definiteIntegrateTerm(term, lower, upper, indvar)
|
|
272
|
+
)
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Definite integration of a fraction
|
|
278
|
+
*: Better handling for different denominator types
|
|
279
|
+
*/
|
|
280
|
+
function definiteIntegrateFraction(fraction, lower, upper, indvar) {
|
|
281
|
+
if (hasSimpleDenominator(fraction)) {
|
|
282
|
+
// Simple case
|
|
283
|
+
return {
|
|
284
|
+
numi: definiteIntegratePolynomial(fraction.numi, lower, upper, indvar),
|
|
285
|
+
deno: fraction.deno
|
|
286
|
+
};
|
|
287
|
+
} else {
|
|
288
|
+
// For polynomial denominators, use numerical integration
|
|
289
|
+
return numericalIntegrateFraction(fraction, lower, upper, indvar);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Numerical integration using Simpson's rule (more accurate than rectangles)
|
|
295
|
+
*: Enhanced numerical integration
|
|
296
|
+
*/
|
|
297
|
+
function numericalIntegrateFraction(fraction, lower, upper, indvar, numSteps = 1000) {
|
|
298
|
+
// Simpson's rule requires even number of steps
|
|
299
|
+
if (numSteps % 2 !== 0) numSteps++;
|
|
300
|
+
|
|
301
|
+
const h = (upper - lower) / numSteps;
|
|
302
|
+
let sum = 0;
|
|
303
|
+
|
|
304
|
+
// Simpson's rule: h/3 * [f(x0) + 4*f(x1) + 2*f(x2) + 4*f(x3) + ... + f(xn)]
|
|
305
|
+
for (let i = 0; i <= numSteps; i++) {
|
|
306
|
+
const x = lower + i * h;
|
|
307
|
+
const point = { [indvar]: x };
|
|
308
|
+
const value = evaluateFraction(fraction, point);
|
|
309
|
+
|
|
310
|
+
if (i === 0 || i === numSteps) {
|
|
311
|
+
sum += value;
|
|
312
|
+
} else if (i % 2 === 1) {
|
|
313
|
+
sum += 4 * value;
|
|
314
|
+
} else {
|
|
315
|
+
sum += 2 * value;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const result = (h / 3) * sum;
|
|
320
|
+
|
|
321
|
+
// Return as a constant term
|
|
322
|
+
return {
|
|
323
|
+
numi: { terms: [createTerm(result)] },
|
|
324
|
+
deno: 1
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ============================================================================
|
|
329
|
+
// DIFFERENTIATION FUNCTIONS - ENHANCED
|
|
330
|
+
// ============================================================================
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Differentiate a term with respect to a variable
|
|
334
|
+
* Power rule: d/dx(c*x^n) = c*n*x^(n-1)
|
|
335
|
+
*/
|
|
336
|
+
function differentiateTerm(term, indvar) {
|
|
337
|
+
const newTerm = deepClone(term);
|
|
338
|
+
|
|
339
|
+
// Get current power
|
|
340
|
+
const power = newTerm.var?.[indvar];
|
|
341
|
+
|
|
342
|
+
// If variable not present, derivative is 0
|
|
343
|
+
if (power === undefined) {
|
|
344
|
+
return createTerm(0);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Apply power rule
|
|
348
|
+
newTerm.coeff = newTerm.coeff * power;
|
|
349
|
+
|
|
350
|
+
// Decrease power
|
|
351
|
+
if (power === 1) {
|
|
352
|
+
delete newTerm.var[indvar];
|
|
353
|
+
if (Object.keys(newTerm.var).length === 0) {
|
|
354
|
+
delete newTerm.var;
|
|
355
|
+
}
|
|
356
|
+
} else {
|
|
357
|
+
newTerm.var[indvar] = power - 1;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return newTerm;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Differentiate a polynomial
|
|
365
|
+
*/
|
|
366
|
+
function differentiatePolynomial(polynomial, indvar) {
|
|
367
|
+
return {
|
|
368
|
+
terms: polynomial.terms
|
|
369
|
+
.map(term => differentiateTerm(term, indvar))
|
|
370
|
+
.filter(term => term.coeff !== 0)
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Differentiate a fraction using quotient rule
|
|
376
|
+
*: Full quotient rule for polynomial denominators
|
|
377
|
+
*
|
|
378
|
+
* d/dx[f/g] = (f'g - fg') / g²
|
|
379
|
+
*/
|
|
380
|
+
function differentiateFraction(fraction, indvar) {
|
|
381
|
+
if (hasSimpleDenominator(fraction)) {
|
|
382
|
+
// Simple case: just differentiate numerator, keep constant denominator
|
|
383
|
+
return {
|
|
384
|
+
numi: differentiatePolynomial(fraction.numi, indvar),
|
|
385
|
+
deno: fraction.deno
|
|
386
|
+
};
|
|
387
|
+
} else {
|
|
388
|
+
// Quotient rule for polynomial denominator
|
|
389
|
+
const f = fraction.numi;
|
|
390
|
+
const g = fraction.deno;
|
|
391
|
+
|
|
392
|
+
const fPrime = differentiatePolynomial(f, indvar);
|
|
393
|
+
const gPrime = differentiatePolynomial(g, indvar);
|
|
394
|
+
|
|
395
|
+
// f' * g
|
|
396
|
+
const fPrimeTimesG = multiplyPolynomials(fPrime, g);
|
|
397
|
+
|
|
398
|
+
// f * g'
|
|
399
|
+
const fTimesGPrime = multiplyPolynomials(f, gPrime);
|
|
400
|
+
|
|
401
|
+
// f'g - fg'
|
|
402
|
+
const numerator = subtractPolynomials(fPrimeTimesG, fTimesGPrime);
|
|
403
|
+
|
|
404
|
+
// g²
|
|
405
|
+
const denominator = multiplyPolynomials(g, g);
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
numi: numerator,
|
|
409
|
+
deno: denominator
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ============================================================================
|
|
415
|
+
// POLYNOMIAL ARITHMETIC -
|
|
416
|
+
// ============================================================================
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Add two polynomials
|
|
420
|
+
*/
|
|
421
|
+
function addPolynomials(poly1, poly2) {
|
|
422
|
+
return {
|
|
423
|
+
terms: [...poly1.terms, ...poly2.terms]
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Subtract two polynomials
|
|
429
|
+
*/
|
|
430
|
+
function subtractPolynomials(poly1, poly2) {
|
|
431
|
+
const negatedPoly2 = {
|
|
432
|
+
terms: poly2.terms.map(t => ({ ...deepClone(t), coeff: -t.coeff }))
|
|
433
|
+
};
|
|
434
|
+
return addPolynomials(poly1, negatedPoly2);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Multiply two terms together
|
|
439
|
+
*/
|
|
440
|
+
function multiplyTerms(term1, term2) {
|
|
441
|
+
const result = {
|
|
442
|
+
coeff: term1.coeff * term2.coeff
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// Combine variables (add exponents for same variable)
|
|
446
|
+
const vars = {};
|
|
447
|
+
|
|
448
|
+
if (term1.var) {
|
|
449
|
+
for (let [v, pow] of Object.entries(term1.var)) {
|
|
450
|
+
vars[v] = pow;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (term2.var) {
|
|
455
|
+
for (let [v, pow] of Object.entries(term2.var)) {
|
|
456
|
+
vars[v] = (vars[v] || 0) + pow;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (Object.keys(vars).length > 0) {
|
|
461
|
+
result.var = vars;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return result;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Multiply two polynomials
|
|
469
|
+
*: Essential for quotient rule
|
|
470
|
+
*/
|
|
471
|
+
function multiplyPolynomials(poly1, poly2) {
|
|
472
|
+
const resultTerms = [];
|
|
473
|
+
|
|
474
|
+
for (let term1 of poly1.terms) {
|
|
475
|
+
for (let term2 of poly2.terms) {
|
|
476
|
+
resultTerms.push(multiplyTerms(term1, term2));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return { terms: resultTerms };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ============================================================================
|
|
484
|
+
// SIMPLIFICATION FUNCTIONS - ENHANCED
|
|
485
|
+
// ============================================================================
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Combine like terms in a polynomial
|
|
489
|
+
*/
|
|
490
|
+
function simplifyPolynomial(polynomial) {
|
|
491
|
+
const termMap = new Map();
|
|
492
|
+
|
|
493
|
+
for (let term of polynomial.terms) {
|
|
494
|
+
const varKey = term.var ? JSON.stringify(term.var) : 'constant';
|
|
495
|
+
|
|
496
|
+
if (termMap.has(varKey)) {
|
|
497
|
+
termMap.get(varKey).coeff += term.coeff;
|
|
498
|
+
} else {
|
|
499
|
+
termMap.set(varKey, deepClone(term));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Filter out zero terms and sort for consistency
|
|
504
|
+
const simplifiedTerms = Array.from(termMap.values())
|
|
505
|
+
.filter(term => Math.abs(term.coeff) > 1e-10)
|
|
506
|
+
.sort((a, b) => {
|
|
507
|
+
// Sort by total degree (descending)
|
|
508
|
+
const degreeA = a.var ? Object.values(a.var).reduce((sum, p) => sum + p, 0) : 0;
|
|
509
|
+
const degreeB = b.var ? Object.values(b.var).reduce((sum, p) => sum + p, 0) : 0;
|
|
510
|
+
return degreeB - degreeA;
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
return { terms: simplifiedTerms };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Simplify a fraction
|
|
518
|
+
*: Enhanced to handle polynomial denominators
|
|
519
|
+
*/
|
|
520
|
+
function simplifyFraction(fraction) {
|
|
521
|
+
const simplifiedNumi = simplifyPolynomial(fraction.numi);
|
|
522
|
+
|
|
523
|
+
if (hasSimpleDenominator(fraction)) {
|
|
524
|
+
// Try to simplify constant denominator with GCD
|
|
525
|
+
if (simplifiedNumi.terms.length > 0) {
|
|
526
|
+
const coeffs = simplifiedNumi.terms.map(t => t.coeff);
|
|
527
|
+
const numGCD = coeffs.reduce((a, b) => gcd(a, b));
|
|
528
|
+
const denoGCD = gcd(numGCD, fraction.deno);
|
|
529
|
+
|
|
530
|
+
if (denoGCD > 1) {
|
|
531
|
+
return {
|
|
532
|
+
numi: {
|
|
533
|
+
terms: simplifiedNumi.terms.map(t => ({
|
|
534
|
+
...deepClone(t),
|
|
535
|
+
coeff: t.coeff / denoGCD
|
|
536
|
+
}))
|
|
537
|
+
},
|
|
538
|
+
deno: fraction.deno / denoGCD
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return {
|
|
544
|
+
numi: simplifiedNumi,
|
|
545
|
+
deno: fraction.deno
|
|
546
|
+
};
|
|
547
|
+
} else {
|
|
548
|
+
// Simplify both numerator and denominator
|
|
549
|
+
const simplifiedDeno = simplifyPolynomial(fraction.deno);
|
|
550
|
+
|
|
551
|
+
// TODO: Factor and cancel common factors
|
|
552
|
+
return {
|
|
553
|
+
numi: simplifiedNumi,
|
|
554
|
+
deno: simplifiedDeno
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Simplify an entire product
|
|
561
|
+
*/
|
|
562
|
+
function simplifyProduct(product) {
|
|
563
|
+
return product.map(fraction => simplifyFraction(fraction));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Simplify entire equation
|
|
568
|
+
*/
|
|
569
|
+
function simplifyEquation(equation) {
|
|
570
|
+
return equation.map(product => simplifyProduct(product));
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// ============================================================================
|
|
574
|
+
// EXPANSION FUNCTIONS
|
|
575
|
+
// ============================================================================
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Expand product of two fractions (both with simple denominators)
|
|
579
|
+
* (a + b)(c + d) = ac + ad + bc + bd
|
|
580
|
+
*/
|
|
581
|
+
function expandFractions(frac1, frac2) {
|
|
582
|
+
const numi1 = frac1.numi;
|
|
583
|
+
const numi2 = frac2.numi;
|
|
584
|
+
|
|
585
|
+
const expandedNumi = multiplyPolynomials(numi1, numi2);
|
|
586
|
+
|
|
587
|
+
// Handle denominators
|
|
588
|
+
let newDeno;
|
|
589
|
+
if (hasSimpleDenominator(frac1) && hasSimpleDenominator(frac2)) {
|
|
590
|
+
newDeno = frac1.deno * frac2.deno;
|
|
591
|
+
} else if (hasSimpleDenominator(frac1) && !hasSimpleDenominator(frac2)) {
|
|
592
|
+
newDeno = multiplyPolynomialByConstant(frac2.deno, frac1.deno);
|
|
593
|
+
} else if (!hasSimpleDenominator(frac1) && hasSimpleDenominator(frac2)) {
|
|
594
|
+
newDeno = multiplyPolynomialByConstant(frac1.deno, frac2.deno);
|
|
595
|
+
} else {
|
|
596
|
+
newDeno = multiplyPolynomials(frac1.deno, frac2.deno);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return simplifyFraction({
|
|
600
|
+
numi: expandedNumi,
|
|
601
|
+
deno: newDeno
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Multiply polynomial by constant
|
|
607
|
+
*/
|
|
608
|
+
function multiplyPolynomialByConstant(polynomial, constant) {
|
|
609
|
+
return {
|
|
610
|
+
terms: polynomial.terms.map(t => ({ ...deepClone(t), coeff: t.coeff * constant }))
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Expand a product into a single fraction
|
|
616
|
+
*/
|
|
617
|
+
function expandProduct(product) {
|
|
618
|
+
if (product.length === 0) {
|
|
619
|
+
return createFraction([createTerm(1)]);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
let result = product[0];
|
|
623
|
+
|
|
624
|
+
for (let i = 1; i < product.length; i++) {
|
|
625
|
+
result = expandFractions(result, product[i]);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return result;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// ============================================================================
|
|
632
|
+
// DISPLAY FUNCTIONS
|
|
633
|
+
// ============================================================================
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Convert term to readable string
|
|
637
|
+
*/
|
|
638
|
+
function termToString(term) {
|
|
639
|
+
let str = '';
|
|
640
|
+
|
|
641
|
+
// Handle coefficient
|
|
642
|
+
if (term.coeff === 0) return '0';
|
|
643
|
+
if (Math.abs(term.coeff - 1) < 1e-10 && term.var) {
|
|
644
|
+
str = '';
|
|
645
|
+
} else if (Math.abs(term.coeff + 1) < 1e-10 && term.var) {
|
|
646
|
+
str = '-';
|
|
647
|
+
} else {
|
|
648
|
+
str = term.coeff.toString();
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Handle variables
|
|
652
|
+
if (term.var) {
|
|
653
|
+
const varStr = Object.entries(term.var)
|
|
654
|
+
.map(([variable, power]) => {
|
|
655
|
+
if (power === 1) return variable;
|
|
656
|
+
return `${variable}^${power}`;
|
|
657
|
+
})
|
|
658
|
+
.join('*');
|
|
659
|
+
str += (str && str !== '-' ? '*' : '') + varStr;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
return str || term.coeff.toString();
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Convert polynomial to readable string
|
|
667
|
+
*/
|
|
668
|
+
function polynomialToString(polynomial) {
|
|
669
|
+
if (!polynomial.terms || polynomial.terms.length === 0) return '0';
|
|
670
|
+
|
|
671
|
+
return polynomial.terms.map((term, i) => {
|
|
672
|
+
const termStr = termToString(term);
|
|
673
|
+
if (i === 0) return termStr;
|
|
674
|
+
if (term.coeff >= 0) return '+ ' + termStr;
|
|
675
|
+
return termStr; // Negative sign already in termStr
|
|
676
|
+
}).join(' ');
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Convert fraction to readable string
|
|
681
|
+
*/
|
|
682
|
+
function fractionToString(fraction) {
|
|
683
|
+
const numi = polynomialToString(fraction.numi);
|
|
684
|
+
|
|
685
|
+
if (hasSimpleDenominator(fraction)) {
|
|
686
|
+
if (fraction.deno === 1) {
|
|
687
|
+
return `(${numi})`;
|
|
688
|
+
}
|
|
689
|
+
return `(${numi})/${fraction.deno}`;
|
|
690
|
+
} else {
|
|
691
|
+
const deno = polynomialToString(fraction.deno);
|
|
692
|
+
return `(${numi})/(${deno})`;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Convert product to readable string
|
|
698
|
+
*/
|
|
699
|
+
function productToString(product) {
|
|
700
|
+
return product.map(fractionToString).join(' * ');
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Convert equation to readable string
|
|
705
|
+
*/
|
|
706
|
+
function equationToString(equation) {
|
|
707
|
+
return equation.map(productToString).join(' + ');
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// ============================================================================
|
|
711
|
+
// EXPORTS
|
|
712
|
+
// ============================================================================
|
|
713
|
+
|
|
714
|
+
export {
|
|
715
|
+
// Utilities
|
|
716
|
+
deepClone,
|
|
717
|
+
createTerm,
|
|
718
|
+
createFraction,
|
|
719
|
+
hasSimpleDenominator,
|
|
720
|
+
gcd,
|
|
721
|
+
|
|
722
|
+
// Evaluation
|
|
723
|
+
evaluateTerm,
|
|
724
|
+
evaluatePolynomial,
|
|
725
|
+
evaluateFraction,
|
|
726
|
+
evaluateProduct,
|
|
727
|
+
evaluateEquation,
|
|
728
|
+
|
|
729
|
+
// Integration
|
|
730
|
+
integrateTerm,
|
|
731
|
+
integratePolynomial,
|
|
732
|
+
integrateFraction,
|
|
733
|
+
definiteIntegrateTerm,
|
|
734
|
+
definiteIntegratePolynomial,
|
|
735
|
+
definiteIntegrateFraction,
|
|
736
|
+
numericalIntegrateFraction,
|
|
737
|
+
|
|
738
|
+
// Differentiation
|
|
739
|
+
differentiateTerm,
|
|
740
|
+
differentiatePolynomial,
|
|
741
|
+
differentiateFraction,
|
|
742
|
+
|
|
743
|
+
// Polynomial Arithmetic
|
|
744
|
+
addPolynomials,
|
|
745
|
+
subtractPolynomials,
|
|
746
|
+
multiplyTerms,
|
|
747
|
+
multiplyPolynomials,
|
|
748
|
+
multiplyPolynomialByConstant,
|
|
749
|
+
|
|
750
|
+
// Simplification
|
|
751
|
+
simplifyPolynomial,
|
|
752
|
+
simplifyFraction,
|
|
753
|
+
simplifyProduct,
|
|
754
|
+
simplifyEquation,
|
|
755
|
+
|
|
756
|
+
// Expansion
|
|
757
|
+
expandFractions,
|
|
758
|
+
expandProduct,
|
|
759
|
+
|
|
760
|
+
// Display
|
|
761
|
+
termToString,
|
|
762
|
+
polynomialToString,
|
|
763
|
+
fractionToString,
|
|
764
|
+
productToString,
|
|
765
|
+
equationToString
|
|
766
|
+
};
|