Skip to content

Commit 1af1aac

Browse files
Multinomials (PolymerLabs#4804)
* multinomial * added tests * making constant readable again * reindent * added multivariate rearrangement tests * updated comment * pr comments: Co-authored-by: Ragav Sachdeva <[email protected]>
1 parent 5c6310d commit 1af1aac

File tree

2 files changed

+494
-223
lines changed

2 files changed

+494
-223
lines changed

src/runtime/refiner.ts

+187-98
Original file line numberDiff line numberDiff line change
@@ -1172,27 +1172,30 @@ export class SQLExtracter {
11721172
}
11731173
}
11741174

1175+
// A constant is represented by an empty Term object, where there is no indeterminate.
1176+
const CONSTANT = '{}';
1177+
11751178
export class Fraction {
1176-
num: Polynomial;
1177-
den: Polynomial;
1179+
num: Multinomial;
1180+
den: Multinomial;
11781181

1179-
constructor(n?: Polynomial, d?: Polynomial) {
1180-
this.num = n ? Polynomial.copyOf(n) : new Polynomial();
1181-
this.den = d ? Polynomial.copyOf(d) : new Polynomial([1]);
1182+
constructor(n?: Multinomial, d?: Multinomial) {
1183+
this.num = n ? Multinomial.copyOf(n) : new Multinomial();
1184+
this.den = d ? Multinomial.copyOf(d) : new Multinomial({[CONSTANT]: 1});
11821185
if (this.den.isZero()) {
11831186
throw new Error('Division by zero.');
11841187
}
11851188
this.reduce();
11861189
}
11871190

11881191
static add(a: Fraction, b: Fraction): Fraction {
1189-
const den = Polynomial.multiply(a.den, b.den);
1190-
const num = Polynomial.add(Polynomial.multiply(a.num, b.den), Polynomial.multiply(b.num, a.den));
1192+
const den = Multinomial.multiply(a.den, b.den);
1193+
const num = Multinomial.add(Multinomial.multiply(a.num, b.den), Multinomial.multiply(b.num, a.den));
11911194
return new Fraction(num, den);
11921195
}
11931196

11941197
static negate(a: Fraction): Fraction {
1195-
return new Fraction(Polynomial.negate(a.num), a.den);
1198+
return new Fraction(Multinomial.negate(a.num), a.den);
11961199
}
11971200

11981201
static subtract(a: Fraction, b: Fraction): Fraction {
@@ -1201,7 +1204,7 @@ export class Fraction {
12011204
}
12021205

12031206
static multiply(a: Fraction, b: Fraction): Fraction {
1204-
return new Fraction(Polynomial.multiply(a.num, b.num), Polynomial.multiply(a.den, b.den));
1207+
return new Fraction(Multinomial.multiply(a.num, b.num), Multinomial.multiply(a.den, b.den));
12051208
}
12061209

12071210
static divide(a: Fraction, b: Fraction): Fraction {
@@ -1211,12 +1214,21 @@ export class Fraction {
12111214

12121215
reduce() {
12131216
if (this.num.isZero()) {
1214-
this.den = new Polynomial([1]);
1217+
this.den = new Multinomial({[CONSTANT]: 1});
12151218
return;
12161219
}
1217-
if (this.num.isConstant() && this.den.isConstant()) {
1218-
this.num = new Polynomial([this.num.coeffs[0]/this.den.coeffs[0]]);
1219-
this.den = new Polynomial([1]);
1220+
if (this.num.isConstant() &&
1221+
this.den.isConstant() &&
1222+
Number.isInteger(this.num.terms[CONSTANT]) &&
1223+
Number.isInteger(this.den.terms[CONSTANT])
1224+
) {
1225+
const gcd = (a: number, b: number) => {
1226+
a = Math.abs(a); b = Math.abs(b);
1227+
return b === 0 ? a : gcd(b, a%b);
1228+
};
1229+
const g = gcd(this.num.terms[CONSTANT], this.den.terms[CONSTANT]);
1230+
this.num = new Multinomial({[CONSTANT]: this.num.terms[CONSTANT]/g});
1231+
this.den = new Multinomial({[CONSTANT]: this.den.terms[CONSTANT]/g});
12201232
return;
12211233
}
12221234
// TODO(ragdev): Fractions can be reduced further by factoring out the gcd of
@@ -1234,9 +1246,10 @@ export class Fraction {
12341246
const fn = Fraction.fromExpression(expr.expr);
12351247
return Fraction.updateGivenOp(expr.operator.op, [fn]);
12361248
} else if (expr instanceof FieldNamePrimitive && expr.evalType === Primitive.NUMBER) {
1237-
return new Fraction(new Polynomial([0, 1], expr.value));
1249+
const term = new Term({[expr.value]: 1});
1250+
return new Fraction(new Multinomial({[term.toKey()]: 1}));
12381251
} else if (expr instanceof NumberPrimitive) {
1239-
return new Fraction(new Polynomial([expr.value]));
1252+
return new Fraction(new Multinomial({[CONSTANT]: expr.value}));
12401253
}
12411254
throw new Error(`Cannot resolve expression: ${expr.toString()}`);
12421255
}
@@ -1254,143 +1267,219 @@ export class Fraction {
12541267
}
12551268
}
12561269

1257-
export class Polynomial {
1258-
private _coeffs: number[];
1259-
indeterminate?: string;
1270+
export class Term {
1271+
private _indeterminates: Dictionary<number>;
12601272

1261-
constructor(coeffs: number[] = [0], variable?: string) {
1262-
this.coeffs = coeffs;
1263-
this.indeterminate = variable;
1273+
constructor(indeterminates: Dictionary<number> = {}) {
1274+
this.indeterminates = indeterminates;
12641275
}
12651276

1266-
static copyOf(pn: Polynomial): Polynomial {
1267-
return new Polynomial(pn.coeffs, pn.indeterminate);
1277+
static copyOf(tm: Term): Term {
1278+
return new Term(tm.indeterminates);
12681279
}
12691280

1270-
get coeffs(): number[] {
1271-
while (this._coeffs.length >= 1 && this._coeffs[this._coeffs.length - 1] === 0) {
1272-
this._coeffs.pop();
1281+
get indeterminates(): Dictionary<number> {
1282+
for (const [indeterminate, power] of Object.entries(this._indeterminates)) {
1283+
if (power === 0) {
1284+
delete this._indeterminates[indeterminate];
1285+
}
12731286
}
1274-
if (this._coeffs.length === 0) {
1275-
this._coeffs.push(0);
1287+
return this._indeterminates;
1288+
}
1289+
1290+
set indeterminates(indtrms: Dictionary<number>) {
1291+
this._indeterminates = {...indtrms};
1292+
}
1293+
1294+
toKey(): string {
1295+
// sort the indeterminates
1296+
const ordered = {};
1297+
const unordered = this.indeterminates;
1298+
Object.keys(unordered).sort().forEach((key) => {
1299+
ordered[key] = unordered[key];
1300+
});
1301+
return JSON.stringify(ordered);
1302+
}
1303+
1304+
static fromKey(key: string): Term {
1305+
return new Term(JSON.parse(key));
1306+
}
1307+
1308+
static indeterminateToExpression(fn: string, pow: number): RefinementExpression {
1309+
if (pow <= 0) {
1310+
throw new Error('Must have positive power.');
12761311
}
1277-
return this._coeffs;
1312+
if (pow === 1) {
1313+
return new FieldNamePrimitive(fn, Primitive.NUMBER);
1314+
}
1315+
return new BinaryExpression(
1316+
Term.indeterminateToExpression(fn, 1),
1317+
Term.indeterminateToExpression(fn, pow - 1),
1318+
new RefinementOperator(Op.MUL));
12781319
}
12791320

1280-
set coeffs(cfs: number[]) {
1281-
this._coeffs = [...cfs];
1321+
// assumes that term is not a constant i.e. not {}
1322+
toExpression(): RefinementExpression {
1323+
if (Object.keys(this.indeterminates).length === 0) {
1324+
throw new Error('Cannot convert an empty term to expression');
1325+
}
1326+
let expr = null;
1327+
for (const [indeterminate, power] of Object.entries(this.indeterminates)) {
1328+
const indtrExpr = Term.indeterminateToExpression(indeterminate, power);
1329+
expr = expr ? new BinaryExpression(expr, indtrExpr, new RefinementOperator(Op.MUL)) : indtrExpr;
1330+
}
1331+
return expr;
12821332
}
1333+
}
12831334

1284-
degree(): number {
1285-
return this.coeffs.length - 1;
1335+
export class Multinomial {
1336+
private _terms: Dictionary<number>;
1337+
1338+
constructor(terms: Dictionary<number> = {}) {
1339+
this.terms = terms;
1340+
}
1341+
1342+
static copyOf(mn: Multinomial): Multinomial {
1343+
return new Multinomial(mn.terms);
12861344
}
12871345

1288-
static assertCompatibility(a: Polynomial, b: Polynomial) {
1289-
if (a.indeterminate && b.indeterminate && a.indeterminate !== b.indeterminate) {
1290-
throw new Error('Incompatible polynomials');
1346+
get terms(): Dictionary<number> {
1347+
for (const [term, coeff] of Object.entries(this._terms)) {
1348+
if (coeff === 0) {
1349+
delete this._terms[term];
1350+
}
12911351
}
1352+
const ordered = {};
1353+
const unordered = this._terms;
1354+
Object.keys(unordered).sort().forEach((key) => {
1355+
ordered[key] = unordered[key];
1356+
});
1357+
this._terms = ordered;
1358+
return this._terms;
1359+
}
1360+
1361+
set terms(tms: Dictionary<number>) {
1362+
this._terms = {...tms};
12921363
}
12931364

1294-
static add(a: Polynomial, b: Polynomial): Polynomial {
1295-
Polynomial.assertCompatibility(a, b);
1296-
const sum = a.degree() > b.degree() ? Polynomial.copyOf(a) : Polynomial.copyOf(b);
1297-
const other = a.degree() > b.degree() ? b : a;
1298-
for (const [i, coeff] of other.coeffs.entries()) {
1299-
sum.coeffs[i] += coeff;
1365+
static add(a: Multinomial, b: Multinomial): Multinomial {
1366+
const sum = Multinomial.copyOf(a);
1367+
for (const [term, coeff] of Object.entries(b.terms)) {
1368+
const val = (sum.terms[term] || 0) + coeff;
1369+
sum.terms[term] = val;
13001370
}
13011371
return sum;
13021372
}
13031373

1304-
static subtract(a: Polynomial, b: Polynomial): Polynomial {
1305-
Polynomial.assertCompatibility(a, b);
1306-
return Polynomial.add(a, Polynomial.negate(b));
1374+
static subtract(a: Multinomial, b: Multinomial): Multinomial {
1375+
return Multinomial.add(a, Multinomial.negate(b));
13071376
}
13081377

1309-
static negate(a: Polynomial): Polynomial {
1310-
return new Polynomial(a.coeffs.map(co => -co), a.indeterminate);
1378+
static negate(a: Multinomial): Multinomial {
1379+
const neg = Multinomial.copyOf(a);
1380+
for (const [term, coeff] of Object.entries(neg.terms)) {
1381+
neg.terms[term] = -coeff;
1382+
}
1383+
return neg;
13111384
}
13121385

1313-
static multiply(a: Polynomial, b: Polynomial): Polynomial {
1314-
Polynomial.assertCompatibility(a, b);
1315-
const deg = a.degree() + b.degree();
1316-
const coeffs = new Array(deg+1).fill(0);
1317-
for (let i = 0; i < coeffs.length; i += 1) {
1318-
for (let j = 0; j <= i; j += 1) {
1319-
if (j <= a.degree() && (i-j) <= b.degree()) {
1320-
coeffs[i] += a.coeffs[j]*b.coeffs[i-j];
1386+
static multiply(a: Multinomial, b: Multinomial): Multinomial {
1387+
const prod = new Multinomial();
1388+
for (const [aKey, acoeff] of Object.entries(a.terms)) {
1389+
for (const [bKey, bcoeff] of Object.entries(b.terms)) {
1390+
const tprod = Term.fromKey(aKey);
1391+
const bterm = Term.fromKey(bKey);
1392+
for (const [indeterminate, power] of Object.entries(bterm.indeterminates)) {
1393+
const val = power + (tprod.indeterminates[indeterminate] || 0);
1394+
tprod.indeterminates[indeterminate] = val;
13211395
}
1396+
const val = acoeff*bcoeff + (prod.terms[tprod.toKey()] || 0);
1397+
prod.terms[tprod.toKey()] = val;
13221398
}
13231399
}
1324-
return new Polynomial(coeffs, a.indeterminate || b.indeterminate);
1400+
return prod;
13251401
}
13261402

13271403
isZero(): boolean {
1328-
return this.isConstant() && this.coeffs[0] === 0;
1404+
return Object.keys(this.terms).length === 0;
13291405
}
13301406

13311407
isConstant(): boolean {
1332-
return this.degree() === 0;
1408+
return this.isZero() || (Object.keys(this.terms).length === 1 && this.terms.hasOwnProperty(CONSTANT));
13331409
}
13341410

1335-
static inderminateToExpression(pow: number, fn: string): RefinementExpression {
1336-
if (pow < 0) {
1337-
throw new Error('Must have non-negative power.');
1338-
}
1339-
if (pow === 0) {
1340-
return new NumberPrimitive(1);
1411+
getIndeterminates(): Set<string> {
1412+
const indeterminates = new Set<string>();
1413+
for (const tKey of Object.keys(this.terms)) {
1414+
const term = Term.fromKey(tKey);
1415+
for (const indeterminate of Object.keys(term.indeterminates)) {
1416+
indeterminates.add(indeterminate);
1417+
}
13411418
}
1342-
if (pow === 1) {
1343-
return new FieldNamePrimitive(fn, Primitive.NUMBER);
1419+
return indeterminates;
1420+
}
1421+
1422+
isUnivariate(): boolean {
1423+
return this.getIndeterminates().size === 1;
1424+
}
1425+
1426+
degree(): number {
1427+
let degree = 0;
1428+
for (const tKey of Object.keys(this.terms)) {
1429+
const term = Term.fromKey(tKey);
1430+
let sum = 0;
1431+
for (const power of Object.values(term.indeterminates)) {
1432+
sum += power;
1433+
}
1434+
degree = sum > degree ? sum : degree;
13441435
}
1345-
return new BinaryExpression(
1346-
Polynomial.inderminateToExpression(1, fn),
1347-
Polynomial.inderminateToExpression(pow-1, fn),
1348-
new RefinementOperator(Op.MUL));
1436+
return degree;
13491437
}
13501438

1351-
// returns ax^n + bx^n-1 + ... c <op> 0
1439+
// returns <multinomial> <op> CONSTANT
13521440
toExpression(op: Op): RefinementExpression {
1353-
if (this.degree() === 0) {
1441+
if (this.isConstant()) {
13541442
return new BinaryExpression(
1355-
new NumberPrimitive(this.coeffs[0]),
1443+
new NumberPrimitive(this.isZero() ? 0 : this.terms[CONSTANT]),
13561444
new NumberPrimitive(0),
13571445
new RefinementOperator(op));
13581446
}
1359-
if (!this.indeterminate) {
1360-
throw new Error('FieldName not found!');
1361-
}
1362-
if (this.degree() === 1) {
1363-
// ax+b <op1> 0 => x <op2> -b/a
1447+
if (this.isUnivariate() && this.degree() === 1) {
13641448
const operator = new RefinementOperator(op);
1365-
if (this.coeffs[1] < 0) {
1449+
const indeterminate = this.getIndeterminates().values().next().value;
1450+
// TODO(ragdev): Implement a neater way to get the leading coefficient
1451+
const leadingCoeff = this.terms[`{"${indeterminate}":1}`];
1452+
const cnst = this.terms[CONSTANT] || 0;
1453+
if (leadingCoeff < 0) {
13661454
operator.flip();
13671455
}
13681456
return new BinaryExpression(
1369-
new FieldNamePrimitive(this.indeterminate, Primitive.NUMBER),
1370-
new NumberPrimitive(-this.coeffs[0]/this.coeffs[1]),
1457+
new FieldNamePrimitive(indeterminate, Primitive.NUMBER),
1458+
new NumberPrimitive(-cnst/leadingCoeff),
13711459
operator);
13721460
}
1373-
let expr = null;
1374-
for (let i = this.coeffs.length - 1; i >= 0; i -= 1) {
1375-
if (this.coeffs[i] === 0) {
1376-
continue;
1461+
const termToExpression = (tKey: string, tCoeff: number) => {
1462+
const termExpr = Term.fromKey(tKey).toExpression();
1463+
if (tCoeff === 1) {
1464+
return termExpr;
13771465
}
1378-
let termExpr = null;
1379-
if (i > 0) {
1380-
termExpr = Polynomial.inderminateToExpression(i, this.indeterminate);
1381-
if (Math.abs(this.coeffs[i]) !== 1) {
1382-
termExpr = new BinaryExpression(
1383-
new NumberPrimitive(this.coeffs[i]),
1384-
termExpr,
1385-
new RefinementOperator(Op.MUL)
1386-
);
1387-
}
1466+
return new BinaryExpression(
1467+
new NumberPrimitive(tCoeff),
1468+
termExpr,
1469+
new RefinementOperator(Op.MUL)
1470+
);
1471+
};
1472+
let expr = null;
1473+
let cnst = 0;
1474+
for (const [tKey, tCoeff] of Object.entries(this.terms)) {
1475+
if (tKey === CONSTANT) {
1476+
cnst = tCoeff;
13881477
} else {
1389-
termExpr = new NumberPrimitive(this.coeffs[0]);
1478+
const termExpr = termToExpression(tKey, tCoeff);
1479+
expr = expr ? new BinaryExpression(expr, termExpr, new RefinementOperator(Op.ADD)) : termExpr;
13901480
}
1391-
expr = expr ? new BinaryExpression(expr, termExpr, new RefinementOperator(Op.ADD)) : termExpr;
13921481
}
1393-
return new BinaryExpression(expr, new NumberPrimitive(0), new RefinementOperator(op));
1482+
return new BinaryExpression(expr, new NumberPrimitive(-cnst), new RefinementOperator(op));
13941483
}
13951484
}
13961485

0 commit comments

Comments
 (0)