-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathproducts.dart
382 lines (352 loc) · 12.3 KB
/
products.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
import 'term.dart';
import 'constants.dart';
import 'unknowns.dart';
import 'sums.dart';
/// A helper class to accumulate lists of factors for the numerator (and optional denominator)
/// of a bunch of Term objects that are being multiplied together.
///
/// This class will deal with a number of simplification operations including cancelling
/// common factors in the numerator and denominator and consolidating constants into the
/// coefficient term.
class FactorAccumulator {
List<Term> numerators = [];
List<Term> denominators;
Constant coefficient = one;
/// Accumulate a single double value into the coefficient (with optional inversion).
void accumulateValue(Constant val, bool isInverted) {
if (isInverted) {
coefficient /= val;
} else {
coefficient *= val;
}
}
/// Accumulate an arbitrary Term object into either the numerator or the denominator
/// depending on the isInverted boolean.
void accumulate(Term t, bool isInverted) {
if (t is Constant) {
accumulateValue(t, isInverted);
} else if (t is Product) {
accumulateValue(t.coefficient, isInverted);
for (var f in t.factors) accumulate(f, isInverted);
} else if (t is Division) {
accumulate(t.numerator, isInverted);
accumulate(t.denominator, !isInverted);
} else {
if (isInverted) {
denominators ??= [];
denominators.add(t);
} else {
numerators.add(t);
}
}
}
/// Eliminate any duplicate copy of the given Term object from the list of numerator
/// factors and return a boolean indicating the success of the elimination.
///
/// Called only for factors being considered for the denominator.
bool _cancelTerm(Term t) {
for (int i = 0; i < numerators.length; i++) {
Term n = numerators[i];
if (n.equals(t)) {
numerators.removeAt(i);
return true;
}
}
return false;
}
/// Return an optimized result for a list of factors, including reduction to a constant
/// or a single term, and distributing any addition Term objects into the remaining factors.
static Term _forList(Constant coefficient, List<Term> terms) {
if (terms.length == 0) {
return coefficient;
} else if (terms.length == 1) {
if (coefficient == one) return terms[0];
if (coefficient == neg_one) return -terms[0];
}
return distribute(coefficient, terms);
}
/// Look for a factor that is a Sum object and distribute it into the other factors
/// to enable a simplified form in which we can cancel terms more effectively.
static Term distribute(Constant coefficient, List<Term> terms) {
for (int i = 0; i < terms.length; i++) {
Term term = terms[i];
if (term is Sum) {
List<Term> distributed = [];
for (var addend in term.addends) {
distributed.add(Product.mulList(coefficient, [
...terms.sublist(0, i),
addend,
...terms.sublist(i + 1),
]));
}
return Sum.addList(distributed);
}
}
return Product(
coefficient: coefficient,
factors: terms,
);
}
/// Check a numerator and a denominator that are summations of other terms for a
/// common factor and eliminate it.
///
/// The current implementation is very simple in that it first looks for common
/// factors within the numerator and denominator and sees if the remainders of
/// the two lists are identical. Rather than divide out the common factor, the
/// implementation cross-multiplies the numerator by the common factor in the
/// denominator and vice versa and then tests those products for equality.
///
/// If the "remainders" (or the "cross-multiplied factors") are identical, this
/// division reduces to the division of the two common factors determined in the
/// first step.
static Term divideSums(Sum numerator, Sum denominator) {
Term numCommon = numerator.commonFactor();
Term denCommon = denominator.commonFactor();
if (numCommon == one && denCommon == one) return null;
Term numCross = numerator * denCommon;
Term denCross = denominator * numCommon;
return (numCross.equals(denCross))
? (numCommon / denCommon)
: null;
}
/// Return the Term object representing the sum total of this entire multiply or divide
/// operation, simplifying as much as possible.
///
/// Current simplifications include the natural simplifications that occurred during
/// factor accumulation and the additional simplifications that come from special
/// coefficients that overwhelm the product (0, nan, infinities) and the simplifications
/// determined by the divideSums() method.
Term getResult() {
if (coefficient.overwhelmsProducts()) {
return coefficient;
}
if (denominators != null) {
int keep = 0;
for (int i = 0; i < denominators.length; i++) {
if (!_cancelTerm(denominators[i])) {
denominators[keep++] = denominators[i];
}
}
if (keep > 0) {
denominators.length = keep;
Term num = _forList(coefficient, numerators);
Term den = _forList(one, denominators);
if (num is Sum && den is Sum) {
Term simplified = divideSums(num, den);
if (simplified != null) return simplified;
}
return Division(num, den);
}
}
return _forList(coefficient, numerators);
}
}
/// A Term object representing the multiplication of a number of other Term objects.
class Product extends Term {
final Constant coefficient;
final List<Term> factors;
Product({this.coefficient = one, List<Term> factors}) : factors = List.unmodifiable(factors);
/// Multiply a list of Term objects with an additional coefficient and return the
/// Term object representing the simplified result.
static Term mulList(Constant coefficient, List<Term> terms) {
FactorAccumulator accumulator = FactorAccumulator();
accumulator.accumulateValue(coefficient, false);
for (var term in terms) accumulator.accumulate(term, false);
return accumulator.getResult();
}
/// Multiply 2 Term objects and return the Term object representing the simplified result.
static Term mul(Term first, Term second) {
FactorAccumulator accumulator = FactorAccumulator();
accumulator.accumulate(first, false);
accumulator.accumulate(second, false);
return accumulator.getResult();
}
@override
bool isNegative() {
bool isNeg = coefficient.isNegative();
for (var factor in factors) {
if (factor.isNegative()) isNeg = !isNeg;
}
return isNeg;
}
@override
bool negatesGracefully() {
for (var term in factors) {
if (term.negatesGracefully()) return true;
}
return false;
}
@override
Term operator -() {
if (!coefficient.isNegative()) {
for (int i = 0; i < factors.length; i++) {
Term term = factors[i];
if (term.negatesGracefully()) {
return Product(
coefficient: coefficient,
factors: [
...factors.sublist(0, i),
-term,
...factors.sublist(i+1),
],
);
}
}
}
if (coefficient == neg_one && factors.length == 1) {
return factors[0];
}
return Product(coefficient: -coefficient, factors: factors);
}
@override Term addDirect(Term other, bool isNegated) {
Constant delta;
if (other is Product && equalFactors(this.factors, other.factors)) {
delta = other.coefficient;
} else if (other is Unknown && this.factors.length == 1 && this.factors[0].equals(other)) {
delta = one;
} else {
return null;
}
Constant newCoefficient = isNegated ? this.coefficient - delta : this.coefficient + delta;
if (newCoefficient.overwhelmsProducts()) return newCoefficient;
return Product(coefficient: newCoefficient, factors: this.factors);
}
@override bool equals(Term other) {
if (other is Product) {
return coefficient == other.coefficient && equalFactors(factors, other.factors);
}
return false;
}
/// Compare two lists of factors to determine if they are identical.
static bool equalFactors(List<Term> factors1, List<Term> factors2) {
if (factors1.length != factors2.length) return false;
List<Term> factors2Copy = [...factors2];
for (var term in factors1) {
bool foundIt = false;
for (int i = 0; i < factors2Copy.length; i++) {
if (term.equals(factors2Copy[i])) {
factors2Copy.removeAt(i);
foundIt = true;
break;
}
}
if (!foundIt) return false;
}
return factors2Copy.length == 0;
}
/// A comparator function for ordering the factor objects for printing in a way that
/// makes the results easier to correlate for human eyes.
static int _sortOrder(Term a, Term b) {
if (a is Constant) {
// TODO: Should not happen now that we have broken out the coefficient?
if (b is Constant) return a.compareTo(b);
return -1;
}
if (b is Constant) {
// TODO: Should not happen now that we have broken out the coefficient?
return 1;
}
if (a is Unknown) {
if (b is Unknown) return a.name.compareTo(b.name);
return -1;
}
if (b is Unknown) {
return 1;
}
return 0;
}
List<Term> __sortedFactors;
List<Term> get _sortedFactors => __sortedFactors ??= [...factors]..sort(_sortOrder);
@override bool startsWithMinus() => coefficient.isNegative();
@override String toString() {
String ret;
if (coefficient == neg_one) {
ret = '-';
} else if (coefficient == one) {
ret = '';
} else {
ret = coefficient.toString();
}
bool prevWasUnknown = false;
String mul = '';
for (Term term in _sortedFactors) {
if (term is Unknown) {
if (!prevWasUnknown) {
ret += '$mul';
prevWasUnknown = true;
}
ret += '$term';
} else {
if (prevWasUnknown) {
prevWasUnknown = false;
}
ret += '$mul$term';
}
mul = '*';
}
return ret;
}
int get _nTerms => factors.length + (coefficient == one ? 0 : 1);
@override String toOutline() => 'Product($_nTerms factors)';
}
/// A Term object representing the division of 2 other Term objects.
///
/// TODO: This object could potentially be integrated into the Product object.
class Division extends Term {
final Term numerator;
final Term denominator;
Division(this.numerator, this.denominator);
/// Divide 2 Terms and return the simplified quotient.
static Term div(Term num, Term den) {
FactorAccumulator accumulator = FactorAccumulator();
accumulator.accumulate(num, false);
accumulator.accumulate(den, true);
return accumulator.getResult();
}
@override
bool negatesGracefully() {
return numerator.negatesGracefully() || denominator.negatesGracefully();
}
@override
Term operator -() {
return (numerator.negatesGracefully())
? Division(-numerator, denominator)
: Division(numerator, -denominator);
}
/// Determine if two Division Terms can be combined by virtue of having a common
/// denominator.
///
/// TODO: look for denominators that share a common factor or differ only by a coefficient.
@override
Term addDirect(Term other, isNegated) {
if (other is Division) {
if (this.denominator.equals(other.denominator)) {
Term newNumerator = this.numerator.addDirect(other.numerator, isNegated);
if (newNumerator == null) {
if (isNegated) {
newNumerator = this.numerator - other.numerator;
} else {
newNumerator = this.numerator + other.numerator;
}
}
if (newNumerator == zero || newNumerator == indeterminate ||
newNumerator == pos_infinity || newNumerator == neg_infinity)
{
return newNumerator;
}
return (newNumerator / this.denominator);
}
}
return null;
}
@override
bool equals(Term term) {
return term is Division &&
term.numerator.equals(this.numerator) &&
term.denominator.equals(this.denominator);
}
@override bool isNegative() => numerator.isNegative() != denominator.isNegative();
@override bool startsWithMinus() => numerator.startsWithMinus();
@override String toString() => '$numerator / $denominator';
@override String toOutline() => 'Division(${numerator.toOutline()} / ${denominator.toOutline()})';
}