From 6063c99dbd3a3ab4dab8c2217a138389a18ea766 Mon Sep 17 00:00:00 2001 From: Joe Pea Date: Fri, 8 Sep 2023 12:40:12 -0700 Subject: [PATCH] Bite the bullet and commit build outputs so that they are as accessible as possible for people that may not be able to run the build in some OS or setup. An upside of this is that examples and documentation can be served out of the box without having to build them. Remove package.json 'prepare' scripts because they are no longer needed, as installing from git will already include build outputs. --- .gitignore | 2 - es/constraint.d.ts | 62 ++ es/constraint.d.ts.map | 1 + es/constraint.js | 86 +++ es/expression.d.ts | 74 ++ es/expression.d.ts.map | 1 + es/expression.js | 163 +++++ es/kiwi.d.ts | 6 + es/kiwi.d.ts.map | 1 + es/kiwi.js | 5 + es/maptype.d.ts | 91 +++ es/maptype.d.ts.map | 1 + es/maptype.js | 143 ++++ es/solver.d.ts | 248 +++++++ es/solver.d.ts.map | 1 + es/solver.js | 944 ++++++++++++++++++++++++ es/strength.d.ts | 37 + es/strength.d.ts.map | 1 + es/strength.js | 49 ++ es/variable.d.ts | 89 +++ es/variable.d.ts.map | 1 + es/variable.js | 126 ++++ lib/kiwi.js | 1558 ++++++++++++++++++++++++++++++++++++++++ lib/kiwi.min.js | 9 + package.json | 3 +- 25 files changed, 3698 insertions(+), 4 deletions(-) create mode 100644 es/constraint.d.ts create mode 100644 es/constraint.d.ts.map create mode 100644 es/constraint.js create mode 100644 es/expression.d.ts create mode 100644 es/expression.d.ts.map create mode 100644 es/expression.js create mode 100644 es/kiwi.d.ts create mode 100644 es/kiwi.d.ts.map create mode 100644 es/kiwi.js create mode 100644 es/maptype.d.ts create mode 100644 es/maptype.d.ts.map create mode 100644 es/maptype.js create mode 100644 es/solver.d.ts create mode 100644 es/solver.d.ts.map create mode 100644 es/solver.js create mode 100644 es/strength.d.ts create mode 100644 es/strength.d.ts.map create mode 100644 es/strength.js create mode 100644 es/variable.d.ts create mode 100644 es/variable.d.ts.map create mode 100644 es/variable.js create mode 100644 lib/kiwi.js create mode 100644 lib/kiwi.min.js diff --git a/.gitignore b/.gitignore index cdaa99c..1ddc7dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,7 @@ .vscode node_modules tmp/ -es/ /coverage -lib/ *.lock *lock.json diff --git a/es/constraint.d.ts b/es/constraint.d.ts new file mode 100644 index 0000000..c1d5bd8 --- /dev/null +++ b/es/constraint.d.ts @@ -0,0 +1,62 @@ +import { Expression } from './expression.js'; +import { Variable } from './variable.js'; +/** + * An enum defining the linear constraint operators. + * + * |Value|Operator|Description| + * |----|-----|-----| + * |`Le`|<=|Less than equal| + * |`Ge`|>=|Greater than equal| + * |`Eq`|==|Equal| + * + * @enum {Number} + */ +export declare enum Operator { + Le = 0, + Ge = 1, + Eq = 2 +} +/** + * A linear constraint equation. + * + * A constraint equation is composed of an expression, an operator, + * and a strength. The RHS of the equation is implicitly zero. + * + * @class + * @param {Expression} expression The constraint expression (LHS). + * @param {Operator} operator The equation operator. + * @param {Expression} [rhs] Right hand side of the expression. + * @param {Number} [strength=Strength.required] The strength of the constraint. + */ +export declare class Constraint { + constructor(expression: Expression | Variable, operator: Operator, rhs?: Expression | Variable | number, strength?: number); + /** + * Returns the unique id number of the constraint. + * @private + */ + id(): number; + /** + * Returns the expression of the constraint. + * + * @return {Expression} expression + */ + expression(): Expression; + /** + * Returns the relational operator of the constraint. + * + * @return {Operator} linear constraint operator + */ + op(): Operator; + /** + * Returns the strength of the constraint. + * + * @return {Number} strength + */ + strength(): number; + toString(): string; + private _expression; + private _operator; + private _strength; + private _id; +} +//# sourceMappingURL=constraint.d.ts.map \ No newline at end of file diff --git a/es/constraint.d.ts.map b/es/constraint.d.ts.map new file mode 100644 index 0000000..7b000d9 --- /dev/null +++ b/es/constraint.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"constraint.d.ts","sourceRoot":"","sources":["../src/constraint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAA;AAE1C,OAAO,EAAC,QAAQ,EAAC,MAAM,eAAe,CAAA;AAEtC;;;;;;;;;;GAUG;AACH,oBAAY,QAAQ;IACnB,EAAE,IAAA;IACF,EAAE,IAAA;IACF,EAAE,IAAA;CACF;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,UAAU;gBAErB,UAAU,EAAE,UAAU,GAAG,QAAQ,EACjC,QAAQ,EAAE,QAAQ,EAClB,GAAG,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,MAAM,EACpC,QAAQ,GAAE,MAA0B;IAWrC;;;OAGG;IACI,EAAE,IAAI,MAAM;IAInB;;;;OAIG;IACI,UAAU,IAAI,UAAU;IAI/B;;;;OAIG;IACI,EAAE,IAAI,QAAQ;IAIrB;;;;OAIG;IACI,QAAQ,IAAI,MAAM;IAIlB,QAAQ,IAAI,MAAM;IAMzB,OAAO,CAAC,WAAW,CAAY;IAC/B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,GAAG,CAAiB;CAC5B"} \ No newline at end of file diff --git a/es/constraint.js b/es/constraint.js new file mode 100644 index 0000000..b12d73c --- /dev/null +++ b/es/constraint.js @@ -0,0 +1,86 @@ +import { Expression } from './expression.js'; +import { Strength } from './strength.js'; +/** + * An enum defining the linear constraint operators. + * + * |Value|Operator|Description| + * |----|-----|-----| + * |`Le`|<=|Less than equal| + * |`Ge`|>=|Greater than equal| + * |`Eq`|==|Equal| + * + * @enum {Number} + */ +export var Operator; +(function (Operator) { + Operator[Operator["Le"] = 0] = "Le"; + Operator[Operator["Ge"] = 1] = "Ge"; + Operator[Operator["Eq"] = 2] = "Eq"; +})(Operator || (Operator = {})); +/** + * A linear constraint equation. + * + * A constraint equation is composed of an expression, an operator, + * and a strength. The RHS of the equation is implicitly zero. + * + * @class + * @param {Expression} expression The constraint expression (LHS). + * @param {Operator} operator The equation operator. + * @param {Expression} [rhs] Right hand side of the expression. + * @param {Number} [strength=Strength.required] The strength of the constraint. + */ +var Constraint = /** @class */ (function () { + function Constraint(expression, operator, rhs, strength) { + if (strength === void 0) { strength = Strength.required; } + this._id = CnId++; + this._operator = operator; + this._strength = Strength.clip(strength); + if (rhs === undefined && expression instanceof Expression) { + this._expression = expression; + } + else { + this._expression = expression.minus(rhs); + } + } + /** + * Returns the unique id number of the constraint. + * @private + */ + Constraint.prototype.id = function () { + return this._id; + }; + /** + * Returns the expression of the constraint. + * + * @return {Expression} expression + */ + Constraint.prototype.expression = function () { + return this._expression; + }; + /** + * Returns the relational operator of the constraint. + * + * @return {Operator} linear constraint operator + */ + Constraint.prototype.op = function () { + return this._operator; + }; + /** + * Returns the strength of the constraint. + * + * @return {Number} strength + */ + Constraint.prototype.strength = function () { + return this._strength; + }; + Constraint.prototype.toString = function () { + return (this._expression.toString() + ' ' + ['<=', '>=', '='][this._operator] + ' 0 (' + this._strength.toString() + ')'); + }; + return Constraint; +}()); +export { Constraint }; +/** + * The internal constraint id counter. + * @private + */ +var CnId = 0; diff --git a/es/expression.d.ts b/es/expression.d.ts new file mode 100644 index 0000000..d549554 --- /dev/null +++ b/es/expression.d.ts @@ -0,0 +1,74 @@ +import { IMap } from './maptype.js'; +import { Variable } from './variable.js'; +/** + * An expression of variable terms and a constant. + * + * The constructor accepts an arbitrary number of parameters, + * each of which must be one of the following types: + * - number + * - Variable + * - Expression + * - 2-tuple of [number, Variable|Expression] + * + * The parameters are summed. The tuples are multiplied. + * + * @class + * @param {...(number|Variable|Expression|Array)} args + */ +export declare class Expression { + constructor(...args: any[]); + /** + * Returns the mapping of terms in the expression. + * + * This *must* be treated as const. + * @private + */ + terms(): IMap; + /** + * Returns the constant of the expression. + * @private + */ + constant(): number; + /** + * Returns the computed value of the expression. + * + * @private + * @return {Number} computed value of the expression + */ + value(): number; + /** + * Creates a new Expression by adding a number, variable or expression + * to the expression. + * + * @param {Number|Variable|Expression} value Value to add. + * @return {Expression} expression + */ + plus(value: number | Variable | Expression): Expression; + /** + * Creates a new Expression by substracting a number, variable or expression + * from the expression. + * + * @param {Number|Variable|Expression} value Value to substract. + * @return {Expression} expression + */ + minus(value: number | Variable | Expression): Expression; + /** + * Creates a new Expression by multiplying with a fixed number. + * + * @param {Number} coefficient Coefficient to multiply with. + * @return {Expression} expression + */ + multiply(coefficient: number): Expression; + /** + * Creates a new Expression by dividing with a fixed number. + * + * @param {Number} coefficient Coefficient to divide by. + * @return {Expression} expression + */ + divide(coefficient: number): Expression; + isConstant(): boolean; + toString(): string; + private _terms; + private _constant; +} +//# sourceMappingURL=expression.d.ts.map \ No newline at end of file diff --git a/es/expression.d.ts.map b/es/expression.d.ts.map new file mode 100644 index 0000000..490b951 --- /dev/null +++ b/es/expression.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"expression.d.ts","sourceRoot":"","sources":["../src/expression.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,IAAI,EAAC,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAC,QAAQ,EAAC,MAAM,eAAe,CAAA;AAEtC;;;;;;;;;;;;;;GAcG;AACH,qBAAa,UAAU;gBACV,GAAG,IAAI,EAAE,GAAG,EAAE;IAO1B;;;;;OAKG;IACI,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC;IAItC;;;OAGG;IACI,QAAQ,IAAI,MAAM;IAIzB;;;;;OAKG;IACI,KAAK,IAAI,MAAM;IAStB;;;;;;OAMG;IACI,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU;IAI9D;;;;;;OAMG;IACI,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU;IAI/D;;;;;OAKG;IACI,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU;IAIhD;;;;;OAKG;IACI,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU;IAIvC,UAAU,IAAI,OAAO;IAIrB,QAAQ,IAAI,MAAM;IAgBzB,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,SAAS,CAAQ;CACzB"} \ No newline at end of file diff --git a/es/expression.js b/es/expression.js new file mode 100644 index 0000000..725bd4c --- /dev/null +++ b/es/expression.js @@ -0,0 +1,163 @@ +import { createMap } from './maptype.js'; +import { Variable } from './variable.js'; +/** + * An expression of variable terms and a constant. + * + * The constructor accepts an arbitrary number of parameters, + * each of which must be one of the following types: + * - number + * - Variable + * - Expression + * - 2-tuple of [number, Variable|Expression] + * + * The parameters are summed. The tuples are multiplied. + * + * @class + * @param {...(number|Variable|Expression|Array)} args + */ +var Expression = /** @class */ (function () { + function Expression() { + var parsed = parseArgs(arguments); + this._terms = parsed.terms; + this._constant = parsed.constant; + } + /** + * Returns the mapping of terms in the expression. + * + * This *must* be treated as const. + * @private + */ + Expression.prototype.terms = function () { + return this._terms; + }; + /** + * Returns the constant of the expression. + * @private + */ + Expression.prototype.constant = function () { + return this._constant; + }; + /** + * Returns the computed value of the expression. + * + * @private + * @return {Number} computed value of the expression + */ + Expression.prototype.value = function () { + var result = this._constant; + for (var i = 0, n = this._terms.size(); i < n; i++) { + var pair = this._terms.itemAt(i); + result += pair.first.value() * pair.second; + } + return result; + }; + /** + * Creates a new Expression by adding a number, variable or expression + * to the expression. + * + * @param {Number|Variable|Expression} value Value to add. + * @return {Expression} expression + */ + Expression.prototype.plus = function (value) { + return new Expression(this, value); + }; + /** + * Creates a new Expression by substracting a number, variable or expression + * from the expression. + * + * @param {Number|Variable|Expression} value Value to substract. + * @return {Expression} expression + */ + Expression.prototype.minus = function (value) { + return new Expression(this, typeof value === 'number' ? -value : [-1, value]); + }; + /** + * Creates a new Expression by multiplying with a fixed number. + * + * @param {Number} coefficient Coefficient to multiply with. + * @return {Expression} expression + */ + Expression.prototype.multiply = function (coefficient) { + return new Expression([coefficient, this]); + }; + /** + * Creates a new Expression by dividing with a fixed number. + * + * @param {Number} coefficient Coefficient to divide by. + * @return {Expression} expression + */ + Expression.prototype.divide = function (coefficient) { + return new Expression([1 / coefficient, this]); + }; + Expression.prototype.isConstant = function () { + return this._terms.size() == 0; + }; + Expression.prototype.toString = function () { + var result = this._terms.array + .map(function (pair) { + return pair.second + '*' + pair.first.toString(); + }) + .join(' + '); + if (!this.isConstant() && this._constant !== 0) { + result += ' + '; + } + result += this._constant; + return result; + }; + return Expression; +}()); +export { Expression }; +/** + * An internal argument parsing function. + * @private + */ +function parseArgs(args) { + var constant = 0.0; + var factory = function () { return 0.0; }; + var terms = createMap(); + for (var i = 0, n = args.length; i < n; ++i) { + var item = args[i]; + if (typeof item === 'number') { + constant += item; + } + else if (item instanceof Variable) { + terms.setDefault(item, factory).second += 1.0; + } + else if (item instanceof Expression) { + constant += item.constant(); + var terms2 = item.terms(); + for (var j = 0, k = terms2.size(); j < k; j++) { + var termPair = terms2.itemAt(j); + terms.setDefault(termPair.first, factory).second += termPair.second; + } + } + else if (item instanceof Array) { + if (item.length !== 2) { + throw new Error('array must have length 2'); + } + var value = item[0]; + var value2 = item[1]; + if (typeof value !== 'number') { + throw new Error('array item 0 must be a number'); + } + if (value2 instanceof Variable) { + terms.setDefault(value2, factory).second += value; + } + else if (value2 instanceof Expression) { + constant += value2.constant() * value; + var terms2 = value2.terms(); + for (var j = 0, k = terms2.size(); j < k; j++) { + var termPair = terms2.itemAt(j); + terms.setDefault(termPair.first, factory).second += termPair.second * value; + } + } + else { + throw new Error('array item 1 must be a variable or expression'); + } + } + else { + throw new Error('invalid Expression argument: ' + item); + } + } + return { terms: terms, constant: constant }; +} diff --git a/es/kiwi.d.ts b/es/kiwi.d.ts new file mode 100644 index 0000000..bc107e4 --- /dev/null +++ b/es/kiwi.d.ts @@ -0,0 +1,6 @@ +export * from './constraint.js'; +export * from './expression.js'; +export * from './solver.js'; +export * from './strength.js'; +export * from './variable.js'; +//# sourceMappingURL=kiwi.d.ts.map \ No newline at end of file diff --git a/es/kiwi.d.ts.map b/es/kiwi.d.ts.map new file mode 100644 index 0000000..2f59984 --- /dev/null +++ b/es/kiwi.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"kiwi.d.ts","sourceRoot":"","sources":["../src/kiwi.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA"} \ No newline at end of file diff --git a/es/kiwi.js b/es/kiwi.js new file mode 100644 index 0000000..415eeef --- /dev/null +++ b/es/kiwi.js @@ -0,0 +1,5 @@ +export * from './constraint.js'; +export * from './expression.js'; +export * from './solver.js'; +export * from './strength.js'; +export * from './variable.js'; diff --git a/es/maptype.d.ts b/es/maptype.d.ts new file mode 100644 index 0000000..d1fef14 --- /dev/null +++ b/es/maptype.d.ts @@ -0,0 +1,91 @@ +export interface IMap extends IndexedMap { +} +export declare function createMap(): IMap; +declare class IndexedMap { + index: { + [id: number]: number; + }; + array: Pair[]; + /** + * Returns the number of items in the array. + */ + size(): number; + /** + * Returns true if the array is empty. + */ + empty(): boolean; + /** + * Returns the item at the given array index. + * + * @param index The integer index of the desired item. + */ + itemAt(index: number): Pair; + /** + * Returns true if the key is in the array, false otherwise. + * + * @param key The key to locate in the array. + */ + contains(key: T): boolean; + /** + * Returns the pair associated with the given key, or undefined. + * + * @param key The key to locate in the array. + */ + find(key: T): Pair; + /** + * Returns the pair associated with the key if it exists. + * + * If the key does not exist, a new pair will be created and + * inserted using the value created by the given factory. + * + * @param key The key to locate in the array. + * @param factory The function which creates the default value. + */ + setDefault(key: T, factory: () => U): Pair; + /** + * Insert the pair into the array and return the pair. + * + * This will overwrite any existing entry in the array. + * + * @param key The key portion of the pair. + * @param value The value portion of the pair. + */ + insert(key: T, value: U): Pair; + /** + * Removes and returns the pair for the given key, or undefined. + * + * @param key The key to remove from the map. + */ + erase(key: T): Pair; + /** + * Create a copy of this associative array. + */ + copy(): IndexedMap; +} +/** + * A class which defines a generic pair object. + * @private + */ +declare class Pair { + first: T; + second: U; + /** + * Construct a new Pair object. + * + * @param first The first item of the pair. + * @param second The second item of the pair. + */ + constructor(first: T, second: U); + /** + * Create a copy of the pair. + */ + copy(): Pair; +} +export {}; +//# sourceMappingURL=maptype.d.ts.map \ No newline at end of file diff --git a/es/maptype.d.ts.map b/es/maptype.d.ts.map new file mode 100644 index 0000000..b331ed4 --- /dev/null +++ b/es/maptype.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"maptype.d.ts","sourceRoot":"","sources":["../src/maptype.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,IAAI,CAAC,CAAC,SAAS;IAAC,EAAE,IAAI,MAAM,CAAA;CAAC,EAAE,CAAC,CAAE,SAAQ,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC;CAAG;AAE9E,wBAAgB,SAAS,CAAC,CAAC,SAAS;IAAC,EAAE,IAAI,MAAM,CAAA;CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAEnE;AAED,cAAM,UAAU,CAAC,CAAC,SAAS;IAAC,EAAE,IAAI,MAAM,CAAA;CAAC,EAAE,CAAC;IACpC,KAAK;;MAA2C;IAChD,KAAK,eAA0B;IAEtC;;OAEG;IACI,IAAI,IAAI,MAAM;IAIrB;;OAEG;IACI,KAAK,IAAI,OAAO;IAIvB;;;;OAIG;IACI,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IAIxC;;;;OAIG;IACI,QAAQ,CAAC,GAAG,EAAE,CAAC;IAItB;;;;OAIG;IACI,IAAI,CAAC,GAAG,EAAE,CAAC;IAKlB;;;;;;;;OAQG;IACI,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IAYvD;;;;;;;OAOG;IACI,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IAY3C;;;;OAIG;IACI,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IAehC;;OAEG;IACI,IAAI,IAAI,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC;CAS/B;AAED;;;GAGG;AAEH,cAAM,IAAI,CAAC,CAAC,EAAE,CAAC;IAOK,KAAK,EAAE,CAAC;IAAS,MAAM,EAAE,CAAC;IAN7C;;;;;OAKG;gBACgB,KAAK,EAAE,CAAC,EAAS,MAAM,EAAE,CAAC;IAE7C;;OAEG;IACI,IAAI;CAGX"} \ No newline at end of file diff --git a/es/maptype.js b/es/maptype.js new file mode 100644 index 0000000..e9b58d8 --- /dev/null +++ b/es/maptype.js @@ -0,0 +1,143 @@ +export function createMap() { + return new IndexedMap(); +} +var IndexedMap = /** @class */ (function () { + function IndexedMap() { + this.index = {}; + this.array = []; + } + /** + * Returns the number of items in the array. + */ + IndexedMap.prototype.size = function () { + return this.array.length; + }; + /** + * Returns true if the array is empty. + */ + IndexedMap.prototype.empty = function () { + return this.array.length === 0; + }; + /** + * Returns the item at the given array index. + * + * @param index The integer index of the desired item. + */ + IndexedMap.prototype.itemAt = function (index) { + return this.array[index]; + }; + /** + * Returns true if the key is in the array, false otherwise. + * + * @param key The key to locate in the array. + */ + IndexedMap.prototype.contains = function (key) { + return this.index[key.id()] !== undefined; + }; + /** + * Returns the pair associated with the given key, or undefined. + * + * @param key The key to locate in the array. + */ + IndexedMap.prototype.find = function (key) { + var i = this.index[key.id()]; + return i === undefined ? undefined : this.array[i]; + }; + /** + * Returns the pair associated with the key if it exists. + * + * If the key does not exist, a new pair will be created and + * inserted using the value created by the given factory. + * + * @param key The key to locate in the array. + * @param factory The function which creates the default value. + */ + IndexedMap.prototype.setDefault = function (key, factory) { + var i = this.index[key.id()]; + if (i === undefined) { + var pair = new Pair(key, factory()); + this.index[key.id()] = this.array.length; + this.array.push(pair); + return pair; + } + else { + return this.array[i]; + } + }; + /** + * Insert the pair into the array and return the pair. + * + * This will overwrite any existing entry in the array. + * + * @param key The key portion of the pair. + * @param value The value portion of the pair. + */ + IndexedMap.prototype.insert = function (key, value) { + var pair = new Pair(key, value); + var i = this.index[key.id()]; + if (i === undefined) { + this.index[key.id()] = this.array.length; + this.array.push(pair); + } + else { + this.array[i] = pair; + } + return pair; + }; + /** + * Removes and returns the pair for the given key, or undefined. + * + * @param key The key to remove from the map. + */ + IndexedMap.prototype.erase = function (key) { + var i = this.index[key.id()]; + if (i === undefined) { + return undefined; + } + this.index[key.id()] = undefined; + var pair = this.array[i]; + var last = this.array.pop(); + if (pair !== last) { + this.array[i] = last; + this.index[last.first.id()] = i; + } + return pair; + }; + /** + * Create a copy of this associative array. + */ + IndexedMap.prototype.copy = function () { + var copy = new IndexedMap(); + for (var i = 0; i < this.array.length; i++) { + var pair = this.array[i].copy(); + copy.array[i] = pair; + copy.index[pair.first.id()] = i; + } + return copy; + }; + return IndexedMap; +}()); +/** + * A class which defines a generic pair object. + * @private + */ +// tslint:disable: max-classes-per-file +var Pair = /** @class */ (function () { + /** + * Construct a new Pair object. + * + * @param first The first item of the pair. + * @param second The second item of the pair. + */ + function Pair(first, second) { + this.first = first; + this.second = second; + } + /** + * Create a copy of the pair. + */ + Pair.prototype.copy = function () { + return new Pair(this.first, this.second); + }; + return Pair; +}()); diff --git a/es/solver.d.ts b/es/solver.d.ts new file mode 100644 index 0000000..052e9f6 --- /dev/null +++ b/es/solver.d.ts @@ -0,0 +1,248 @@ +import { Constraint, Operator } from './constraint.js'; +import { Expression } from './expression.js'; +import { Variable } from './variable.js'; +/** + * The constraint solver class. + * + * @class + */ +export declare class Solver { + /** + * @type {number} - The max number of solver iterations before an error + * is thrown, in order to prevent infinite iteration. Default: `10,000`. + */ + maxIterations: number; + /** + * Construct a new Solver. + */ + constructor(); + /** + * Creates and add a constraint to the solver. + * + * @param {Expression|Variable} lhs Left hand side of the expression + * @param {Operator} operator Operator + * @param {Expression|Variable|Number} rhs Right hand side of the expression + * @param {Number} [strength=Strength.required] Strength + */ + createConstraint(lhs: Expression | Variable, operator: Operator, rhs: Expression | Variable | number, strength?: number): Constraint; + /** + * Add a constraint to the solver. + * + * @param {Constraint} constraint Constraint to add to the solver + */ + addConstraint(constraint: Constraint): void; + /** + * Remove a constraint from the solver. + * + * @param {Constraint} constraint Constraint to remove from the solver + */ + removeConstraint(constraint: Constraint): void; + /** + * Test whether the solver contains the constraint. + * + * @param {Constraint} constraint Constraint to test for + * @return {Bool} true or false + */ + hasConstraint(constraint: Constraint): boolean; + /** + * Add an edit variable to the solver. + * + * @param {Variable} variable Edit variable to add to the solver + * @param {Number} strength Strength, should be less than `Strength.required` + */ + addEditVariable(variable: Variable, strength: number): void; + /** + * Remove an edit variable from the solver. + * + * @param {Variable} variable Edit variable to remove from the solver + */ + removeEditVariable(variable: Variable): void; + /** + * Test whether the solver contains the edit variable. + * + * @param {Variable} variable Edit variable to test for + * @return {Bool} true or false + */ + hasEditVariable(variable: Variable): boolean; + /** + * Suggest the value of an edit variable. + * + * @param {Variable} variable Edit variable to suggest a value for + * @param {Number} value Suggested value + */ + suggestValue(variable: Variable, value: number): void; + /** + * Update the values of the variables. + */ + updateVariables(): void; + /** + * Get the symbol for the given variable. + * + * If a symbol does not exist for the variable, one will be created. + * @private + */ + private _getVarSymbol; + /** + * Create a new Row object for the given constraint. + * + * The terms in the constraint will be converted to cells in the row. + * Any term in the constraint with a coefficient of zero is ignored. + * This method uses the `_getVarSymbol` method to get the symbol for + * the variables added to the row. If the symbol for a given cell + * variable is basic, the cell variable will be substituted with the + * basic row. + * + * The necessary slack and error variables will be added to the row. + * If the constant for the row is negative, the sign for the row + * will be inverted so the constant becomes positive. + * + * Returns the created Row and the tag for tracking the constraint. + * @private + */ + private _createRow; + /** + * Choose the subject for solving for the row. + * + * This method will choose the best subject for using as the solve + * target for the row. An invalid symbol will be returned if there + * is no valid target. + * + * The symbols are chosen according to the following precedence: + * + * 1) The first symbol representing an external variable. + * 2) A negative slack or error tag variable. + * + * If a subject cannot be found, an invalid symbol will be returned. + * + * @private + */ + private _chooseSubject; + /** + * Add the row to the tableau using an artificial variable. + * + * This will return false if the constraint cannot be satisfied. + * + * @private + */ + private _addWithArtificialVariable; + /** + * Substitute the parametric symbol with the given row. + * + * This method will substitute all instances of the parametric symbol + * in the tableau and the objective function with the given row. + * + * @private + */ + private _substitute; + /** + * Optimize the system for the given objective function. + * + * This method performs iterations of Phase 2 of the simplex method + * until the objective function reaches a minimum. + * + * @private + */ + private _optimize; + /** + * Optimize the system using the dual of the simplex method. + * + * The current state of the system should be such that the objective + * function is optimal, but not feasible. This method will perform + * an iteration of the dual simplex method to make the solution both + * optimal and feasible. + * + * @private + */ + private _dualOptimize; + /** + * Compute the entering variable for a pivot operation. + * + * This method will return first symbol in the objective function which + * is non-dummy and has a coefficient less than zero. If no symbol meets + * the criteria, it means the objective function is at a minimum, and an + * invalid symbol is returned. + * + * @private + */ + private _getEnteringSymbol; + /** + * Compute the entering symbol for the dual optimize operation. + * + * This method will return the symbol in the row which has a positive + * coefficient and yields the minimum ratio for its respective symbol + * in the objective function. The provided row *must* be infeasible. + * If no symbol is found which meats the criteria, an invalid symbol + * is returned. + * + * @private + */ + private _getDualEnteringSymbol; + /** + * Compute the symbol for pivot exit row. + * + * This method will return the symbol for the exit row in the row + * map. If no appropriate exit symbol is found, an invalid symbol + * will be returned. This indicates that the objective function is + * unbounded. + * + * @private + */ + private _getLeavingSymbol; + /** + * Compute the leaving symbol for a marker variable. + * + * This method will return a symbol corresponding to a basic row + * which holds the given marker variable. The row will be chosen + * according to the following precedence: + * + * 1) The row with a restricted basic varible and a negative coefficient + * for the marker with the smallest ratio of -constant / coefficient. + * + * 2) The row with a restricted basic variable and the smallest ratio + * of constant / coefficient. + * + * 3) The last unrestricted row which contains the marker. + * + * If the marker does not exist in any row, an invalid symbol will be + * returned. This indicates an internal solver error since the marker + * *should* exist somewhere in the tableau. + * + * @private + */ + private _getMarkerLeavingSymbol; + /** + * Remove the effects of a constraint on the objective function. + * + * @private + */ + private _removeConstraintEffects; + /** + * Remove the effects of an error marker on the objective function. + * + * @private + */ + private _removeMarkerEffects; + /** + * Get the first Slack or Error symbol in the row. + * + * If no such symbol is present, an invalid symbol will be returned. + * + * @private + */ + private _anyPivotableSymbol; + /** + * Returns a new Symbol of the given type. + * + * @private + */ + private _makeSymbol; + private _cnMap; + private _rowMap; + private _varMap; + private _editMap; + private _infeasibleRows; + private _objective; + private _artificial; + private _idTick; +} +//# sourceMappingURL=solver.d.ts.map \ No newline at end of file diff --git a/es/solver.d.ts.map b/es/solver.d.ts.map new file mode 100644 index 0000000..4cb0d0f --- /dev/null +++ b/es/solver.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"solver.d.ts","sourceRoot":"","sources":["../src/solver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,QAAQ,EAAC,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAA;AAG1C,OAAO,EAAC,QAAQ,EAAC,MAAM,eAAe,CAAA;AAEtC;;;;GAIG;AACH,qBAAa,MAAM;IAClB;;;OAGG;IACI,aAAa,SAAO;IAE3B;;OAEG;;IAGH;;;;;;;OAOG;IACI,gBAAgB,CACtB,GAAG,EAAE,UAAU,GAAG,QAAQ,EAC1B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,UAAU,GAAG,QAAQ,GAAG,MAAM,EACnC,QAAQ,GAAE,MAA0B,GAClC,UAAU;IAMb;;;;OAIG;IACI,aAAa,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAoDlD;;;;OAIG;IACI,gBAAgB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IA+BrD;;;;;OAKG;IACI,aAAa,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO;IAIrD;;;;;OAKG;IACI,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAiBlE;;;;OAIG;IACI,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAQnD;;;;;OAKG;IACI,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO;IAInD;;;;;OAKG;IACI,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IA6C5D;;OAEG;IACI,eAAe,IAAI,IAAI;IAc9B;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAKrB;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,UAAU;IAiElB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,cAAc;IAuBtB;;;;;;OAMG;IACH,OAAO,CAAC,0BAA0B;IAsClC;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;IAenB;;;;;;;OAOG;IAEH,OAAO,CAAC,SAAS;IAsBjB;;;;;;;;;OASG;IACH,OAAO,CAAC,aAAa;IAqBrB;;;;;;;;;OASG;IACH,OAAO,CAAC,kBAAkB;IAY1B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,sBAAsB;IAoB9B;;;;;;;;;OASG;IACH,OAAO,CAAC,iBAAiB;IAsBzB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,uBAAuB;IA0C/B;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;IAShC;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAS5B;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB;IAY3B;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,eAAe,CAAe;IACtC,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,WAAW,CAAY;IAC/B,OAAO,CAAC,OAAO,CAAY;CAC3B"} \ No newline at end of file diff --git a/es/solver.js b/es/solver.js new file mode 100644 index 0000000..8f7dcfc --- /dev/null +++ b/es/solver.js @@ -0,0 +1,944 @@ +import { Constraint, Operator } from './constraint.js'; +import { Expression } from './expression.js'; +import { createMap } from './maptype.js'; +import { Strength } from './strength.js'; +/** + * The constraint solver class. + * + * @class + */ +var Solver = /** @class */ (function () { + /** + * Construct a new Solver. + */ + function Solver() { + /** + * @type {number} - The max number of solver iterations before an error + * is thrown, in order to prevent infinite iteration. Default: `10,000`. + */ + this.maxIterations = 1000; + this._cnMap = createCnMap(); + this._rowMap = createRowMap(); + this._varMap = createVarMap(); + this._editMap = createEditMap(); + this._infeasibleRows = []; + this._objective = new Row(); + this._artificial = null; + this._idTick = 0; + } + /** + * Creates and add a constraint to the solver. + * + * @param {Expression|Variable} lhs Left hand side of the expression + * @param {Operator} operator Operator + * @param {Expression|Variable|Number} rhs Right hand side of the expression + * @param {Number} [strength=Strength.required] Strength + */ + Solver.prototype.createConstraint = function (lhs, operator, rhs, strength) { + if (strength === void 0) { strength = Strength.required; } + var cn = new Constraint(lhs, operator, rhs, strength); + this.addConstraint(cn); + return cn; + }; + /** + * Add a constraint to the solver. + * + * @param {Constraint} constraint Constraint to add to the solver + */ + Solver.prototype.addConstraint = function (constraint) { + var cnPair = this._cnMap.find(constraint); + if (cnPair !== undefined) { + throw new Error('duplicate constraint'); + } + // Creating a row causes symbols to be reserved for the variables + // in the constraint. If this method exits with an exception, + // then its possible those variables will linger in the var map. + // Since its likely that those variables will be used in other + // constraints and since exceptional conditions are uncommon, + // i'm not too worried about aggressive cleanup of the var map. + var data = this._createRow(constraint); + var row = data.row; + var tag = data.tag; + var subject = this._chooseSubject(row, tag); + // If chooseSubject couldnt find a valid entering symbol, one + // last option is available if the entire row is composed of + // dummy variables. If the constant of the row is zero, then + // this represents redundant constraints and the new dummy + // marker can enter the basis. If the constant is non-zero, + // then it represents an unsatisfiable constraint. + if (subject.type() === SymbolType.Invalid && row.allDummies()) { + if (!nearZero(row.constant())) { + throw new Error('unsatisfiable constraint'); + } + else { + subject = tag.marker; + } + } + // If an entering symbol still isn't found, then the row must + // be added using an artificial variable. If that fails, then + // the row represents an unsatisfiable constraint. + if (subject.type() === SymbolType.Invalid) { + if (!this._addWithArtificialVariable(row)) { + throw new Error('unsatisfiable constraint'); + } + } + else { + row.solveFor(subject); + this._substitute(subject, row); + this._rowMap.insert(subject, row); + } + this._cnMap.insert(constraint, tag); + // Optimizing after each constraint is added performs less + // aggregate work due to a smaller average system size. It + // also ensures the solver remains in a consistent state. + this._optimize(this._objective); + }; + /** + * Remove a constraint from the solver. + * + * @param {Constraint} constraint Constraint to remove from the solver + */ + Solver.prototype.removeConstraint = function (constraint) { + var cnPair = this._cnMap.erase(constraint); + if (cnPair === undefined) { + throw new Error('unknown constraint'); + } + // Remove the error effects from the objective function + // *before* pivoting, or substitutions into the objective + // will lead to incorrect solver results. + this._removeConstraintEffects(constraint, cnPair.second); + // If the marker is basic, simply drop the row. Otherwise, + // pivot the marker into the basis and then drop the row. + var marker = cnPair.second.marker; + var rowPair = this._rowMap.erase(marker); + if (rowPair === undefined) { + var leaving = this._getMarkerLeavingSymbol(marker); + if (leaving.type() === SymbolType.Invalid) { + throw new Error('failed to find leaving row'); + } + rowPair = this._rowMap.erase(leaving); + rowPair.second.solveForEx(leaving, marker); + this._substitute(marker, rowPair.second); + } + // Optimizing after each constraint is removed ensures that the + // solver remains consistent. It makes the solver api easier to + // use at a small tradeoff for speed. + this._optimize(this._objective); + }; + /** + * Test whether the solver contains the constraint. + * + * @param {Constraint} constraint Constraint to test for + * @return {Bool} true or false + */ + Solver.prototype.hasConstraint = function (constraint) { + return this._cnMap.contains(constraint); + }; + /** + * Add an edit variable to the solver. + * + * @param {Variable} variable Edit variable to add to the solver + * @param {Number} strength Strength, should be less than `Strength.required` + */ + Solver.prototype.addEditVariable = function (variable, strength) { + var editPair = this._editMap.find(variable); + if (editPair !== undefined) { + throw new Error('duplicate edit variable'); + } + strength = Strength.clip(strength); + if (strength === Strength.required) { + throw new Error('bad required strength'); + } + var expr = new Expression(variable); + var cn = new Constraint(expr, Operator.Eq, undefined, strength); + this.addConstraint(cn); + var tag = this._cnMap.find(cn).second; + var info = { tag: tag, constraint: cn, constant: 0.0 }; + this._editMap.insert(variable, info); + }; + /** + * Remove an edit variable from the solver. + * + * @param {Variable} variable Edit variable to remove from the solver + */ + Solver.prototype.removeEditVariable = function (variable) { + var editPair = this._editMap.erase(variable); + if (editPair === undefined) { + throw new Error('unknown edit variable'); + } + this.removeConstraint(editPair.second.constraint); + }; + /** + * Test whether the solver contains the edit variable. + * + * @param {Variable} variable Edit variable to test for + * @return {Bool} true or false + */ + Solver.prototype.hasEditVariable = function (variable) { + return this._editMap.contains(variable); + }; + /** + * Suggest the value of an edit variable. + * + * @param {Variable} variable Edit variable to suggest a value for + * @param {Number} value Suggested value + */ + Solver.prototype.suggestValue = function (variable, value) { + var editPair = this._editMap.find(variable); + if (editPair === undefined) { + throw new Error('unknown edit variable'); + } + var rows = this._rowMap; + var info = editPair.second; + var delta = value - info.constant; + info.constant = value; + // Check first if the positive error variable is basic. + var marker = info.tag.marker; + var rowPair = rows.find(marker); + if (rowPair !== undefined) { + if (rowPair.second.add(-delta) < 0.0) { + this._infeasibleRows.push(marker); + } + this._dualOptimize(); + return; + } + // Check next if the negative error variable is basic. + var other = info.tag.other; + rowPair = rows.find(other); + if (rowPair !== undefined) { + if (rowPair.second.add(delta) < 0.0) { + this._infeasibleRows.push(other); + } + this._dualOptimize(); + return; + } + // Otherwise update each row where the error variables exist. + for (var i = 0, n = rows.size(); i < n; ++i) { + var rowPair_1 = rows.itemAt(i); + var row = rowPair_1.second; + var coeff = row.coefficientFor(marker); + if (coeff !== 0.0 && row.add(delta * coeff) < 0.0 && rowPair_1.first.type() !== SymbolType.External) { + this._infeasibleRows.push(rowPair_1.first); + } + } + this._dualOptimize(); + }; + /** + * Update the values of the variables. + */ + Solver.prototype.updateVariables = function () { + var vars = this._varMap; + var rows = this._rowMap; + for (var i = 0, n = vars.size(); i < n; ++i) { + var pair = vars.itemAt(i); + var rowPair = rows.find(pair.second); + if (rowPair !== undefined) { + pair.first.setValue(rowPair.second.constant()); + } + else { + pair.first.setValue(0.0); + } + } + }; + /** + * Get the symbol for the given variable. + * + * If a symbol does not exist for the variable, one will be created. + * @private + */ + Solver.prototype._getVarSymbol = function (variable) { + var _this = this; + var factory = function () { return _this._makeSymbol(SymbolType.External); }; + return this._varMap.setDefault(variable, factory).second; + }; + /** + * Create a new Row object for the given constraint. + * + * The terms in the constraint will be converted to cells in the row. + * Any term in the constraint with a coefficient of zero is ignored. + * This method uses the `_getVarSymbol` method to get the symbol for + * the variables added to the row. If the symbol for a given cell + * variable is basic, the cell variable will be substituted with the + * basic row. + * + * The necessary slack and error variables will be added to the row. + * If the constant for the row is negative, the sign for the row + * will be inverted so the constant becomes positive. + * + * Returns the created Row and the tag for tracking the constraint. + * @private + */ + Solver.prototype._createRow = function (constraint) { + var expr = constraint.expression(); + var row = new Row(expr.constant()); + // Substitute the current basic variables into the row. + var terms = expr.terms(); + for (var i = 0, n = terms.size(); i < n; ++i) { + var termPair = terms.itemAt(i); + if (!nearZero(termPair.second)) { + var symbol = this._getVarSymbol(termPair.first); + var basicPair = this._rowMap.find(symbol); + if (basicPair !== undefined) { + row.insertRow(basicPair.second, termPair.second); + } + else { + row.insertSymbol(symbol, termPair.second); + } + } + } + // Add the necessary slack, error, and dummy variables. + var objective = this._objective; + var strength = constraint.strength(); + var tag = { marker: INVALID_SYMBOL, other: INVALID_SYMBOL }; + switch (constraint.op()) { + case Operator.Le: + case Operator.Ge: { + var coeff = constraint.op() === Operator.Le ? 1.0 : -1.0; + var slack = this._makeSymbol(SymbolType.Slack); + tag.marker = slack; + row.insertSymbol(slack, coeff); + if (strength < Strength.required) { + var error = this._makeSymbol(SymbolType.Error); + tag.other = error; + row.insertSymbol(error, -coeff); + objective.insertSymbol(error, strength); + } + break; + } + case Operator.Eq: { + if (strength < Strength.required) { + var errplus = this._makeSymbol(SymbolType.Error); + var errminus = this._makeSymbol(SymbolType.Error); + tag.marker = errplus; + tag.other = errminus; + row.insertSymbol(errplus, -1.0); // v = eplus - eminus + row.insertSymbol(errminus, 1.0); // v - eplus + eminus = 0 + objective.insertSymbol(errplus, strength); + objective.insertSymbol(errminus, strength); + } + else { + var dummy = this._makeSymbol(SymbolType.Dummy); + tag.marker = dummy; + row.insertSymbol(dummy); + } + break; + } + } + // Ensure the row has a positive constant. + if (row.constant() < 0.0) { + row.reverseSign(); + } + return { row: row, tag: tag }; + }; + /** + * Choose the subject for solving for the row. + * + * This method will choose the best subject for using as the solve + * target for the row. An invalid symbol will be returned if there + * is no valid target. + * + * The symbols are chosen according to the following precedence: + * + * 1) The first symbol representing an external variable. + * 2) A negative slack or error tag variable. + * + * If a subject cannot be found, an invalid symbol will be returned. + * + * @private + */ + Solver.prototype._chooseSubject = function (row, tag) { + var cells = row.cells(); + for (var i = 0, n = cells.size(); i < n; ++i) { + var pair = cells.itemAt(i); + if (pair.first.type() === SymbolType.External) { + return pair.first; + } + } + var type = tag.marker.type(); + if (type === SymbolType.Slack || type === SymbolType.Error) { + if (row.coefficientFor(tag.marker) < 0.0) { + return tag.marker; + } + } + type = tag.other.type(); + if (type === SymbolType.Slack || type === SymbolType.Error) { + if (row.coefficientFor(tag.other) < 0.0) { + return tag.other; + } + } + return INVALID_SYMBOL; + }; + /** + * Add the row to the tableau using an artificial variable. + * + * This will return false if the constraint cannot be satisfied. + * + * @private + */ + Solver.prototype._addWithArtificialVariable = function (row) { + // Create and add the artificial variable to the tableau. + var art = this._makeSymbol(SymbolType.Slack); + this._rowMap.insert(art, row.copy()); + this._artificial = row.copy(); + // Optimize the artificial objective. This is successful + // only if the artificial objective is optimized to zero. + this._optimize(this._artificial); + var success = nearZero(this._artificial.constant()); + this._artificial = null; + // If the artificial variable is basic, pivot the row so that + // it becomes non-basic. If the row is constant, exit early. + var pair = this._rowMap.erase(art); + if (pair !== undefined) { + var basicRow = pair.second; + if (basicRow.isConstant()) { + return success; + } + var entering = this._anyPivotableSymbol(basicRow); + if (entering.type() === SymbolType.Invalid) { + return false; // unsatisfiable (will this ever happen?) + } + basicRow.solveForEx(art, entering); + this._substitute(entering, basicRow); + this._rowMap.insert(entering, basicRow); + } + // Remove the artificial variable from the tableau. + var rows = this._rowMap; + for (var i = 0, n = rows.size(); i < n; ++i) { + rows.itemAt(i).second.removeSymbol(art); + } + this._objective.removeSymbol(art); + return success; + }; + /** + * Substitute the parametric symbol with the given row. + * + * This method will substitute all instances of the parametric symbol + * in the tableau and the objective function with the given row. + * + * @private + */ + Solver.prototype._substitute = function (symbol, row) { + var rows = this._rowMap; + for (var i = 0, n = rows.size(); i < n; ++i) { + var pair = rows.itemAt(i); + pair.second.substitute(symbol, row); + if (pair.second.constant() < 0.0 && pair.first.type() !== SymbolType.External) { + this._infeasibleRows.push(pair.first); + } + } + this._objective.substitute(symbol, row); + if (this._artificial) { + this._artificial.substitute(symbol, row); + } + }; + /** + * Optimize the system for the given objective function. + * + * This method performs iterations of Phase 2 of the simplex method + * until the objective function reaches a minimum. + * + * @private + */ + Solver.prototype._optimize = function (objective) { + var iterations = 0; + while (iterations < this.maxIterations) { + var entering = this._getEnteringSymbol(objective); + if (entering.type() === SymbolType.Invalid) { + return; + } + var leaving = this._getLeavingSymbol(entering); + if (leaving.type() === SymbolType.Invalid) { + throw new Error('the objective is unbounded'); + } + // pivot the entering symbol into the basis + var row = this._rowMap.erase(leaving).second; + row.solveForEx(leaving, entering); + this._substitute(entering, row); + this._rowMap.insert(entering, row); + iterations++; + } + throw new Error('solver iterations exceeded'); + }; + /** + * Optimize the system using the dual of the simplex method. + * + * The current state of the system should be such that the objective + * function is optimal, but not feasible. This method will perform + * an iteration of the dual simplex method to make the solution both + * optimal and feasible. + * + * @private + */ + Solver.prototype._dualOptimize = function () { + var rows = this._rowMap; + var infeasible = this._infeasibleRows; + while (infeasible.length !== 0) { + var leaving = infeasible.pop(); + var pair = rows.find(leaving); + if (pair !== undefined && pair.second.constant() < 0.0) { + var entering = this._getDualEnteringSymbol(pair.second); + if (entering.type() === SymbolType.Invalid) { + throw new Error('dual optimize failed'); + } + // pivot the entering symbol into the basis + var row = pair.second; + rows.erase(leaving); + row.solveForEx(leaving, entering); + this._substitute(entering, row); + rows.insert(entering, row); + } + } + }; + /** + * Compute the entering variable for a pivot operation. + * + * This method will return first symbol in the objective function which + * is non-dummy and has a coefficient less than zero. If no symbol meets + * the criteria, it means the objective function is at a minimum, and an + * invalid symbol is returned. + * + * @private + */ + Solver.prototype._getEnteringSymbol = function (objective) { + var cells = objective.cells(); + for (var i = 0, n = cells.size(); i < n; ++i) { + var pair = cells.itemAt(i); + var symbol = pair.first; + if (pair.second < 0.0 && symbol.type() !== SymbolType.Dummy) { + return symbol; + } + } + return INVALID_SYMBOL; + }; + /** + * Compute the entering symbol for the dual optimize operation. + * + * This method will return the symbol in the row which has a positive + * coefficient and yields the minimum ratio for its respective symbol + * in the objective function. The provided row *must* be infeasible. + * If no symbol is found which meats the criteria, an invalid symbol + * is returned. + * + * @private + */ + Solver.prototype._getDualEnteringSymbol = function (row) { + var ratio = Number.MAX_VALUE; + var entering = INVALID_SYMBOL; + var cells = row.cells(); + for (var i = 0, n = cells.size(); i < n; ++i) { + var pair = cells.itemAt(i); + var symbol = pair.first; + var c = pair.second; + if (c > 0.0 && symbol.type() !== SymbolType.Dummy) { + var coeff = this._objective.coefficientFor(symbol); + var r = coeff / c; + if (r < ratio) { + ratio = r; + entering = symbol; + } + } + } + return entering; + }; + /** + * Compute the symbol for pivot exit row. + * + * This method will return the symbol for the exit row in the row + * map. If no appropriate exit symbol is found, an invalid symbol + * will be returned. This indicates that the objective function is + * unbounded. + * + * @private + */ + Solver.prototype._getLeavingSymbol = function (entering) { + var ratio = Number.MAX_VALUE; + var found = INVALID_SYMBOL; + var rows = this._rowMap; + for (var i = 0, n = rows.size(); i < n; ++i) { + var pair = rows.itemAt(i); + var symbol = pair.first; + if (symbol.type() !== SymbolType.External) { + var row = pair.second; + var temp = row.coefficientFor(entering); + if (temp < 0.0) { + var temp_ratio = -row.constant() / temp; + if (temp_ratio < ratio) { + ratio = temp_ratio; + found = symbol; + } + } + } + } + return found; + }; + /** + * Compute the leaving symbol for a marker variable. + * + * This method will return a symbol corresponding to a basic row + * which holds the given marker variable. The row will be chosen + * according to the following precedence: + * + * 1) The row with a restricted basic varible and a negative coefficient + * for the marker with the smallest ratio of -constant / coefficient. + * + * 2) The row with a restricted basic variable and the smallest ratio + * of constant / coefficient. + * + * 3) The last unrestricted row which contains the marker. + * + * If the marker does not exist in any row, an invalid symbol will be + * returned. This indicates an internal solver error since the marker + * *should* exist somewhere in the tableau. + * + * @private + */ + Solver.prototype._getMarkerLeavingSymbol = function (marker) { + var dmax = Number.MAX_VALUE; + var r1 = dmax; + var r2 = dmax; + var invalid = INVALID_SYMBOL; + var first = invalid; + var second = invalid; + var third = invalid; + var rows = this._rowMap; + for (var i = 0, n = rows.size(); i < n; ++i) { + var pair = rows.itemAt(i); + var row = pair.second; + var c = row.coefficientFor(marker); + if (c === 0.0) { + continue; + } + var symbol = pair.first; + if (symbol.type() === SymbolType.External) { + third = symbol; + } + else if (c < 0.0) { + var r = -row.constant() / c; + if (r < r1) { + r1 = r; + first = symbol; + } + } + else { + var r = row.constant() / c; + if (r < r2) { + r2 = r; + second = symbol; + } + } + } + if (first !== invalid) { + return first; + } + if (second !== invalid) { + return second; + } + return third; + }; + /** + * Remove the effects of a constraint on the objective function. + * + * @private + */ + Solver.prototype._removeConstraintEffects = function (cn, tag) { + if (tag.marker.type() === SymbolType.Error) { + this._removeMarkerEffects(tag.marker, cn.strength()); + } + if (tag.other.type() === SymbolType.Error) { + this._removeMarkerEffects(tag.other, cn.strength()); + } + }; + /** + * Remove the effects of an error marker on the objective function. + * + * @private + */ + Solver.prototype._removeMarkerEffects = function (marker, strength) { + var pair = this._rowMap.find(marker); + if (pair !== undefined) { + this._objective.insertRow(pair.second, -strength); + } + else { + this._objective.insertSymbol(marker, -strength); + } + }; + /** + * Get the first Slack or Error symbol in the row. + * + * If no such symbol is present, an invalid symbol will be returned. + * + * @private + */ + Solver.prototype._anyPivotableSymbol = function (row) { + var cells = row.cells(); + for (var i = 0, n = cells.size(); i < n; ++i) { + var pair = cells.itemAt(i); + var type = pair.first.type(); + if (type === SymbolType.Slack || type === SymbolType.Error) { + return pair.first; + } + } + return INVALID_SYMBOL; + }; + /** + * Returns a new Symbol of the given type. + * + * @private + */ + Solver.prototype._makeSymbol = function (type) { + return new Symbol(type, this._idTick++); + }; + return Solver; +}()); +export { Solver }; +/** + * Test whether a value is approximately zero. + * @private + */ +function nearZero(value) { + var eps = 1.0e-8; + return value < 0.0 ? -value < eps : value < eps; +} +/** + * An internal function for creating a constraint map. + * @private + */ +function createCnMap() { + return createMap(); +} +/** + * An internal function for creating a row map. + * @private + */ +function createRowMap() { + return createMap(); +} +/** + * An internal function for creating a variable map. + * @private + */ +function createVarMap() { + return createMap(); +} +/** + * An internal function for creating an edit map. + * @private + */ +function createEditMap() { + return createMap(); +} +/** + * An enum defining the available symbol types. + * @private + */ +var SymbolType; +(function (SymbolType) { + SymbolType[SymbolType["Invalid"] = 0] = "Invalid"; + SymbolType[SymbolType["External"] = 1] = "External"; + SymbolType[SymbolType["Slack"] = 2] = "Slack"; + SymbolType[SymbolType["Error"] = 3] = "Error"; + SymbolType[SymbolType["Dummy"] = 4] = "Dummy"; +})(SymbolType || (SymbolType = {})); +/** + * An internal class representing a symbol in the solver. + * @private + */ +var Symbol = /** @class */ (function () { + /** + * Construct a new Symbol + * + * @param [type] The type of the symbol. + * @param [id] The unique id number of the symbol. + */ + function Symbol(type, id) { + this._id = id; + this._type = type; + } + /** + * Returns the unique id number of the symbol. + */ + Symbol.prototype.id = function () { + return this._id; + }; + /** + * Returns the type of the symbol. + */ + Symbol.prototype.type = function () { + return this._type; + }; + return Symbol; +}()); +/** + * A static invalid symbol + * @private + */ +var INVALID_SYMBOL = new Symbol(SymbolType.Invalid, -1); +/** + * An internal row class used by the solver. + * @private + */ +var Row = /** @class */ (function () { + /** + * Construct a new Row. + */ + function Row(constant) { + if (constant === void 0) { constant = 0.0; } + this._cellMap = createMap(); + this._constant = constant; + } + /** + * Returns the mapping of symbols to coefficients. + */ + Row.prototype.cells = function () { + return this._cellMap; + }; + /** + * Returns the constant for the row. + */ + Row.prototype.constant = function () { + return this._constant; + }; + /** + * Returns true if the row is a constant value. + */ + Row.prototype.isConstant = function () { + return this._cellMap.empty(); + }; + /** + * Returns true if the Row has all dummy symbols. + */ + Row.prototype.allDummies = function () { + var cells = this._cellMap; + for (var i = 0, n = cells.size(); i < n; ++i) { + var pair = cells.itemAt(i); + if (pair.first.type() !== SymbolType.Dummy) { + return false; + } + } + return true; + }; + /** + * Create a copy of the row. + */ + Row.prototype.copy = function () { + var theCopy = new Row(this._constant); + theCopy._cellMap = this._cellMap.copy(); + return theCopy; + }; + /** + * Add a constant value to the row constant. + * + * Returns the new value of the constant. + */ + Row.prototype.add = function (value) { + return (this._constant += value); + }; + /** + * Insert the symbol into the row with the given coefficient. + * + * If the symbol already exists in the row, the coefficient + * will be added to the existing coefficient. If the resulting + * coefficient is zero, the symbol will be removed from the row. + */ + Row.prototype.insertSymbol = function (symbol, coefficient) { + if (coefficient === void 0) { coefficient = 1.0; } + var pair = this._cellMap.setDefault(symbol, function () { return 0.0; }); + if (nearZero((pair.second += coefficient))) { + this._cellMap.erase(symbol); + } + }; + /** + * Insert a row into this row with a given coefficient. + * + * The constant and the cells of the other row will be + * multiplied by the coefficient and added to this row. Any + * cell with a resulting coefficient of zero will be removed + * from the row. + */ + Row.prototype.insertRow = function (other, coefficient) { + if (coefficient === void 0) { coefficient = 1.0; } + this._constant += other._constant * coefficient; + var cells = other._cellMap; + for (var i = 0, n = cells.size(); i < n; ++i) { + var pair = cells.itemAt(i); + this.insertSymbol(pair.first, pair.second * coefficient); + } + }; + /** + * Remove a symbol from the row. + */ + Row.prototype.removeSymbol = function (symbol) { + this._cellMap.erase(symbol); + }; + /** + * Reverse the sign of the constant and cells in the row. + */ + Row.prototype.reverseSign = function () { + this._constant = -this._constant; + var cells = this._cellMap; + for (var i = 0, n = cells.size(); i < n; ++i) { + var pair = cells.itemAt(i); + pair.second = -pair.second; + } + }; + /** + * Solve the row for the given symbol. + * + * This method assumes the row is of the form + * a * x + b * y + c = 0 and (assuming solve for x) will modify + * the row to represent the right hand side of + * x = -b/a * y - c / a. The target symbol will be removed from + * the row, and the constant and other cells will be multiplied + * by the negative inverse of the target coefficient. + * + * The given symbol *must* exist in the row. + */ + Row.prototype.solveFor = function (symbol) { + var cells = this._cellMap; + var pair = cells.erase(symbol); + var coeff = -1.0 / pair.second; + this._constant *= coeff; + for (var i = 0, n = cells.size(); i < n; ++i) { + cells.itemAt(i).second *= coeff; + } + }; + /** + * Solve the row for the given symbols. + * + * This method assumes the row is of the form + * x = b * y + c and will solve the row such that + * y = x / b - c / b. The rhs symbol will be removed from the + * row, the lhs added, and the result divided by the negative + * inverse of the rhs coefficient. + * + * The lhs symbol *must not* exist in the row, and the rhs + * symbol must* exist in the row. + */ + Row.prototype.solveForEx = function (lhs, rhs) { + this.insertSymbol(lhs, -1.0); + this.solveFor(rhs); + }; + /** + * Returns the coefficient for the given symbol. + */ + Row.prototype.coefficientFor = function (symbol) { + var pair = this._cellMap.find(symbol); + return pair !== undefined ? pair.second : 0.0; + }; + /** + * Substitute a symbol with the data from another row. + * + * Given a row of the form a * x + b and a substitution of the + * form x = 3 * y + c the row will be updated to reflect the + * expression 3 * a * y + a * c + b. + * + * If the symbol does not exist in the row, this is a no-op. + */ + Row.prototype.substitute = function (symbol, row) { + var pair = this._cellMap.erase(symbol); + if (pair !== undefined) { + this.insertRow(row, pair.second); + } + }; + return Row; +}()); diff --git a/es/strength.d.ts b/es/strength.d.ts new file mode 100644 index 0000000..faf2714 --- /dev/null +++ b/es/strength.d.ts @@ -0,0 +1,37 @@ +/** + * @class Strength + */ +export declare class Strength { + /** + * Create a new symbolic strength. + * + * @param a strong + * @param b medium + * @param c weak + * @param [w] weight + * @return strength + */ + static create(a: number, b: number, c: number, w?: number): number; + /** + * The 'required' symbolic strength. + */ + static required: number; + /** + * The 'strong' symbolic strength. + */ + static strong: number; + /** + * The 'medium' symbolic strength. + */ + static medium: number; + /** + * The 'weak' symbolic strength. + */ + static weak: number; + /** + * Clip a symbolic strength to the allowed min and max. + * @private + */ + static clip(value: number): number; +} +//# sourceMappingURL=strength.d.ts.map \ No newline at end of file diff --git a/es/strength.d.ts.map b/es/strength.d.ts.map new file mode 100644 index 0000000..0e85135 --- /dev/null +++ b/es/strength.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"strength.d.ts","sourceRoot":"","sources":["../src/strength.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,QAAQ;IACpB;;;;;;;;OAQG;IACH,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,GAAE,MAAY;IAQ9D;;OAEG;IACH,MAAM,CAAC,QAAQ,SAA0C;IAEzD;;OAEG;IACH,MAAM,CAAC,MAAM,SAAiC;IAE9C;;OAEG;IACH,MAAM,CAAC,MAAM,SAAiC;IAE9C;;OAEG;IACH,MAAM,CAAC,IAAI,SAAiC;IAE5C;;;OAGG;IACH,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM;CAGzB"} \ No newline at end of file diff --git a/es/strength.js b/es/strength.js new file mode 100644 index 0000000..ebde726 --- /dev/null +++ b/es/strength.js @@ -0,0 +1,49 @@ +/** + * @class Strength + */ +var Strength = /** @class */ (function () { + function Strength() { + } + /** + * Create a new symbolic strength. + * + * @param a strong + * @param b medium + * @param c weak + * @param [w] weight + * @return strength + */ + Strength.create = function (a, b, c, w) { + if (w === void 0) { w = 1.0; } + var result = 0.0; + result += Math.max(0.0, Math.min(1000.0, a * w)) * 1000000.0; + result += Math.max(0.0, Math.min(1000.0, b * w)) * 1000.0; + result += Math.max(0.0, Math.min(1000.0, c * w)); + return result; + }; + /** + * Clip a symbolic strength to the allowed min and max. + * @private + */ + Strength.clip = function (value) { + return Math.max(0.0, Math.min(Strength.required, value)); + }; + /** + * The 'required' symbolic strength. + */ + Strength.required = Strength.create(1000.0, 1000.0, 1000.0); + /** + * The 'strong' symbolic strength. + */ + Strength.strong = Strength.create(1.0, 0.0, 0.0); + /** + * The 'medium' symbolic strength. + */ + Strength.medium = Strength.create(0.0, 1.0, 0.0); + /** + * The 'weak' symbolic strength. + */ + Strength.weak = Strength.create(0.0, 0.0, 1.0); + return Strength; +}()); +export { Strength }; diff --git a/es/variable.d.ts b/es/variable.d.ts new file mode 100644 index 0000000..9b5da8a --- /dev/null +++ b/es/variable.d.ts @@ -0,0 +1,89 @@ +import { Expression } from './expression.js'; +/** + * The primary user constraint variable. + * + * @class + * @param {String} [name=""] The name to associated with the variable. + */ +export declare class Variable { + constructor(name?: string); + /** + * Returns the unique id number of the variable. + * @private + */ + id(): number; + /** + * Returns the name of the variable. + * + * @return {String} name of the variable + */ + name(): string; + /** + * Set the name of the variable. + * + * @param {String} name Name of the variable + */ + setName(name: string): void; + /** + * Returns the user context object of the variable. + * @private + */ + context(): any; + /** + * Set the user context object of the variable. + * @private + */ + setContext(context: any): void; + /** + * Returns the value of the variable. + * + * @return {Number} Calculated value + */ + value(): number; + /** + * Set the value of the variable. + * @private + */ + setValue(value: number): void; + /** + * Creates a new Expression by adding a number, variable or expression + * to the variable. + * + * @param {Number|Variable|Expression} value Value to add. + * @return {Expression} expression + */ + plus(value: number | Variable | Expression): Expression; + /** + * Creates a new Expression by substracting a number, variable or expression + * from the variable. + * + * @param {Number|Variable|Expression} value Value to substract. + * @return {Expression} expression + */ + minus(value: number | Variable | Expression): Expression; + /** + * Creates a new Expression by multiplying with a fixed number. + * + * @param {Number} coefficient Coefficient to multiply with. + * @return {Expression} expression + */ + multiply(coefficient: number): Expression; + /** + * Creates a new Expression by dividing with a fixed number. + * + * @param {Number} coefficient Coefficient to divide by. + * @return {Expression} expression + */ + divide(coefficient: number): Expression; + /** + * Returns the JSON representation of the variable. + * @private + */ + toJSON(): any; + toString(): string; + private _name; + private _value; + private _context; + private _id; +} +//# sourceMappingURL=variable.d.ts.map \ No newline at end of file diff --git a/es/variable.d.ts.map b/es/variable.d.ts.map new file mode 100644 index 0000000..647ee11 --- /dev/null +++ b/es/variable.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"variable.d.ts","sourceRoot":"","sources":["../src/variable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAA;AAE1C;;;;;GAKG;AACH,qBAAa,QAAQ;gBACR,IAAI,GAAE,MAAW;IAI7B;;;OAGG;IACI,EAAE,IAAI,MAAM;IAInB;;;;OAIG;IACI,IAAI,IAAI,MAAM;IAIrB;;;;OAIG;IACI,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIlC;;;OAGG;IACI,OAAO,IAAI,GAAG;IAIrB;;;OAGG;IACI,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI;IAIrC;;;;OAIG;IACI,KAAK,IAAI,MAAM;IAItB;;;OAGG;IACI,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIpC;;;;;;OAMG;IACI,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU;IAI9D;;;;;;OAMG;IACI,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU;IAI/D;;;;;OAKG;IACI,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU;IAIhD;;;;;OAKG;IACI,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU;IAI9C;;;OAGG;IACI,MAAM,IAAI,GAAG;IAOb,QAAQ,IAAI,MAAM;IAIzB,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAY;IAC5B,OAAO,CAAC,GAAG,CAAkB;CAC7B"} \ No newline at end of file diff --git a/es/variable.js b/es/variable.js new file mode 100644 index 0000000..06c137b --- /dev/null +++ b/es/variable.js @@ -0,0 +1,126 @@ +import { Expression } from './expression.js'; +/** + * The primary user constraint variable. + * + * @class + * @param {String} [name=""] The name to associated with the variable. + */ +var Variable = /** @class */ (function () { + function Variable(name) { + if (name === void 0) { name = ''; } + this._value = 0.0; + this._context = null; + this._id = VarId++; + this._name = name; + } + /** + * Returns the unique id number of the variable. + * @private + */ + Variable.prototype.id = function () { + return this._id; + }; + /** + * Returns the name of the variable. + * + * @return {String} name of the variable + */ + Variable.prototype.name = function () { + return this._name; + }; + /** + * Set the name of the variable. + * + * @param {String} name Name of the variable + */ + Variable.prototype.setName = function (name) { + this._name = name; + }; + /** + * Returns the user context object of the variable. + * @private + */ + Variable.prototype.context = function () { + return this._context; + }; + /** + * Set the user context object of the variable. + * @private + */ + Variable.prototype.setContext = function (context) { + this._context = context; + }; + /** + * Returns the value of the variable. + * + * @return {Number} Calculated value + */ + Variable.prototype.value = function () { + return this._value; + }; + /** + * Set the value of the variable. + * @private + */ + Variable.prototype.setValue = function (value) { + this._value = value; + }; + /** + * Creates a new Expression by adding a number, variable or expression + * to the variable. + * + * @param {Number|Variable|Expression} value Value to add. + * @return {Expression} expression + */ + Variable.prototype.plus = function (value) { + return new Expression(this, value); + }; + /** + * Creates a new Expression by substracting a number, variable or expression + * from the variable. + * + * @param {Number|Variable|Expression} value Value to substract. + * @return {Expression} expression + */ + Variable.prototype.minus = function (value) { + return new Expression(this, typeof value === 'number' ? -value : [-1, value]); + }; + /** + * Creates a new Expression by multiplying with a fixed number. + * + * @param {Number} coefficient Coefficient to multiply with. + * @return {Expression} expression + */ + Variable.prototype.multiply = function (coefficient) { + return new Expression([coefficient, this]); + }; + /** + * Creates a new Expression by dividing with a fixed number. + * + * @param {Number} coefficient Coefficient to divide by. + * @return {Expression} expression + */ + Variable.prototype.divide = function (coefficient) { + return new Expression([1 / coefficient, this]); + }; + /** + * Returns the JSON representation of the variable. + * @private + */ + Variable.prototype.toJSON = function () { + return { + name: this._name, + value: this._value, + }; + }; + Variable.prototype.toString = function () { + return this._context + '[' + this._name + ':' + this._value + ']'; + }; + return Variable; +}()); +export { Variable }; +/** + * The internal variable id counter. + * @private + */ +var VarId = 0; diff --git a/lib/kiwi.js b/lib/kiwi.js new file mode 100644 index 0000000..a9e1fea --- /dev/null +++ b/lib/kiwi.js @@ -0,0 +1,1558 @@ + +/*----------------------------------------------------------------------------- +| Copyright (c) 2014-2019, Nucleic Development Team & H. Rutjes & Lume. +| +| Distributed under the terms of the Modified BSD License. +| +| The full license is in the file COPYING.txt, distributed with this software. +-----------------------------------------------------------------------------*/ + +/** + * Lume Kiwi is an efficient implementation of the Cassowary constraint solving + * algorithm, based on the seminal Cassowary paper. + * It is *not* a refactoring or port of the original C++ solver, but + * has been designed from the ground up to be lightweight and fast. + * + * **Example** + * + * ```javascript + * import * as kiwi from '@lume/kiwi'; + * + * // Create a solver + * const solver = new kiwi.Solver(); + * + * // Adjust the max number of solver iterations before an error is thrown if + * // more is needed. Default is 10,000. + * solver.maxIterations = 20000; + * + * // Create and add some editable variables + * const left = new kiwi.Variable(); + * const width = new kiwi.Variable(); + * solver.addEditVariable(left, kiwi.Strength.strong); + * solver.addEditVariable(width, kiwi.Strength.strong); + * + * // Create a variable calculated through a constraint + * const centerX = new kiwi.Variable(); + * const expr = new kiwi.Expression([-1, centerX], left, [0.5, width]); + * solver.addConstraint(new kiwi.Constraint(expr, kiwi.Operator.Eq, kiwi.Strength.required)); + * + * // Suggest some values to the solver + * solver.suggestValue(left, 0); + * solver.suggestValue(width, 500); + * + * // Lets solve the problem! + * solver.updateVariables(); + * + * console.assert(centerX.value() === 250); + * ``` + * + * ## API Documentation + * @module @lume/kiwi + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = global || self, factory(global.kiwi = {})); +}(this, function (exports) { 'use strict'; + + function createMap() { + return new IndexedMap(); + } + var IndexedMap = /** @class */ (function () { + function IndexedMap() { + this.index = {}; + this.array = []; + } + /** + * Returns the number of items in the array. + */ + IndexedMap.prototype.size = function () { + return this.array.length; + }; + /** + * Returns true if the array is empty. + */ + IndexedMap.prototype.empty = function () { + return this.array.length === 0; + }; + /** + * Returns the item at the given array index. + * + * @param index The integer index of the desired item. + */ + IndexedMap.prototype.itemAt = function (index) { + return this.array[index]; + }; + /** + * Returns true if the key is in the array, false otherwise. + * + * @param key The key to locate in the array. + */ + IndexedMap.prototype.contains = function (key) { + return this.index[key.id()] !== undefined; + }; + /** + * Returns the pair associated with the given key, or undefined. + * + * @param key The key to locate in the array. + */ + IndexedMap.prototype.find = function (key) { + var i = this.index[key.id()]; + return i === undefined ? undefined : this.array[i]; + }; + /** + * Returns the pair associated with the key if it exists. + * + * If the key does not exist, a new pair will be created and + * inserted using the value created by the given factory. + * + * @param key The key to locate in the array. + * @param factory The function which creates the default value. + */ + IndexedMap.prototype.setDefault = function (key, factory) { + var i = this.index[key.id()]; + if (i === undefined) { + var pair = new Pair(key, factory()); + this.index[key.id()] = this.array.length; + this.array.push(pair); + return pair; + } + else { + return this.array[i]; + } + }; + /** + * Insert the pair into the array and return the pair. + * + * This will overwrite any existing entry in the array. + * + * @param key The key portion of the pair. + * @param value The value portion of the pair. + */ + IndexedMap.prototype.insert = function (key, value) { + var pair = new Pair(key, value); + var i = this.index[key.id()]; + if (i === undefined) { + this.index[key.id()] = this.array.length; + this.array.push(pair); + } + else { + this.array[i] = pair; + } + return pair; + }; + /** + * Removes and returns the pair for the given key, or undefined. + * + * @param key The key to remove from the map. + */ + IndexedMap.prototype.erase = function (key) { + var i = this.index[key.id()]; + if (i === undefined) { + return undefined; + } + this.index[key.id()] = undefined; + var pair = this.array[i]; + var last = this.array.pop(); + if (pair !== last) { + this.array[i] = last; + this.index[last.first.id()] = i; + } + return pair; + }; + /** + * Create a copy of this associative array. + */ + IndexedMap.prototype.copy = function () { + var copy = new IndexedMap(); + for (var i = 0; i < this.array.length; i++) { + var pair = this.array[i].copy(); + copy.array[i] = pair; + copy.index[pair.first.id()] = i; + } + return copy; + }; + return IndexedMap; + }()); + /** + * A class which defines a generic pair object. + * @private + */ + // tslint:disable: max-classes-per-file + var Pair = /** @class */ (function () { + /** + * Construct a new Pair object. + * + * @param first The first item of the pair. + * @param second The second item of the pair. + */ + function Pair(first, second) { + this.first = first; + this.second = second; + } + /** + * Create a copy of the pair. + */ + Pair.prototype.copy = function () { + return new Pair(this.first, this.second); + }; + return Pair; + }()); + + /** + * The primary user constraint variable. + * + * @class + * @param {String} [name=""] The name to associated with the variable. + */ + var Variable = /** @class */ (function () { + function Variable(name) { + if (name === void 0) { name = ''; } + this._value = 0.0; + this._context = null; + this._id = VarId++; + this._name = name; + } + /** + * Returns the unique id number of the variable. + * @private + */ + Variable.prototype.id = function () { + return this._id; + }; + /** + * Returns the name of the variable. + * + * @return {String} name of the variable + */ + Variable.prototype.name = function () { + return this._name; + }; + /** + * Set the name of the variable. + * + * @param {String} name Name of the variable + */ + Variable.prototype.setName = function (name) { + this._name = name; + }; + /** + * Returns the user context object of the variable. + * @private + */ + Variable.prototype.context = function () { + return this._context; + }; + /** + * Set the user context object of the variable. + * @private + */ + Variable.prototype.setContext = function (context) { + this._context = context; + }; + /** + * Returns the value of the variable. + * + * @return {Number} Calculated value + */ + Variable.prototype.value = function () { + return this._value; + }; + /** + * Set the value of the variable. + * @private + */ + Variable.prototype.setValue = function (value) { + this._value = value; + }; + /** + * Creates a new Expression by adding a number, variable or expression + * to the variable. + * + * @param {Number|Variable|Expression} value Value to add. + * @return {Expression} expression + */ + Variable.prototype.plus = function (value) { + return new Expression(this, value); + }; + /** + * Creates a new Expression by substracting a number, variable or expression + * from the variable. + * + * @param {Number|Variable|Expression} value Value to substract. + * @return {Expression} expression + */ + Variable.prototype.minus = function (value) { + return new Expression(this, typeof value === 'number' ? -value : [-1, value]); + }; + /** + * Creates a new Expression by multiplying with a fixed number. + * + * @param {Number} coefficient Coefficient to multiply with. + * @return {Expression} expression + */ + Variable.prototype.multiply = function (coefficient) { + return new Expression([coefficient, this]); + }; + /** + * Creates a new Expression by dividing with a fixed number. + * + * @param {Number} coefficient Coefficient to divide by. + * @return {Expression} expression + */ + Variable.prototype.divide = function (coefficient) { + return new Expression([1 / coefficient, this]); + }; + /** + * Returns the JSON representation of the variable. + * @private + */ + Variable.prototype.toJSON = function () { + return { + name: this._name, + value: this._value, + }; + }; + Variable.prototype.toString = function () { + return this._context + '[' + this._name + ':' + this._value + ']'; + }; + return Variable; + }()); + /** + * The internal variable id counter. + * @private + */ + var VarId = 0; + + /** + * An expression of variable terms and a constant. + * + * The constructor accepts an arbitrary number of parameters, + * each of which must be one of the following types: + * - number + * - Variable + * - Expression + * - 2-tuple of [number, Variable|Expression] + * + * The parameters are summed. The tuples are multiplied. + * + * @class + * @param {...(number|Variable|Expression|Array)} args + */ + var Expression = /** @class */ (function () { + function Expression() { + var parsed = parseArgs(arguments); + this._terms = parsed.terms; + this._constant = parsed.constant; + } + /** + * Returns the mapping of terms in the expression. + * + * This *must* be treated as const. + * @private + */ + Expression.prototype.terms = function () { + return this._terms; + }; + /** + * Returns the constant of the expression. + * @private + */ + Expression.prototype.constant = function () { + return this._constant; + }; + /** + * Returns the computed value of the expression. + * + * @private + * @return {Number} computed value of the expression + */ + Expression.prototype.value = function () { + var result = this._constant; + for (var i = 0, n = this._terms.size(); i < n; i++) { + var pair = this._terms.itemAt(i); + result += pair.first.value() * pair.second; + } + return result; + }; + /** + * Creates a new Expression by adding a number, variable or expression + * to the expression. + * + * @param {Number|Variable|Expression} value Value to add. + * @return {Expression} expression + */ + Expression.prototype.plus = function (value) { + return new Expression(this, value); + }; + /** + * Creates a new Expression by substracting a number, variable or expression + * from the expression. + * + * @param {Number|Variable|Expression} value Value to substract. + * @return {Expression} expression + */ + Expression.prototype.minus = function (value) { + return new Expression(this, typeof value === 'number' ? -value : [-1, value]); + }; + /** + * Creates a new Expression by multiplying with a fixed number. + * + * @param {Number} coefficient Coefficient to multiply with. + * @return {Expression} expression + */ + Expression.prototype.multiply = function (coefficient) { + return new Expression([coefficient, this]); + }; + /** + * Creates a new Expression by dividing with a fixed number. + * + * @param {Number} coefficient Coefficient to divide by. + * @return {Expression} expression + */ + Expression.prototype.divide = function (coefficient) { + return new Expression([1 / coefficient, this]); + }; + Expression.prototype.isConstant = function () { + return this._terms.size() == 0; + }; + Expression.prototype.toString = function () { + var result = this._terms.array + .map(function (pair) { + return pair.second + '*' + pair.first.toString(); + }) + .join(' + '); + if (!this.isConstant() && this._constant !== 0) { + result += ' + '; + } + result += this._constant; + return result; + }; + return Expression; + }()); + /** + * An internal argument parsing function. + * @private + */ + function parseArgs(args) { + var constant = 0.0; + var factory = function () { return 0.0; }; + var terms = createMap(); + for (var i = 0, n = args.length; i < n; ++i) { + var item = args[i]; + if (typeof item === 'number') { + constant += item; + } + else if (item instanceof Variable) { + terms.setDefault(item, factory).second += 1.0; + } + else if (item instanceof Expression) { + constant += item.constant(); + var terms2 = item.terms(); + for (var j = 0, k = terms2.size(); j < k; j++) { + var termPair = terms2.itemAt(j); + terms.setDefault(termPair.first, factory).second += termPair.second; + } + } + else if (item instanceof Array) { + if (item.length !== 2) { + throw new Error('array must have length 2'); + } + var value = item[0]; + var value2 = item[1]; + if (typeof value !== 'number') { + throw new Error('array item 0 must be a number'); + } + if (value2 instanceof Variable) { + terms.setDefault(value2, factory).second += value; + } + else if (value2 instanceof Expression) { + constant += value2.constant() * value; + var terms2 = value2.terms(); + for (var j = 0, k = terms2.size(); j < k; j++) { + var termPair = terms2.itemAt(j); + terms.setDefault(termPair.first, factory).second += termPair.second * value; + } + } + else { + throw new Error('array item 1 must be a variable or expression'); + } + } + else { + throw new Error('invalid Expression argument: ' + item); + } + } + return { terms: terms, constant: constant }; + } + + /** + * @class Strength + */ + var Strength = /** @class */ (function () { + function Strength() { + } + /** + * Create a new symbolic strength. + * + * @param a strong + * @param b medium + * @param c weak + * @param [w] weight + * @return strength + */ + Strength.create = function (a, b, c, w) { + if (w === void 0) { w = 1.0; } + var result = 0.0; + result += Math.max(0.0, Math.min(1000.0, a * w)) * 1000000.0; + result += Math.max(0.0, Math.min(1000.0, b * w)) * 1000.0; + result += Math.max(0.0, Math.min(1000.0, c * w)); + return result; + }; + /** + * Clip a symbolic strength to the allowed min and max. + * @private + */ + Strength.clip = function (value) { + return Math.max(0.0, Math.min(Strength.required, value)); + }; + /** + * The 'required' symbolic strength. + */ + Strength.required = Strength.create(1000.0, 1000.0, 1000.0); + /** + * The 'strong' symbolic strength. + */ + Strength.strong = Strength.create(1.0, 0.0, 0.0); + /** + * The 'medium' symbolic strength. + */ + Strength.medium = Strength.create(0.0, 1.0, 0.0); + /** + * The 'weak' symbolic strength. + */ + Strength.weak = Strength.create(0.0, 0.0, 1.0); + return Strength; + }()); + + (function (Operator) { + Operator[Operator["Le"] = 0] = "Le"; + Operator[Operator["Ge"] = 1] = "Ge"; + Operator[Operator["Eq"] = 2] = "Eq"; + })(exports.Operator || (exports.Operator = {})); + /** + * A linear constraint equation. + * + * A constraint equation is composed of an expression, an operator, + * and a strength. The RHS of the equation is implicitly zero. + * + * @class + * @param {Expression} expression The constraint expression (LHS). + * @param {Operator} operator The equation operator. + * @param {Expression} [rhs] Right hand side of the expression. + * @param {Number} [strength=Strength.required] The strength of the constraint. + */ + var Constraint = /** @class */ (function () { + function Constraint(expression, operator, rhs, strength) { + if (strength === void 0) { strength = Strength.required; } + this._id = CnId++; + this._operator = operator; + this._strength = Strength.clip(strength); + if (rhs === undefined && expression instanceof Expression) { + this._expression = expression; + } + else { + this._expression = expression.minus(rhs); + } + } + /** + * Returns the unique id number of the constraint. + * @private + */ + Constraint.prototype.id = function () { + return this._id; + }; + /** + * Returns the expression of the constraint. + * + * @return {Expression} expression + */ + Constraint.prototype.expression = function () { + return this._expression; + }; + /** + * Returns the relational operator of the constraint. + * + * @return {Operator} linear constraint operator + */ + Constraint.prototype.op = function () { + return this._operator; + }; + /** + * Returns the strength of the constraint. + * + * @return {Number} strength + */ + Constraint.prototype.strength = function () { + return this._strength; + }; + Constraint.prototype.toString = function () { + return (this._expression.toString() + ' ' + ['<=', '>=', '='][this._operator] + ' 0 (' + this._strength.toString() + ')'); + }; + return Constraint; + }()); + /** + * The internal constraint id counter. + * @private + */ + var CnId = 0; + + /** + * The constraint solver class. + * + * @class + */ + var Solver = /** @class */ (function () { + /** + * Construct a new Solver. + */ + function Solver() { + /** + * @type {number} - The max number of solver iterations before an error + * is thrown, in order to prevent infinite iteration. Default: `10,000`. + */ + this.maxIterations = 1000; + this._cnMap = createCnMap(); + this._rowMap = createRowMap(); + this._varMap = createVarMap(); + this._editMap = createEditMap(); + this._infeasibleRows = []; + this._objective = new Row(); + this._artificial = null; + this._idTick = 0; + } + /** + * Creates and add a constraint to the solver. + * + * @param {Expression|Variable} lhs Left hand side of the expression + * @param {Operator} operator Operator + * @param {Expression|Variable|Number} rhs Right hand side of the expression + * @param {Number} [strength=Strength.required] Strength + */ + Solver.prototype.createConstraint = function (lhs, operator, rhs, strength) { + if (strength === void 0) { strength = Strength.required; } + var cn = new Constraint(lhs, operator, rhs, strength); + this.addConstraint(cn); + return cn; + }; + /** + * Add a constraint to the solver. + * + * @param {Constraint} constraint Constraint to add to the solver + */ + Solver.prototype.addConstraint = function (constraint) { + var cnPair = this._cnMap.find(constraint); + if (cnPair !== undefined) { + throw new Error('duplicate constraint'); + } + // Creating a row causes symbols to be reserved for the variables + // in the constraint. If this method exits with an exception, + // then its possible those variables will linger in the var map. + // Since its likely that those variables will be used in other + // constraints and since exceptional conditions are uncommon, + // i'm not too worried about aggressive cleanup of the var map. + var data = this._createRow(constraint); + var row = data.row; + var tag = data.tag; + var subject = this._chooseSubject(row, tag); + // If chooseSubject couldnt find a valid entering symbol, one + // last option is available if the entire row is composed of + // dummy variables. If the constant of the row is zero, then + // this represents redundant constraints and the new dummy + // marker can enter the basis. If the constant is non-zero, + // then it represents an unsatisfiable constraint. + if (subject.type() === SymbolType.Invalid && row.allDummies()) { + if (!nearZero(row.constant())) { + throw new Error('unsatisfiable constraint'); + } + else { + subject = tag.marker; + } + } + // If an entering symbol still isn't found, then the row must + // be added using an artificial variable. If that fails, then + // the row represents an unsatisfiable constraint. + if (subject.type() === SymbolType.Invalid) { + if (!this._addWithArtificialVariable(row)) { + throw new Error('unsatisfiable constraint'); + } + } + else { + row.solveFor(subject); + this._substitute(subject, row); + this._rowMap.insert(subject, row); + } + this._cnMap.insert(constraint, tag); + // Optimizing after each constraint is added performs less + // aggregate work due to a smaller average system size. It + // also ensures the solver remains in a consistent state. + this._optimize(this._objective); + }; + /** + * Remove a constraint from the solver. + * + * @param {Constraint} constraint Constraint to remove from the solver + */ + Solver.prototype.removeConstraint = function (constraint) { + var cnPair = this._cnMap.erase(constraint); + if (cnPair === undefined) { + throw new Error('unknown constraint'); + } + // Remove the error effects from the objective function + // *before* pivoting, or substitutions into the objective + // will lead to incorrect solver results. + this._removeConstraintEffects(constraint, cnPair.second); + // If the marker is basic, simply drop the row. Otherwise, + // pivot the marker into the basis and then drop the row. + var marker = cnPair.second.marker; + var rowPair = this._rowMap.erase(marker); + if (rowPair === undefined) { + var leaving = this._getMarkerLeavingSymbol(marker); + if (leaving.type() === SymbolType.Invalid) { + throw new Error('failed to find leaving row'); + } + rowPair = this._rowMap.erase(leaving); + rowPair.second.solveForEx(leaving, marker); + this._substitute(marker, rowPair.second); + } + // Optimizing after each constraint is removed ensures that the + // solver remains consistent. It makes the solver api easier to + // use at a small tradeoff for speed. + this._optimize(this._objective); + }; + /** + * Test whether the solver contains the constraint. + * + * @param {Constraint} constraint Constraint to test for + * @return {Bool} true or false + */ + Solver.prototype.hasConstraint = function (constraint) { + return this._cnMap.contains(constraint); + }; + /** + * Add an edit variable to the solver. + * + * @param {Variable} variable Edit variable to add to the solver + * @param {Number} strength Strength, should be less than `Strength.required` + */ + Solver.prototype.addEditVariable = function (variable, strength) { + var editPair = this._editMap.find(variable); + if (editPair !== undefined) { + throw new Error('duplicate edit variable'); + } + strength = Strength.clip(strength); + if (strength === Strength.required) { + throw new Error('bad required strength'); + } + var expr = new Expression(variable); + var cn = new Constraint(expr, exports.Operator.Eq, undefined, strength); + this.addConstraint(cn); + var tag = this._cnMap.find(cn).second; + var info = { tag: tag, constraint: cn, constant: 0.0 }; + this._editMap.insert(variable, info); + }; + /** + * Remove an edit variable from the solver. + * + * @param {Variable} variable Edit variable to remove from the solver + */ + Solver.prototype.removeEditVariable = function (variable) { + var editPair = this._editMap.erase(variable); + if (editPair === undefined) { + throw new Error('unknown edit variable'); + } + this.removeConstraint(editPair.second.constraint); + }; + /** + * Test whether the solver contains the edit variable. + * + * @param {Variable} variable Edit variable to test for + * @return {Bool} true or false + */ + Solver.prototype.hasEditVariable = function (variable) { + return this._editMap.contains(variable); + }; + /** + * Suggest the value of an edit variable. + * + * @param {Variable} variable Edit variable to suggest a value for + * @param {Number} value Suggested value + */ + Solver.prototype.suggestValue = function (variable, value) { + var editPair = this._editMap.find(variable); + if (editPair === undefined) { + throw new Error('unknown edit variable'); + } + var rows = this._rowMap; + var info = editPair.second; + var delta = value - info.constant; + info.constant = value; + // Check first if the positive error variable is basic. + var marker = info.tag.marker; + var rowPair = rows.find(marker); + if (rowPair !== undefined) { + if (rowPair.second.add(-delta) < 0.0) { + this._infeasibleRows.push(marker); + } + this._dualOptimize(); + return; + } + // Check next if the negative error variable is basic. + var other = info.tag.other; + rowPair = rows.find(other); + if (rowPair !== undefined) { + if (rowPair.second.add(delta) < 0.0) { + this._infeasibleRows.push(other); + } + this._dualOptimize(); + return; + } + // Otherwise update each row where the error variables exist. + for (var i = 0, n = rows.size(); i < n; ++i) { + var rowPair_1 = rows.itemAt(i); + var row = rowPair_1.second; + var coeff = row.coefficientFor(marker); + if (coeff !== 0.0 && row.add(delta * coeff) < 0.0 && rowPair_1.first.type() !== SymbolType.External) { + this._infeasibleRows.push(rowPair_1.first); + } + } + this._dualOptimize(); + }; + /** + * Update the values of the variables. + */ + Solver.prototype.updateVariables = function () { + var vars = this._varMap; + var rows = this._rowMap; + for (var i = 0, n = vars.size(); i < n; ++i) { + var pair = vars.itemAt(i); + var rowPair = rows.find(pair.second); + if (rowPair !== undefined) { + pair.first.setValue(rowPair.second.constant()); + } + else { + pair.first.setValue(0.0); + } + } + }; + /** + * Get the symbol for the given variable. + * + * If a symbol does not exist for the variable, one will be created. + * @private + */ + Solver.prototype._getVarSymbol = function (variable) { + var _this = this; + var factory = function () { return _this._makeSymbol(SymbolType.External); }; + return this._varMap.setDefault(variable, factory).second; + }; + /** + * Create a new Row object for the given constraint. + * + * The terms in the constraint will be converted to cells in the row. + * Any term in the constraint with a coefficient of zero is ignored. + * This method uses the `_getVarSymbol` method to get the symbol for + * the variables added to the row. If the symbol for a given cell + * variable is basic, the cell variable will be substituted with the + * basic row. + * + * The necessary slack and error variables will be added to the row. + * If the constant for the row is negative, the sign for the row + * will be inverted so the constant becomes positive. + * + * Returns the created Row and the tag for tracking the constraint. + * @private + */ + Solver.prototype._createRow = function (constraint) { + var expr = constraint.expression(); + var row = new Row(expr.constant()); + // Substitute the current basic variables into the row. + var terms = expr.terms(); + for (var i = 0, n = terms.size(); i < n; ++i) { + var termPair = terms.itemAt(i); + if (!nearZero(termPair.second)) { + var symbol = this._getVarSymbol(termPair.first); + var basicPair = this._rowMap.find(symbol); + if (basicPair !== undefined) { + row.insertRow(basicPair.second, termPair.second); + } + else { + row.insertSymbol(symbol, termPair.second); + } + } + } + // Add the necessary slack, error, and dummy variables. + var objective = this._objective; + var strength = constraint.strength(); + var tag = { marker: INVALID_SYMBOL, other: INVALID_SYMBOL }; + switch (constraint.op()) { + case exports.Operator.Le: + case exports.Operator.Ge: { + var coeff = constraint.op() === exports.Operator.Le ? 1.0 : -1.0; + var slack = this._makeSymbol(SymbolType.Slack); + tag.marker = slack; + row.insertSymbol(slack, coeff); + if (strength < Strength.required) { + var error = this._makeSymbol(SymbolType.Error); + tag.other = error; + row.insertSymbol(error, -coeff); + objective.insertSymbol(error, strength); + } + break; + } + case exports.Operator.Eq: { + if (strength < Strength.required) { + var errplus = this._makeSymbol(SymbolType.Error); + var errminus = this._makeSymbol(SymbolType.Error); + tag.marker = errplus; + tag.other = errminus; + row.insertSymbol(errplus, -1.0); // v = eplus - eminus + row.insertSymbol(errminus, 1.0); // v - eplus + eminus = 0 + objective.insertSymbol(errplus, strength); + objective.insertSymbol(errminus, strength); + } + else { + var dummy = this._makeSymbol(SymbolType.Dummy); + tag.marker = dummy; + row.insertSymbol(dummy); + } + break; + } + } + // Ensure the row has a positive constant. + if (row.constant() < 0.0) { + row.reverseSign(); + } + return { row: row, tag: tag }; + }; + /** + * Choose the subject for solving for the row. + * + * This method will choose the best subject for using as the solve + * target for the row. An invalid symbol will be returned if there + * is no valid target. + * + * The symbols are chosen according to the following precedence: + * + * 1) The first symbol representing an external variable. + * 2) A negative slack or error tag variable. + * + * If a subject cannot be found, an invalid symbol will be returned. + * + * @private + */ + Solver.prototype._chooseSubject = function (row, tag) { + var cells = row.cells(); + for (var i = 0, n = cells.size(); i < n; ++i) { + var pair = cells.itemAt(i); + if (pair.first.type() === SymbolType.External) { + return pair.first; + } + } + var type = tag.marker.type(); + if (type === SymbolType.Slack || type === SymbolType.Error) { + if (row.coefficientFor(tag.marker) < 0.0) { + return tag.marker; + } + } + type = tag.other.type(); + if (type === SymbolType.Slack || type === SymbolType.Error) { + if (row.coefficientFor(tag.other) < 0.0) { + return tag.other; + } + } + return INVALID_SYMBOL; + }; + /** + * Add the row to the tableau using an artificial variable. + * + * This will return false if the constraint cannot be satisfied. + * + * @private + */ + Solver.prototype._addWithArtificialVariable = function (row) { + // Create and add the artificial variable to the tableau. + var art = this._makeSymbol(SymbolType.Slack); + this._rowMap.insert(art, row.copy()); + this._artificial = row.copy(); + // Optimize the artificial objective. This is successful + // only if the artificial objective is optimized to zero. + this._optimize(this._artificial); + var success = nearZero(this._artificial.constant()); + this._artificial = null; + // If the artificial variable is basic, pivot the row so that + // it becomes non-basic. If the row is constant, exit early. + var pair = this._rowMap.erase(art); + if (pair !== undefined) { + var basicRow = pair.second; + if (basicRow.isConstant()) { + return success; + } + var entering = this._anyPivotableSymbol(basicRow); + if (entering.type() === SymbolType.Invalid) { + return false; // unsatisfiable (will this ever happen?) + } + basicRow.solveForEx(art, entering); + this._substitute(entering, basicRow); + this._rowMap.insert(entering, basicRow); + } + // Remove the artificial variable from the tableau. + var rows = this._rowMap; + for (var i = 0, n = rows.size(); i < n; ++i) { + rows.itemAt(i).second.removeSymbol(art); + } + this._objective.removeSymbol(art); + return success; + }; + /** + * Substitute the parametric symbol with the given row. + * + * This method will substitute all instances of the parametric symbol + * in the tableau and the objective function with the given row. + * + * @private + */ + Solver.prototype._substitute = function (symbol, row) { + var rows = this._rowMap; + for (var i = 0, n = rows.size(); i < n; ++i) { + var pair = rows.itemAt(i); + pair.second.substitute(symbol, row); + if (pair.second.constant() < 0.0 && pair.first.type() !== SymbolType.External) { + this._infeasibleRows.push(pair.first); + } + } + this._objective.substitute(symbol, row); + if (this._artificial) { + this._artificial.substitute(symbol, row); + } + }; + /** + * Optimize the system for the given objective function. + * + * This method performs iterations of Phase 2 of the simplex method + * until the objective function reaches a minimum. + * + * @private + */ + Solver.prototype._optimize = function (objective) { + var iterations = 0; + while (iterations < this.maxIterations) { + var entering = this._getEnteringSymbol(objective); + if (entering.type() === SymbolType.Invalid) { + return; + } + var leaving = this._getLeavingSymbol(entering); + if (leaving.type() === SymbolType.Invalid) { + throw new Error('the objective is unbounded'); + } + // pivot the entering symbol into the basis + var row = this._rowMap.erase(leaving).second; + row.solveForEx(leaving, entering); + this._substitute(entering, row); + this._rowMap.insert(entering, row); + iterations++; + } + throw new Error('solver iterations exceeded'); + }; + /** + * Optimize the system using the dual of the simplex method. + * + * The current state of the system should be such that the objective + * function is optimal, but not feasible. This method will perform + * an iteration of the dual simplex method to make the solution both + * optimal and feasible. + * + * @private + */ + Solver.prototype._dualOptimize = function () { + var rows = this._rowMap; + var infeasible = this._infeasibleRows; + while (infeasible.length !== 0) { + var leaving = infeasible.pop(); + var pair = rows.find(leaving); + if (pair !== undefined && pair.second.constant() < 0.0) { + var entering = this._getDualEnteringSymbol(pair.second); + if (entering.type() === SymbolType.Invalid) { + throw new Error('dual optimize failed'); + } + // pivot the entering symbol into the basis + var row = pair.second; + rows.erase(leaving); + row.solveForEx(leaving, entering); + this._substitute(entering, row); + rows.insert(entering, row); + } + } + }; + /** + * Compute the entering variable for a pivot operation. + * + * This method will return first symbol in the objective function which + * is non-dummy and has a coefficient less than zero. If no symbol meets + * the criteria, it means the objective function is at a minimum, and an + * invalid symbol is returned. + * + * @private + */ + Solver.prototype._getEnteringSymbol = function (objective) { + var cells = objective.cells(); + for (var i = 0, n = cells.size(); i < n; ++i) { + var pair = cells.itemAt(i); + var symbol = pair.first; + if (pair.second < 0.0 && symbol.type() !== SymbolType.Dummy) { + return symbol; + } + } + return INVALID_SYMBOL; + }; + /** + * Compute the entering symbol for the dual optimize operation. + * + * This method will return the symbol in the row which has a positive + * coefficient and yields the minimum ratio for its respective symbol + * in the objective function. The provided row *must* be infeasible. + * If no symbol is found which meats the criteria, an invalid symbol + * is returned. + * + * @private + */ + Solver.prototype._getDualEnteringSymbol = function (row) { + var ratio = Number.MAX_VALUE; + var entering = INVALID_SYMBOL; + var cells = row.cells(); + for (var i = 0, n = cells.size(); i < n; ++i) { + var pair = cells.itemAt(i); + var symbol = pair.first; + var c = pair.second; + if (c > 0.0 && symbol.type() !== SymbolType.Dummy) { + var coeff = this._objective.coefficientFor(symbol); + var r = coeff / c; + if (r < ratio) { + ratio = r; + entering = symbol; + } + } + } + return entering; + }; + /** + * Compute the symbol for pivot exit row. + * + * This method will return the symbol for the exit row in the row + * map. If no appropriate exit symbol is found, an invalid symbol + * will be returned. This indicates that the objective function is + * unbounded. + * + * @private + */ + Solver.prototype._getLeavingSymbol = function (entering) { + var ratio = Number.MAX_VALUE; + var found = INVALID_SYMBOL; + var rows = this._rowMap; + for (var i = 0, n = rows.size(); i < n; ++i) { + var pair = rows.itemAt(i); + var symbol = pair.first; + if (symbol.type() !== SymbolType.External) { + var row = pair.second; + var temp = row.coefficientFor(entering); + if (temp < 0.0) { + var temp_ratio = -row.constant() / temp; + if (temp_ratio < ratio) { + ratio = temp_ratio; + found = symbol; + } + } + } + } + return found; + }; + /** + * Compute the leaving symbol for a marker variable. + * + * This method will return a symbol corresponding to a basic row + * which holds the given marker variable. The row will be chosen + * according to the following precedence: + * + * 1) The row with a restricted basic varible and a negative coefficient + * for the marker with the smallest ratio of -constant / coefficient. + * + * 2) The row with a restricted basic variable and the smallest ratio + * of constant / coefficient. + * + * 3) The last unrestricted row which contains the marker. + * + * If the marker does not exist in any row, an invalid symbol will be + * returned. This indicates an internal solver error since the marker + * *should* exist somewhere in the tableau. + * + * @private + */ + Solver.prototype._getMarkerLeavingSymbol = function (marker) { + var dmax = Number.MAX_VALUE; + var r1 = dmax; + var r2 = dmax; + var invalid = INVALID_SYMBOL; + var first = invalid; + var second = invalid; + var third = invalid; + var rows = this._rowMap; + for (var i = 0, n = rows.size(); i < n; ++i) { + var pair = rows.itemAt(i); + var row = pair.second; + var c = row.coefficientFor(marker); + if (c === 0.0) { + continue; + } + var symbol = pair.first; + if (symbol.type() === SymbolType.External) { + third = symbol; + } + else if (c < 0.0) { + var r = -row.constant() / c; + if (r < r1) { + r1 = r; + first = symbol; + } + } + else { + var r = row.constant() / c; + if (r < r2) { + r2 = r; + second = symbol; + } + } + } + if (first !== invalid) { + return first; + } + if (second !== invalid) { + return second; + } + return third; + }; + /** + * Remove the effects of a constraint on the objective function. + * + * @private + */ + Solver.prototype._removeConstraintEffects = function (cn, tag) { + if (tag.marker.type() === SymbolType.Error) { + this._removeMarkerEffects(tag.marker, cn.strength()); + } + if (tag.other.type() === SymbolType.Error) { + this._removeMarkerEffects(tag.other, cn.strength()); + } + }; + /** + * Remove the effects of an error marker on the objective function. + * + * @private + */ + Solver.prototype._removeMarkerEffects = function (marker, strength) { + var pair = this._rowMap.find(marker); + if (pair !== undefined) { + this._objective.insertRow(pair.second, -strength); + } + else { + this._objective.insertSymbol(marker, -strength); + } + }; + /** + * Get the first Slack or Error symbol in the row. + * + * If no such symbol is present, an invalid symbol will be returned. + * + * @private + */ + Solver.prototype._anyPivotableSymbol = function (row) { + var cells = row.cells(); + for (var i = 0, n = cells.size(); i < n; ++i) { + var pair = cells.itemAt(i); + var type = pair.first.type(); + if (type === SymbolType.Slack || type === SymbolType.Error) { + return pair.first; + } + } + return INVALID_SYMBOL; + }; + /** + * Returns a new Symbol of the given type. + * + * @private + */ + Solver.prototype._makeSymbol = function (type) { + return new Symbol(type, this._idTick++); + }; + return Solver; + }()); + /** + * Test whether a value is approximately zero. + * @private + */ + function nearZero(value) { + var eps = 1.0e-8; + return value < 0.0 ? -value < eps : value < eps; + } + /** + * An internal function for creating a constraint map. + * @private + */ + function createCnMap() { + return createMap(); + } + /** + * An internal function for creating a row map. + * @private + */ + function createRowMap() { + return createMap(); + } + /** + * An internal function for creating a variable map. + * @private + */ + function createVarMap() { + return createMap(); + } + /** + * An internal function for creating an edit map. + * @private + */ + function createEditMap() { + return createMap(); + } + /** + * An enum defining the available symbol types. + * @private + */ + var SymbolType; + (function (SymbolType) { + SymbolType[SymbolType["Invalid"] = 0] = "Invalid"; + SymbolType[SymbolType["External"] = 1] = "External"; + SymbolType[SymbolType["Slack"] = 2] = "Slack"; + SymbolType[SymbolType["Error"] = 3] = "Error"; + SymbolType[SymbolType["Dummy"] = 4] = "Dummy"; + })(SymbolType || (SymbolType = {})); + /** + * An internal class representing a symbol in the solver. + * @private + */ + var Symbol = /** @class */ (function () { + /** + * Construct a new Symbol + * + * @param [type] The type of the symbol. + * @param [id] The unique id number of the symbol. + */ + function Symbol(type, id) { + this._id = id; + this._type = type; + } + /** + * Returns the unique id number of the symbol. + */ + Symbol.prototype.id = function () { + return this._id; + }; + /** + * Returns the type of the symbol. + */ + Symbol.prototype.type = function () { + return this._type; + }; + return Symbol; + }()); + /** + * A static invalid symbol + * @private + */ + var INVALID_SYMBOL = new Symbol(SymbolType.Invalid, -1); + /** + * An internal row class used by the solver. + * @private + */ + var Row = /** @class */ (function () { + /** + * Construct a new Row. + */ + function Row(constant) { + if (constant === void 0) { constant = 0.0; } + this._cellMap = createMap(); + this._constant = constant; + } + /** + * Returns the mapping of symbols to coefficients. + */ + Row.prototype.cells = function () { + return this._cellMap; + }; + /** + * Returns the constant for the row. + */ + Row.prototype.constant = function () { + return this._constant; + }; + /** + * Returns true if the row is a constant value. + */ + Row.prototype.isConstant = function () { + return this._cellMap.empty(); + }; + /** + * Returns true if the Row has all dummy symbols. + */ + Row.prototype.allDummies = function () { + var cells = this._cellMap; + for (var i = 0, n = cells.size(); i < n; ++i) { + var pair = cells.itemAt(i); + if (pair.first.type() !== SymbolType.Dummy) { + return false; + } + } + return true; + }; + /** + * Create a copy of the row. + */ + Row.prototype.copy = function () { + var theCopy = new Row(this._constant); + theCopy._cellMap = this._cellMap.copy(); + return theCopy; + }; + /** + * Add a constant value to the row constant. + * + * Returns the new value of the constant. + */ + Row.prototype.add = function (value) { + return (this._constant += value); + }; + /** + * Insert the symbol into the row with the given coefficient. + * + * If the symbol already exists in the row, the coefficient + * will be added to the existing coefficient. If the resulting + * coefficient is zero, the symbol will be removed from the row. + */ + Row.prototype.insertSymbol = function (symbol, coefficient) { + if (coefficient === void 0) { coefficient = 1.0; } + var pair = this._cellMap.setDefault(symbol, function () { return 0.0; }); + if (nearZero((pair.second += coefficient))) { + this._cellMap.erase(symbol); + } + }; + /** + * Insert a row into this row with a given coefficient. + * + * The constant and the cells of the other row will be + * multiplied by the coefficient and added to this row. Any + * cell with a resulting coefficient of zero will be removed + * from the row. + */ + Row.prototype.insertRow = function (other, coefficient) { + if (coefficient === void 0) { coefficient = 1.0; } + this._constant += other._constant * coefficient; + var cells = other._cellMap; + for (var i = 0, n = cells.size(); i < n; ++i) { + var pair = cells.itemAt(i); + this.insertSymbol(pair.first, pair.second * coefficient); + } + }; + /** + * Remove a symbol from the row. + */ + Row.prototype.removeSymbol = function (symbol) { + this._cellMap.erase(symbol); + }; + /** + * Reverse the sign of the constant and cells in the row. + */ + Row.prototype.reverseSign = function () { + this._constant = -this._constant; + var cells = this._cellMap; + for (var i = 0, n = cells.size(); i < n; ++i) { + var pair = cells.itemAt(i); + pair.second = -pair.second; + } + }; + /** + * Solve the row for the given symbol. + * + * This method assumes the row is of the form + * a * x + b * y + c = 0 and (assuming solve for x) will modify + * the row to represent the right hand side of + * x = -b/a * y - c / a. The target symbol will be removed from + * the row, and the constant and other cells will be multiplied + * by the negative inverse of the target coefficient. + * + * The given symbol *must* exist in the row. + */ + Row.prototype.solveFor = function (symbol) { + var cells = this._cellMap; + var pair = cells.erase(symbol); + var coeff = -1.0 / pair.second; + this._constant *= coeff; + for (var i = 0, n = cells.size(); i < n; ++i) { + cells.itemAt(i).second *= coeff; + } + }; + /** + * Solve the row for the given symbols. + * + * This method assumes the row is of the form + * x = b * y + c and will solve the row such that + * y = x / b - c / b. The rhs symbol will be removed from the + * row, the lhs added, and the result divided by the negative + * inverse of the rhs coefficient. + * + * The lhs symbol *must not* exist in the row, and the rhs + * symbol must* exist in the row. + */ + Row.prototype.solveForEx = function (lhs, rhs) { + this.insertSymbol(lhs, -1.0); + this.solveFor(rhs); + }; + /** + * Returns the coefficient for the given symbol. + */ + Row.prototype.coefficientFor = function (symbol) { + var pair = this._cellMap.find(symbol); + return pair !== undefined ? pair.second : 0.0; + }; + /** + * Substitute a symbol with the data from another row. + * + * Given a row of the form a * x + b and a substitution of the + * form x = 3 * y + c the row will be updated to reflect the + * expression 3 * a * y + a * c + b. + * + * If the symbol does not exist in the row, this is a no-op. + */ + Row.prototype.substitute = function (symbol, row) { + var pair = this._cellMap.erase(symbol); + if (pair !== undefined) { + this.insertRow(row, pair.second); + } + }; + return Row; + }()); + + exports.Constraint = Constraint; + exports.Expression = Expression; + exports.Solver = Solver; + exports.Strength = Strength; + exports.Variable = Variable; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); diff --git a/lib/kiwi.min.js b/lib/kiwi.min.js new file mode 100644 index 0000000..38cbe72 --- /dev/null +++ b/lib/kiwi.min.js @@ -0,0 +1,9 @@ + +/*----------------------------------------------------------------------------- +| Copyright (c) 2014-2019, Nucleic Development Team & H. Rutjes & Lume. +| +| Distributed under the terms of the Modified BSD License. +| +| The full license is in the file COPYING.txt, distributed with this software. +-----------------------------------------------------------------------------*/ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).kiwi={})}(this,function(d){"use strict";function f(){return new t}i.prototype.size=function(){return this.array.length},i.prototype.empty=function(){return 0===this.array.length},i.prototype.itemAt=function(t){return this.array[t]},i.prototype.contains=function(t){return void 0!==this.index[t.id()]},i.prototype.find=function(t){t=this.index[t.id()];return void 0===t?void 0:this.array[t]},i.prototype.setDefault=function(t,e){var r=this.index[t.id()];return void 0===r?(e=new o(t,e()),this.index[t.id()]=this.array.length,this.array.push(e),e):this.array[r]},i.prototype.insert=function(t,e){var e=new o(t,e),r=this.index[t.id()];return void 0===r?(this.index[t.id()]=this.array.length,this.array.push(e)):this.array[r]=e,e},i.prototype.erase=function(t){var e,r=this.index[t.id()];if(void 0!==r)return this.index[t.id()]=void 0,(t=this.array[r])!==(e=this.array.pop())&&(this.array[r]=e,this.index[e.first.id()]=r),t},i.prototype.copy=function(){for(var t=new i,e=0;e=","="][this._operator]+" 0 ("+this._strength.toString()+")"};var c=p;function p(t,e,r,i){void 0===i&&(i=v.required),this._id=h++,this._operator=e,this._strength=v.clip(i),void 0===r&&t instanceof y?this._expression=t:this._expression=t.minus(r)}var _,u,h=0,m=(b.prototype.createConstraint=function(t,e,r,i){void 0===i&&(i=v.required);t=new c(t,e,r,i);return this.addConstraint(t),t},b.prototype.addConstraint=function(t){if(void 0!==this._cnMap.find(t))throw new Error("duplicate constraint");var e=this._createRow(t),r=e.row,e=e.tag,i=this._chooseSubject(r,e);if(i.type()===_.Invalid&&r.allDummies()){if(!w(r.constant()))throw new Error("unsatisfiable constraint");i=e.marker}if(i.type()===_.Invalid){if(!this._addWithArtificialVariable(r))throw new Error("unsatisfiable constraint")}else r.solveFor(i),this._substitute(i,r),this._rowMap.insert(i,r);this._cnMap.insert(t,e),this._optimize(this._objective)},b.prototype.removeConstraint=function(t){var e=this._cnMap.erase(t);if(void 0===e)throw new Error("unknown constraint");this._removeConstraintEffects(t,e.second);t=e.second.marker,e=this._rowMap.erase(t);if(void 0===e){var r=this._getMarkerLeavingSymbol(t);if(r.type()===_.Invalid)throw new Error("failed to find leaving row");(e=this._rowMap.erase(r)).second.solveForEx(r,t),this._substitute(t,e.second)}this._optimize(this._objective)},b.prototype.hasConstraint=function(t){return this._cnMap.contains(t)},b.prototype.addEditVariable=function(t,e){if(void 0!==this._editMap.find(t))throw new Error("duplicate edit variable");if((e=v.clip(e))===v.required)throw new Error("bad required strength");var r=new y(t),r=new c(r,d.Operator.Eq,void 0,e),e=(this.addConstraint(r),this._cnMap.find(r).second);this._editMap.insert(t,{tag:e,constraint:r,constant:0})},b.prototype.removeEditVariable=function(t){t=this._editMap.erase(t);if(void 0===t)throw new Error("unknown edit variable");this.removeConstraint(t.second.constraint)},b.prototype.hasEditVariable=function(t){return this._editMap.contains(t)},b.prototype.suggestValue=function(t,e){t=this._editMap.find(t);if(void 0===t)throw new Error("unknown edit variable");var r=this._rowMap,t=t.second,i=e-t.constant,o=(t.constant=e,t.tag.marker);if(void 0!==(e=r.find(o)))e.second.add(-i)<0&&this._infeasibleRows.push(o);else if(t=t.tag.other,void 0!==(e=r.find(t)))e.second.add(i)<0&&this._infeasibleRows.push(t);else for(var n=0,s=r.size();n docs/Kiwi.md && node scripts/docs-update-headings.js && prettier docs/Kiwi.md --write", "lint": "prettier . --check", - "bench": "node bench/main.cjs", - "prepare": "npm run build" + "bench": "node bench/main.cjs" } }