nowjs-core
Version:
NowCanDo Javascript Core [nowjs-core] is a library written by TypeScript code maintains under Apache 2.0 licence
1,176 lines (1,175 loc) • 35.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const index_1 = require("./index");
function getUniqueArray(arr) {
const result = [];
const hash = {};
if (typeof arr !== 'object' || !arr.length) {
return result;
}
for (let i = 0, len = arr.length; i < len; i++) {
if (!hash[arr[i]]) {
result.push(arr[i]);
hash[arr[i]] = 1;
}
}
return result;
}
function convertArrayValuesToHashMap(arr) {
if (!arr || typeof arr !== 'object') {
return {};
}
const obj = {};
for (let i = 0, len = arr.length; i < len; i++) {
obj[arr[i]] = 1;
}
return obj;
}
function sortArrayWithSubsetAtEnd(arr, subset) {
if (!arr || typeof arr !== 'object' || !subset || typeof subset !== 'object') {
return [];
}
const list = [];
const hash = convertArrayValuesToHashMap(subset);
for (let i = 0, len = arr.length; i < len; i++) {
if (!hash[arr[i]]) {
list.push(arr[i]);
}
}
return list.sort().concat(subset.sort());
}
function areObjectsSame(obj1, obj2) {
let a;
let b;
if (obj1 === obj2) {
return true;
}
if (!(obj1 instanceof obj2.constructor)) {
return false;
}
for (const prop in obj1) {
if (!obj1.hasOwnProperty(prop)) {
continue;
}
a = obj1[prop];
b = obj2[prop];
if (typeof a === 'object') {
if (typeof a !== typeof b) {
return false;
}
if (!areObjectsSame(a, b)) {
return false;
}
}
else {
if (a.toString() !== b.toString()) {
return false;
}
}
}
return true;
}
const STANDARD_MAX = 'standardMax';
const STANDARD_MIN = 'standardMin';
const NONSTANDARD_MIN = 'nonstandardMin';
const NONSTANDARD_MAX = 'nonstandardMax';
class Simplex {
constructor(problem) {
this.problem = problem;
this.checkForErrors(problem);
this.input = new SimplexInput(problem.Type, problem.Objective, problem.Constraints);
}
get Definition() {
return this.Definition;
}
solve() {
return this.solveInternal();
}
static solve(problem) {
const simplex = new Simplex(problem);
return simplex.solve();
}
checkForErrors(obj) {
const errMsg = Simplex.getErrors(obj);
if (errMsg) {
throw new Error(errMsg);
}
}
static getErrors(obj) {
if (typeof obj !== 'object') {
return 'An object must be passed to Simplex.solve()';
}
if (!obj.Type || !obj.Objective || !obj.Constraints) {
return 'The object must have the properties `type`, `objective` and `constraints`.';
}
}
solveInternal() {
this.tableau = new SimplexTableau(this.input);
this.output = this.tableau.solve().getOutput();
return this.output;
}
}
exports.Simplex = Simplex;
class SimplexMatrix extends index_1.NumericMatrix {
constructor(...rows) {
super(0, 0, ...rows);
}
checkColumnIndex(col) { }
checkRowIndex(row) { }
checkIndexes(row, col) { }
get ColSize() {
return this.getSize()[1];
}
get RowSize() {
return this.getSize()[0];
}
getSize() {
let columns = 0;
const rows = this.arr.length;
let i = rows;
let x;
while (i--) {
x = this.arr[i].length;
columns = columns < x ? x : columns;
}
return [rows, columns];
}
static scaleRow(scale, row) {
if (!Array.isArray(row)) {
return row;
}
row = row.concat();
for (let i = 0, len = row.length; i < len; i++) {
row[i] *= scale;
}
return row;
}
static addRows(rowA, rowB) {
return SimplexMatrix.scaleThenAddRows(1, rowA, 1, rowB);
}
static scaleThenAddRows(scaleA, rowA, scaleB, rowB) {
rowA = Array.isArray(rowA) ? rowA.concat() : [];
rowB = Array.isArray(rowB) ? rowB.concat() : [];
const len = Math.max(rowA.length, rowB.length);
for (let i = 0; i < len; i++) {
rowA[i] = scaleA * (rowA[i] || 0) + scaleB * (rowB[i] || 0);
}
return rowA;
}
static inverseArray(arr) {
if (!Array.isArray(arr)) {
return arr;
}
let i = arr.length;
while (i--) {
arr[i] = -arr[i];
}
return arr;
}
toString() {
let str = '';
this.forEachRow((i, row) => {
if (i) {
str += ',';
}
str += '[' + row.toString() + ']';
});
str = '[' + str + ']';
return str;
}
setUniformedWidth() {
const info = SimplexMatrix.getMaxArray(this.arr);
for (let i = 0, len = this.arr.length; i < len; i++) {
this.arr[i].length = info.max;
}
return this;
}
getGreatestValueFromLastRow(isPositive) {
if (!this.arr || this.arr.length < 1) {
return -1;
}
const row = this.arr[this.arr.length - 1];
const obj = SimplexMatrix.getGreatestElementInRow(row, row.length - 1, !!isPositive);
if (isPositive) {
return -1 < obj.value ? obj.index : -1;
}
else {
return obj.value < 0 ? obj.index : -1;
}
}
getRowIndexWithPosMinColumnRatio(colI, excludeLastRow) {
const obj = {
rowIndex: -1,
minValue: Infinity,
};
const len = this.arr.length + (excludeLastRow ? -1 : 0);
let val;
let row;
if (colI < 0 || this.arr[0].length <= colI) {
return null;
}
for (let i = 0; i < len; i++) {
row = this.arr[i];
val = row[row.length - 1] / row[colI];
if (0 <= val && val < obj.minValue) {
obj.rowIndex = i;
obj.minValue = val;
}
}
return obj;
}
getUnitValueForColumn(colI) {
let nonZeroValues = 0;
let val = 0;
this.forEachRow((i, row) => {
if (row[colI] === 1) {
val = row[row.length - 1];
}
if (row[colI]) {
nonZeroValues++;
}
});
val = nonZeroValues === 1 ? val : 0;
return val;
}
getLastElementOnLastRow() {
const row = this.arr[this.arr.length - 1];
return row[row.length - 1];
}
static getGreatestElementInRow(arr, excludeIndex, findPositive) {
if (!arr || !Array.isArray(arr)) {
return null;
}
const obj = {
value: Infinity * (findPositive ? -1 : 1),
index: -1,
};
for (let i = 0, len = arr.length; i < len; i++) {
if (excludeIndex === i) {
continue;
}
if ((findPositive && obj.value < arr[i]) || (!findPositive && arr[i] < obj.value)) {
obj.index = i;
obj.value = arr[i];
}
}
return obj;
}
addRow(arr) {
arr = Array.isArray(arr) ? arr : [arr];
this.arr.push(arr);
return this;
}
addToRow(iRow, els) {
if (this.arr[iRow]) {
this.arr[iRow] = (this.arr[iRow] || []).concat(els);
}
else {
this.addRow(els);
}
return this;
}
equals(obj) {
return obj && obj instanceof SimplexMatrix && this.toString() === obj.toString();
}
static getMaxArray(arrays) {
const obj = {
index: 0,
max: 0,
};
if (!Array.isArray(arrays)) {
return null;
}
if (!Array.isArray(arrays[0])) {
obj.max = arrays.length;
return obj;
}
let i = arrays.length;
while (i--) {
if (obj.max < arrays[i].length) {
obj.index = i;
obj.max = arrays[i].length;
}
}
return obj;
}
scaleRow(scaleA, rowI) {
const row = this.arr[rowI] || [];
for (let i = 0, len = row.length; i < len; i++) {
row[i] *= scaleA;
}
return this;
}
pivot(rowI, colI) {
if (!this.arr[rowI]) {
return this;
}
const x = this.getElement(rowI, colI);
let val;
let pRow;
this.scaleRow(1 / x, rowI);
pRow = this.arr[rowI];
for (let i = 0, len = this.arr.length; i < len; i++) {
if (i === rowI) {
continue;
}
val = this.getElement(i, colI);
this.arr[i] = SimplexMatrix.scaleThenAddRows(-val, pRow, 1, this.arr[i]);
}
return this;
}
}
exports.SimplexMatrix = SimplexMatrix;
class SimplexExpression {
constructor(str) {
this.terms = null;
this.terms = {};
if (typeof str !== 'string' || !str.length) {
return;
}
SimplexExpression.checkString(str);
str = SimplexExpression.addSpaceBetweenTerms(str);
this.terms = SimplexExpression.convertExpressionToObject(str);
}
get Terms() {
return this.terms;
}
static encodeE(str) {
str = (str || '').toString();
str = str.replace(/(\de)([+])(\d)/gi, '$1_plus_$3');
str = str.replace(/(\de)([\-])(\d)/gi, '$1_sub_$3');
return str;
}
static decodeE(str) {
str = (str || '').toString();
str = str.replace(/_plus_/g, '+');
str = str.replace(/_sub_/g, '-');
return str;
}
static hasManyCompares(str) {
const RE_compares = /[<>]=?|=/g;
const matches = ('' + str).replace(/\s/g, '').match(RE_compares) || [];
return 1 < matches.length;
}
static addSpaceBetweenTerms(str) {
str = SimplexExpression.encodeE(str);
str = str.replace(/([\+\-])/g, ' $1 ');
str = str.replace(/\s{2,}/g, ' ');
str = str.trim();
str = SimplexExpression.decodeE(str);
return str;
}
static hasExcludedOperations(str) {
return /[\*\/%]/.test(str);
}
static hasIncompleteBinaryOperator(str) {
let hasError;
const noSpaceStr = ('' + str).replace(/\s/g, '');
const RE_hasNoPlusOrMinus = /^[^\+\-]+$/;
const RE_noLeftAndRightTerms = /[\+\-]{2}|[\+\-]$/;
const hasMoreThanOneTerm = /\S+\s+\S+/.test(str);
hasError = hasMoreThanOneTerm && RE_hasNoPlusOrMinus.test(noSpaceStr);
hasError = hasError || RE_noLeftAndRightTerms.test(noSpaceStr);
return hasError;
}
static hasComparison(str) {
return /[><=]/.test(str);
}
static getErrorMessage(str) {
let errMsg;
if (SimplexExpression.hasComparison(str)) {
errMsg = 'Comparison are not allowed within an expression.';
}
if (!errMsg && SimplexExpression.hasExcludedOperations(str)) {
errMsg = 'Addition and subtraction are only supported.';
}
if (!errMsg && SimplexExpression.hasIncompleteBinaryOperator(str)) {
errMsg = 'Exactly one math operators must be between terms.\n Good:(a+b). Bad:(a++ b+).';
}
if (errMsg) {
errMsg += '\n Input: `' + str + '`';
}
return errMsg;
}
static checkString(str) {
const errMsg = SimplexExpression.getErrorMessage(str);
if (errMsg) {
throw new Error(errMsg);
}
}
static extractComponentsFromVariable(str) {
str = '' + str;
const re = /^[\+\-]?\d+(\.\d+)?(e[\+\-]?\d+)?/i;
let coeff = '' + (str.match(re) || [''])[0];
let term = str.replace(re, '') || '1';
if (+str === 0) {
coeff = 0;
}
if (coeff === '') {
coeff = /^\-/.test(term) ? -1 : 1;
term = term.replace(/^[\+\-]/, '');
}
return [+coeff, term];
}
static splitStrByTerms(str) {
const RE_findSignForTerm = /([\+\-])\s+/g;
const RE_spaceOrPlus = /\s+[\+]?/;
return ('' + str)
.replace(/^\s*\+/, '')
.replace(RE_findSignForTerm, '$1')
.split(RE_spaceOrPlus);
}
static convertExpressionToObject(str) {
let term;
const obj = {};
const matches = SimplexExpression.splitStrByTerms((str || '').trim());
let i = matches.length;
while (i--) {
term = SimplexExpression.extractComponentsFromVariable(matches[i]);
if (!term[0]) {
term = [0, 1];
}
obj[term[1]] = obj[term[1]] ? obj[term[1]] + term[0] : term[0];
}
return obj;
}
static termAtIndex(i, name, value) {
let result = '';
if (value) {
if (value < 0) {
result += value === -1 ? '-' : value;
}
else {
if (i) {
result += '+';
}
result += value === 1 ? '' : value;
}
result += name;
}
else {
result += 0 < name && i ? '+' : '';
result += name;
}
return result;
}
getTermNames(excludeNumbers, excludeSlack) {
const obj = this.terms;
let terms = [];
let key;
const RE_slack = /^slack\d*$/i;
for (key in obj) {
if (!obj.hasOwnProperty(key) || (excludeSlack && RE_slack.test(key))) {
continue;
}
if (isNaN(key)) {
terms.push(key);
}
}
terms = terms.sort();
if (!excludeNumbers && obj && obj[1]) {
terms.push(obj[1].toString());
}
return terms;
}
forEachTerm(fn) {
if (typeof fn !== 'function') {
return;
}
for (const prop in this.terms) {
if (this.terms.hasOwnProperty(prop)) {
fn(prop, this.terms[prop], this.terms);
}
}
}
forEachConstant(fn) {
if (typeof fn !== 'function') {
return;
}
const prop = '1';
if (this.terms[prop]) {
fn(prop, this.terms[prop], this.terms);
}
}
forEachVariable(fn) {
if (typeof fn !== 'function') {
return;
}
for (const prop in this.terms) {
if (this.terms.hasOwnProperty(prop) && prop !== '1') {
fn(prop, this.terms[prop], this.terms);
}
}
}
toString() {
const arr = [];
const names = this.getTermNames();
let i;
let name;
let len;
const func = SimplexExpression.termAtIndex;
if (!names.length) {
return '0';
}
for (i = 0, len = names.length; i < len; i++) {
name = names[i];
arr.push(func(i, name, this.terms[name]));
}
return arr.join(' ').replace(/\s[\+\-]/g, '$& ');
}
inverse() {
this.forEachTerm((termName, value, terms) => {
terms[termName] = -value;
});
return this;
}
addTerm(name, value) {
if (typeof value !== 'undefined') {
value += this.terms[name] || 0;
if (value) {
this.terms[name] = value;
}
else {
this.removeTerm(name);
}
}
else {
this.addExpression(name);
}
return this;
}
setTerm(name, value) {
if (value) {
this.terms[name] = value;
}
else {
this.removeTerm(name);
}
return this;
}
addExpression(obj) {
if (!(obj instanceof SimplexExpression)) {
obj = new SimplexExpression(obj);
}
this.addTerms(obj.toTermValueArray());
return this;
}
toTermValueArray() {
const arr = [];
for (const name in this.terms) {
if (this.terms.hasOwnProperty(name)) {
arr.push([name, this.terms[name]]);
}
}
return arr;
}
addTerms(arr) {
if (!arr || typeof arr !== 'object') {
return this;
}
for (let i = 0, len = arr.length; i < len; i++) {
if (arr[i] && typeof arr[i] === 'object') {
this.addTerm(arr[i][0], arr[i][1]);
}
}
return this;
}
removeTerm(name) {
delete this.terms[name];
return this;
}
scale(factor) {
factor = +factor;
this.forEachTerm((name, value, terms) => {
terms[name] = factor * value;
});
return this;
}
hasTerm(name) {
return !!this.terms[name];
}
getTermValue(name) {
return this.terms[name];
}
getAllCoeffients(excludeNumbers, excludeSlack) {
const arr = [];
const names = this.getTermNames(excludeNumbers, excludeSlack);
for (let i = 0, len = names.length; i < len; i++) {
arr.push(+(this.terms[names[i]] || names[i]));
}
return arr;
}
getCoefficients(termNames) {
const arr = [];
let i = termNames.length;
while (i--) {
arr[i] = this.terms[termNames[i]] || 0;
}
return arr;
}
clon() {
return new SimplexExpression(this.toString());
}
}
exports.SimplexExpression = SimplexExpression;
class SimplexConstraint {
constructor(str) {
this.comparison = '';
this.specialTerms = {};
const obj = SimplexConstraint.parseToObject(str);
if (obj) {
this.comparison = obj.comparison;
this.leftSide = obj.lhs;
this.rightSide = obj.rhs;
}
this.specialTerms = {};
}
equals(obj) {
return areObjectsSame(this, obj);
}
get LeftSide() {
return this.leftSide;
}
get RightSide() {
return this.rightSide;
}
get Comparison() {
return this.comparison;
}
static hasManyCompares(str) {
const RE_compares = /[<>]=?|=/g;
const matches = ('' + str).replace(/\s/g, '').match(RE_compares) || [];
return 1 < matches.length;
}
static hasIncompleteBinaryOperator(str) {
str = str.replace(/\s{2,}/g, '');
const noSpaceStr = ('' + str).replace(/\s/g, '');
const hasNoOperatorBetweenValues = /[^+\-><=]\s+[^+\-><=]/.test('' + str);
const RE_noLeftAndRightTerms = /[+\-][><=+\-]|[><=+\-]$/;
return RE_noLeftAndRightTerms.test(noSpaceStr) || hasNoOperatorBetweenValues;
}
static getErrorMessage(str) {
let errMsg;
if (SimplexConstraint.hasManyCompares(str)) {
errMsg = 'Only 1 comparision (<,>,=, >=, <=) is allow in a Constraint.';
}
if (!errMsg && SimplexConstraint.hasIncompleteBinaryOperator(str)) {
errMsg = 'Math operators must be in between terms. Good:(a+b=c). Bad:(a b+=c)';
}
return errMsg;
}
static checkInput(str) {
const errMsg = SimplexConstraint.getErrorMessage(str);
if (errMsg) {
throw new Error(errMsg);
}
}
static switchSides(sideA, sideB, forEachTermFunc) {
forEachTermFunc.call(sideA, (name, value) => {
sideB.addTerm(name, -value);
sideA.removeTerm(name);
});
}
getTermNames(excludeNumbers) {
const arr = [].concat(this.leftSide.getTermNames(excludeNumbers), this.rightSide.getTermNames(excludeNumbers));
return getUniqueArray(arr);
}
static parseToObject(str) {
str = str.replace(/([><])(\s+)(=)/g, '$1$3');
SimplexConstraint.checkInput(str);
const RE_comparison = /[><]=?|=/;
const arr = ('' + str).split(RE_comparison);
const obj = {
rhs: new SimplexExpression('0'),
comparison: '=',
};
obj.lhs = new SimplexExpression(arr[0]);
if (1 < arr.length) {
obj.rhs = new SimplexExpression(arr[1]);
obj.comparison = '' + RE_comparison.exec(str);
}
return obj;
}
static parse(str) {
const obj = SimplexConstraint.parseToObject(str);
let e;
if (obj) {
e = new SimplexConstraint();
e.comparison = obj.comparison;
e.leftSide = obj.lhs;
e.rightSide = obj.rhs;
}
return e;
}
toString() {
return [this.leftSide, this.comparison, this.rightSide].join(' ');
}
getSwappedSides(doSwap) {
return {
a: !doSwap ? this.leftSide : this.rightSide,
b: doSwap ? this.leftSide : this.rightSide,
};
}
moveTypeToOneSide(varSide, numSide) {
let varSides;
let numSides;
if (/left|right/.test(varSide)) {
varSides = this.getSwappedSides(/left/.test(varSide));
SimplexConstraint.switchSides(varSides.a, varSides.b, varSides.a.forEachVariable);
}
if (/left|right/.test(numSide)) {
numSides = this.getSwappedSides(/left/.test(numSide));
SimplexConstraint.switchSides(numSides.a, numSides.b, numSides.a.forEachConstant);
}
return this;
}
inverse() {
const oppositeCompare = {
'=': '=',
'>=': '<',
'>': '<=',
'<=': '>',
'<': '>=',
};
if (oppositeCompare[this.comparison]) {
this.comparison = oppositeCompare[this.comparison];
this.leftSide.inverse();
this.rightSide.inverse();
}
return this;
}
removeStrictInequality() {
let eps;
if (/^[<>]$/.test(this.comparison)) {
eps = SimplexConstraint.EPSILON * ('>' === this.comparison ? 1 : -1);
this.rightSide.addTerm('1', eps);
this.comparison += '=';
}
return this;
}
normalize() {
this.moveTypeToOneSide('left', 'right');
if (this.rightSide.getTermValue('1') < 0) {
this.inverse();
}
return this.removeStrictInequality();
}
addSlack(val) {
this.setSpecialTerm({
key: 'slack',
name: 'slack',
value: val,
});
return this;
}
setSpecialTerm(obj) {
if (!obj || typeof obj !== 'object' || !obj.name || !obj.key) {
return this;
}
this.specialTerms[obj.key] = this.specialTerms[obj.key] || {};
const oldName = this.specialTerms[obj.key].name;
if (oldName) {
if (typeof obj.value === 'undefined') {
obj.value = this.leftSide.getTermValue(oldName);
}
this.leftSide.removeTerm(oldName);
}
this.specialTerms[obj.key].name = obj.name;
this.leftSide.setTerm(this.specialTerms[obj.key].name, +obj.value);
return this;
}
addArtificalVariable(val) {
this.setSpecialTerm({
key: 'artifical',
name: 'artifical',
value: val,
});
return this;
}
hasSpecialTerm(name) {
return !!this.specialTerms[name];
}
renameSlack(name) {
this.setSpecialTerm({
key: 'slack',
name,
});
return this;
}
renameArtificial(name) {
this.setSpecialTerm({
key: 'artifical',
name,
});
return this;
}
convertToEquation() {
this.normalize();
switch (this.comparison) {
case '<=':
this.addSlack(1);
break;
case '>=':
this.addSlack(-1);
this.addArtificalVariable(1);
break;
}
this.comparison = '=';
return this;
}
getSpecialTermNames() {
const names = [];
for (const prop in this.specialTerms) {
if (this.specialTerms.hasOwnProperty(prop) && this.specialTerms[prop]) {
names.push(this.specialTerms[prop].name);
}
}
return names.length ? names : null;
}
getSpecialTermValue(name) {
const obj = this.specialTerms[name];
if (!obj) {
return null;
}
return this.getCoefficients([obj.name])[0];
}
getArtificalName() {
const obj = this.specialTerms.artifical;
if (!obj) {
return null;
}
return obj.name;
}
scale(factor) {
this.leftSide.scale(factor);
this.rightSide.scale(factor);
return this;
}
varSwitchSide(name, moveTo) {
if (!/left|right/.test(moveTo)) {
return this;
}
name = isNaN(name) ? name : '1';
const sideA = 'left' === moveTo ? this.rightSide : this.leftSide;
const sideB = 'left' !== moveTo ? this.rightSide : this.leftSide;
if (sideA.hasTerm(name)) {
sideB.addTerm(name, -sideA.getTermValue(name));
sideA.removeTerm(name);
}
return this;
}
getCoefficients(termNames) {
if (!termNames) {
return null;
}
const arr = new Array(termNames.length);
let val;
let i = arr.length;
while (i--) {
val = this.leftSide.getTermValue(termNames[i]);
if (val === undefined) {
val = this.rightSide.getTermValue(termNames[i]);
}
arr[i] = val || 0;
}
return arr;
}
getTermValuesFromLeftSide(termNames) {
if (!termNames) {
return null;
}
const arr = new Array(termNames.length);
let val;
let i = arr.length;
while (i--) {
val = this.leftSide.getTermValue(termNames[i]);
if (val === undefined) {
val = -this.rightSide.getTermValue(termNames[i]);
}
arr[i] = val || 0;
}
return arr;
}
}
exports.SimplexConstraint = SimplexConstraint;
SimplexConstraint.EPSILON = 1e-6;
class SimplexTableau {
constructor(input) {
this.input = null;
this.colNames = [];
this.matrix = null;
this.limit = 1e4;
this.cycles = 0;
this.input = input;
this.input.convertToStandardForm();
this.setMatrixFromInput();
}
static getErrorMessage(input) {
if (!(input instanceof SimplexInput)) {
return 'Must pass an instance of the Input class.';
}
}
checkForError(input) {
const errMsg = SimplexTableau.getErrorMessage(input);
if (errMsg) {
throw new Error(errMsg);
}
}
addZToMatrix(termNames) {
const b = new SimplexConstraint('0 = ' + this.input.Z.toString());
b.moveTypeToOneSide('left', 'right');
let row = b.LeftSide.getCoefficients(termNames);
row = row.concat(b.RightSide.getTermValue('1') || 0);
this.matrix.addRow(row);
}
addConstraintsToMatrix(termNames) {
const constraints = this.input.Constraints;
for (let i = 0, len = constraints.length; i < len; i++) {
this.matrix.addRow(constraints[i].getCoefficients(termNames));
}
}
getSortedTermNames() {
const termNames = this.input.getTermNames(true);
const specialNames = this.input.getAllSpecialTermNames();
return sortArrayWithSubsetAtEnd(termNames, specialNames);
}
setMatrixFromInput() {
this.matrix = new SimplexMatrix();
this.colNames = this.getSortedTermNames();
this.addConstraintsToMatrix(this.colNames.concat('1'));
this.addZToMatrix(this.colNames);
}
solve(isMin) {
let point = this.getPivotPoint(this.matrix, isMin);
let limit = this.limit;
while (point && limit--) {
this.matrix.pivot(point.row, point.column);
point = this.getPivotPoint(this.matrix, isMin);
this.cycles++;
}
return this;
}
getPivotPoint(matrix, isMin) {
if (!(matrix instanceof SimplexMatrix)) {
return null;
}
const point = { column: 0, row: 0 };
point.column = matrix.getGreatestValueFromLastRow(!!isMin);
const obj = matrix.getRowIndexWithPosMinColumnRatio(point.column, true) || { rowIndex: -1, minValue: Infinity };
point.row = obj.rowIndex;
if (point.column < 0 || point.row < 0) {
return null;
}
return point;
}
getOutput() {
const obj = { z: 0 };
const names = this.colNames.concat();
for (let i = 0, len = names.length; i < len; i++) {
obj[names[i]] = this.matrix.getUnitValueForColumn(i);
}
obj.z = this.matrix.getLastElementOnLastRow();
return new SimplexOutput(obj);
}
toString() {
let result = '';
if (this.matrix) {
result += '[' + this.colNames.concat('Constant').toString() + '],';
result += this.matrix.toString();
}
return result;
}
}
exports.SimplexTableau = SimplexTableau;
class SimplexInput {
constructor(type, z, constraints) {
this.z = null;
this.type = null;
this.terms = [];
this.constraints = [];
this.isStandardMode = false;
this.type = type;
this.z = new SimplexExpression(z);
this.constraints = this.getArrOfConstraints(constraints);
this.setTermNames();
this.checkConstraints();
}
getZTermNotInAnyOfTheConstraints() {
let varMissing = '';
const terms = this.z.getTermNames();
let term;
let i = 0;
const iLen = terms.length;
for (; !varMissing && i < iLen; i++) {
term = terms[i];
let j = 0;
const jLen = this.constraints.length;
for (; j < jLen; j++) {
if (this.constraints[j].LeftSide.Terms[term]) {
break;
}
}
if (j === jLen) {
varMissing = term;
}
}
return varMissing;
}
checkConstraints() {
const errMsg = [];
const missingZVar = this.getZTermNotInAnyOfTheConstraints();
if (missingZVar) {
errMsg.push('`' + missingZVar + '`, from the objective function, should appear least once in a constraint.');
}
return errMsg;
}
getArrOfConstraints(arr) {
arr = Array.isArray(arr) ? arr : [arr];
const constraints = [];
let i = arr.length;
while (i--) {
constraints[i] = new SimplexConstraint(arr[i]);
}
return constraints;
}
computeType() {
const hasLessThan = this.doAnyConstrainsHaveRelation(/<=?/);
const hasGreaterThan = this.doAnyConstrainsHaveRelation(/>=?/);
if (/max/.test(this.type)) {
return hasGreaterThan ? NONSTANDARD_MAX : STANDARD_MAX;
}
if (/min/.test(this.type)) {
return hasLessThan ? NONSTANDARD_MIN : STANDARD_MIN;
}
}
doAnyConstrainsHaveRelation(comparison) {
if (!comparison) {
return false;
}
const comparisoned = new RegExp(comparison);
return this.anyConstraints((i, constraint) => {
return comparisoned.test(constraint.comparison);
});
}
doAllConstrainsHaveRelation(comparison) {
const comparisoned = new RegExp(comparison);
return this.allConstraints((i, constraint) => {
return comparisoned.test(constraint.comparison);
});
}
anyConstraints(fn) {
for (let i = 0, len = this.constraints.length; i < len; i++) {
if (fn(i, this.constraints[i], this.constraints)) {
return true;
}
}
return false;
}
allConstraints(fn) {
let result = true;
for (let i = 0, len = this.constraints.length; i < len; i++) {
result = result && !!fn(i, this.constraints[i], this.constraints);
}
return result;
}
getAllArtificalNames() {
const names = [];
this.forEachConstraint((i, constraint) => {
const name = constraint.getArtificalName();
if (name) {
names.push(name);
}
});
return names;
}
forEachConstraint(fn) {
for (let i = 0, len = this.constraints.length; i < len; i++) {
fn(i, this.constraints[i], this.constraints);
}
}
addNumbersToSpecialTerms() {
const c = this.constraints;
let slackI = 1;
let artificalI = 1;
for (let i = 0, len = c.length; i < len; i++) {
if (c[i].hasSpecialTerm('slack')) {
c[i].renameSlack('slack' + slackI);
slackI++;
}
if (c[i].hasSpecialTerm('artifical')) {
c[i].renameArtificial('artifical' + artificalI);
artificalI++;
}
}
}
getTermNames(onlyVariables) {
let vars = [];
let i = this.constraints.length;
while (i--) {
vars = vars.concat(this.constraints[i].getTermNames(onlyVariables));
}
return getUniqueArray(vars).sort();
}
getAllSpecialTermNames() {
let names = [];
this.forEachConstraint((i, constraint) => {
names = names.concat(constraint.getSpecialTermNames());
});
return names;
}
setTermNames() {
this.terms = this.getTermNames();
}
get Constraints() {
return this.constraints;
}
get Z() {
return this.z;
}
toString() {
return [this.type + ' z = ' + this.z, 'where ' + this.constraints.join(', ')].join(', ');
}
convertConstraintsToMaxForm() {
const c = this.constraints;
for (let i = 0, len = c.length; i < len; i++) {
c[i] = c[i].convertToEquation();
}
}
convertToStandardForm() {
if (this.isStandardMode) {
return this;
}
this.convertConstraintsToMaxForm();
this.addNumbersToSpecialTerms();
this.setTermNames();
this.isStandardMode = true;
return this;
}
}
exports.SimplexInput = SimplexInput;
class SimplexOutput {
constructor(obj) {
this.result = obj;
this.checkForError();
}
static getErrorMessage(obj) {
let errMsg;
if (typeof obj !== 'object') {
errMsg = 'An object must be passed.';
}
return errMsg;
}
checkForError() {
const errMsg = SimplexOutput.getErrorMessage(this.result);
if (errMsg) {
throw new Error(errMsg);
}
}
toString() {
return JSON.stringify({ Result: this.result });
}
get Result() {
return this.result;
}
}
exports.SimplexOutput = SimplexOutput;