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" } }