/* * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) * version 3 as published by the Free Software Foundation. In accordance with * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect * that Ascensio System SIA expressly excludes the warranty of non-infringement * of any third-party rights. * * This program is distributed WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under * Section 5 of the GNU AGPL version 3. * * Pursuant to Section 7(b) of the License you must retain the original Product * logo when distributing the program. Pursuant to Section 7(e) we decline to * grant you any rights under trademark law for use of our trademarks. * * All the Product's GUI elements, including illustrations and icon sets, as * well as technical writing content are licensed under the terms of the * Creative Commons Attribution-ShareAlike 4.0 International. See the License * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode * */ "use strict"; ( /** * @param {Window} window * @param {undefined} undefined */ function (window, undefined) { // Import const oCNumberType = AscCommonExcel.cNumber; const CellValueType = AscCommon.CellValueType; // Collections for UI Solver feature /** @enum {number} */ const c_oAscOptimizeTo = { max: 1, min: 2, valueOf: 3 }; /** @enum {number} */ const c_oAscSolvingMethod = { grgNonlinear: 1, simplexLP: 2, evolutionary: 3 }; /** @enum {number} */ const c_oAscDerivativeType = { forward: 1, central: 2 }; /** @enum {number} */ const c_oAscOperator = { '<=': 1, '=': 2, '>=': 3, integer: 4, bin: 5, diff: 6 }; /**@enum {number} */ const c_oAscSolverResult = { keepSolverSolution: 0, restoreOriginalValues: 1 }; /**@enum {number} */ const c_oResultStatus = { foundOptimalSolution: 0, solutionHasConverged: 1, cannotImproveSolution: 2, maxIterationsReached: 3, objectiveCellNotConverge: 4, notFindFeasibleSolution: 5, stoppedByUser: 6, linearityConditionsNotSatisfied: 7, tooLargeProblem: 8, errorValInObjectiveOrConstraintCell: 9, maxTimeReached: 10, notEnoughMemory: 11, errorInModel: 12, foundIntegerSolution: 13, maxFeasibleSolutionReached: 14, maxSubproblemSolutionReached: 15, }; // Common class /** * Class representing base attributes and methods for features of analysis. * @param {parserFormula} oParsedFormula - Formula object * @param {Range} oChangingCell - Changing cells. * For Goal Seek feature it's 1-1 object, for Solver 1-*. * @constructor */ function CBaseAnalysis(oParsedFormula, oChangingCell) { this.oParsedFormula = oParsedFormula; this.oChangingCell = oChangingCell; this.sRegNumDecimalSeparator = AscCommon.g_oDefaultCultureInfo.NumberDecimalSeparator; this.nIntervalId = null; this.bIsPause = false; this.nDelay = 70; // in ms for interval. this.nCurAttempt = 0; this.bIsSingleStep = false; this.nPrevFactValue = null; // todo add value from settings this.nMaxIterations = 100; // max iterations of goal seek. Default value is 100 } /** * Returns a result of formula with picked changing value. * @memberof CBaseAnalysis * @param {number} [nChangingVal] * @param {Range} [oChangingCell] * @returns {number} nFactValue */ CBaseAnalysis.prototype.calculateFormula = function(nChangingVal, oChangingCell) { let oParsedFormula = this.getParsedFormula(); let nFactValue = null; if (nChangingVal !== undefined && oChangingCell) { let sRegNumDecimalSeparator = this.getRegNumDecimalSeparator(); oChangingCell.setValue(String(nChangingVal).replace('.', sRegNumDecimalSeparator)); oChangingCell.worksheet.workbook.dependencyFormulas.unlockRecal(); } oParsedFormula.parse(); nFactValue = oParsedFormula.calculate().getValue(); // If result of formula returns type cNumber, convert to Number if (nFactValue instanceof oCNumberType) { nFactValue = nFactValue.toNumber(); } return nFactValue; }; /** * Returns a formula object. * @memberof CBaseAnalysis * @returns {parserFormula} */ CBaseAnalysis.prototype.getParsedFormula = function() { return this.oParsedFormula; }; /** * Returns changing cell. * @memberof CBaseAnalysis * @returns {Range} */ CBaseAnalysis.prototype.getChangingCell = function() { return this.oChangingCell; }; /** * Sets changing cell. * @memberof CBaseAnalysis * @param {Range} oChangingCell */ CBaseAnalysis.prototype.setChangingCell = function(oChangingCell) { this.oChangingCell = oChangingCell; }; /** * Returns a number decimal separator according chosen region. It may be "." or ",". * @memberof CBaseAnalysis * @returns {string} */ CBaseAnalysis.prototype.getRegNumDecimalSeparator = function() { return this.sRegNumDecimalSeparator; }; /** * Returns an id of interval. Uses for clear interval in UI. * @memberof CBaseAnalysis * @returns {number} */ CBaseAnalysis.prototype.getIntervalId = function() { return this.nIntervalId; }; /** * Sets an id of interval. Uses for clear interval in UI. * @memberof CBaseAnalysis * @param {number} nIntervalId */ CBaseAnalysis.prototype.setIntervalId = function(nIntervalId) { this.nIntervalId = nIntervalId; }; /** * Returns a flag who recognizes calculation process is paused or not. * @memberof CBaseAnalysis * @returns {boolean} */ CBaseAnalysis.prototype.getIsPause = function() { return this.bIsPause; }; /** * Sets a flag who recognizes calculation process is paused or not. * @memberof CBaseAnalysis * @param bIsPause */ CBaseAnalysis.prototype.setIsPause = function(bIsPause) { this.bIsPause = bIsPause; }; /** * Returns a delay in ms. Using for interval in UI. * @memberof CBaseAnalysis * @returns {number} */ CBaseAnalysis.prototype.getDelay = function() { return this.nDelay; }; /** * Returns a number of the current attempt. * @memberof CBaseAnalysis * @returns {number} */ CBaseAnalysis.prototype.getCurrentAttempt = function() { return this.nCurAttempt; }; /** * Increases a value of the current attempt. * @memberof CBaseAnalysis */ CBaseAnalysis.prototype.increaseCurrentAttempt = function() { this.nCurAttempt += 1; }; /** * Returns a flag who recognizes goal seek is runs in "single step" mode. Uses for "step" method. * @memberof CBaseAnalysis * @returns {boolean} */ CBaseAnalysis.prototype.getIsSingleStep = function() { return this.bIsSingleStep; }; /** * Sets a flag who recognizes goal seek is runs in "single step" mode. * @memberof CBaseAnalysis * @param {boolean} bIsSingleStep */ CBaseAnalysis.prototype.setIsSingleStep = function(bIsSingleStep) { this.bIsSingleStep = bIsSingleStep; }; /** * Returns previous value of formula result. * @memberof CBaseAnalysis * @returns {number} */ CBaseAnalysis.prototype.getPrevFactValue = function() { return this.nPrevFactValue; }; /** * Sets previous value of formula result. * @memberof CBaseAnalysis * @param {number} nPrevFactValue */ CBaseAnalysis.prototype.setPrevFactValue = function(nPrevFactValue) { this.nPrevFactValue = nPrevFactValue; }; /** * Returns max iterations. * @memberof CBaseAnalysis * @returns {number} */ CBaseAnalysis.prototype.getMaxIterations = function() { return this.nMaxIterations; }; /** * Sets max iterations. * @memberof CBaseAnalysis * @param {number} nMaxIterations */ CBaseAnalysis.prototype.setMaxIterations = function(nMaxIterations) { this.nMaxIterations = nMaxIterations; }; // Goal seek /** * Class representing a goal seek feature * @param {parserFormula} oParsedFormula - Formula object. * For a goal seek uses methods: parse - for update values in formula, calculate - for calculate formula result. * @param {number} nExpectedVal - Expected value. * @param {Range} oChangingCell - Changing cell. * @constructor */ function CGoalSeek(oParsedFormula, nExpectedVal, oChangingCell) { CBaseAnalysis.call(this, oParsedFormula, oChangingCell); this.nExpectedVal = nExpectedVal; this.sFormulaCellName = null; this.nStepDirection = null; this.nFirstChangingVal = null; this.nChangingVal = null; this.nPrevValue = null; this.bReverseCompare = null; this.bEnabledRidder = false; this.nLow = null; this.nHigh = null; this.nRelativeError = 1e-4; // relative error of goal seek. Default value is 1e-4 } CGoalSeek.prototype = Object.create(CBaseAnalysis.prototype); CGoalSeek.prototype.constructor = CGoalSeek; /** * Fills attributes for work with goal seek. * @memberof CGoalSeek */ CGoalSeek.prototype.init = function() { let oChangingCell = this.getChangingCell(); let sChangingVal = oChangingCell.getValue(); this.setFirstChangingValue(sChangingVal); this.setFormulaCellName(this.getParsedFormula()); this.initStepDirection(); this.initReverseCompare(); this.setChangingValue(sChangingVal ? Number(sChangingVal) : 0); }; /** * Main logic of calculating goal seek. * Takes "Changing cell" and its change according to expected value until result of formula be equal to expected value. * For calculate uses exponent step and Ridder method. * Notice: That algorithm works for most formulas who can solve with help linear or non-linear equations. * Exception is engineering formula like DEC2BIN, DEC2HEX etc. Those formulas can't be solved with that way because * they are not linear or non-linear equations. * Runs only in sync or async loop. * @memberof CGoalSeek * @return {boolean} The flag who recognizes end a loop of calculation goal seek. True - stop a loop, false - continue a loop. */ CGoalSeek.prototype.calculate = function() { if (this.getIsPause()) { return true; } const oChangingCell = this.getChangingCell(); let nChangingVal = this.getChangingValue(); let nExpectedVal = this.getExpectedVal(); let nPrevFactValue = this.getPrevFactValue(); let nFactValue, nDiff, nMedianFx, nMedianVal, nLowFx; this.increaseCurrentAttempt(); // Exponent step mode if (!this.getEnabledRidder()) { nFactValue = this.calculateFormula(nChangingVal, oChangingCell); nDiff = nFactValue - nExpectedVal; // Checks should it switch to Ridder algorithm. if (this.getReverseCompare()) { this.setEnabledRidder(!!(nFactValue < nExpectedVal || (nPrevFactValue && nFactValue > nPrevFactValue))); } else { this.setEnabledRidder(!!(nFactValue > nExpectedVal || (nPrevFactValue && nFactValue < nPrevFactValue))); } } if (this.getEnabledRidder()) { if (this.getLowBorder() == null && this.getHighBorder() == null) { this.setLowBorder(this.getPrevValue()); this.setHighBorder(nChangingVal); } let nLow = this.getLowBorder(); let nHigh = this.getHighBorder(); // Search f(lowBorder_value) and f(highBorder_value) nLowFx = this.calculateFormula(nLow, oChangingCell) - nExpectedVal; let nHighFx = this.calculateFormula(nHigh, oChangingCell) - nExpectedVal; // Search avg value in interval [nLow, nHigh] nMedianVal = (nLow + nHigh) / 2; nMedianFx = this.calculateFormula(nMedianVal, oChangingCell) - nExpectedVal; // Search changing value via root of exponential function nChangingVal = nMedianVal + (nMedianVal - nLow) * Math.sign(nLowFx - nHighFx) * nMedianFx / Math.sqrt(Math.pow(nMedianFx,2) - nLowFx * nHighFx); // If result exponential function is NaN then we use nMedianVal as changing value. It may be possible for unlinear function like sin, cos, tg. if (isNaN(nChangingVal)) { nChangingVal = nMedianVal; } nFactValue = this.calculateFormula(nChangingVal, oChangingCell); nDiff = nFactValue - nExpectedVal; this.setChangingValue(nChangingVal); } var oApi = Asc.editor; oApi.sendEvent("asc_onGoalSeekUpdate", nExpectedVal, nFactValue, this.getCurrentAttempt(), this.getFormulaCellName()); // Check: Need a finish calculate if (Math.abs(nDiff) < this.getRelativeError()) { oApi.sendEvent("asc_onGoalSeekStop", true); return true; } if (this.getCurrentAttempt() >= this.getMaxIterations() || isNaN(nDiff)) { oApi.sendEvent("asc_onGoalSeekStop", false); return true; } //Calculates next changing value if (this.getEnabledRidder()) { if (nMedianFx < 0 !== nDiff < 0) { this.setLowBorder(nMedianVal); this.setHighBorder(nChangingVal); } else if (nDiff < 0 !== nLowFx < 0) { this.setHighBorder(nChangingVal); } else { this.setLowBorder(nChangingVal); } } else { // Exponent step logic let nCurAttempt = this.getCurrentAttempt(); let nFirstChangingVal = this.getFirstChangingValue(); let nStepDirection = this.getStepDirection(); this.setPrevValue(nChangingVal); this.setPrevFactValue(nFactValue); if (!nFirstChangingVal) { this.setChangingValue((1 / 100 * nStepDirection) + (Math.pow(2, nCurAttempt - 1) - 1) * (1 / 10 * nStepDirection)); } else { this.setChangingValue(nFirstChangingVal + (nFirstChangingVal / 100 * nStepDirection) + (Math.pow(2, nCurAttempt - 1) - 1) * (nFirstChangingVal / 10 * nStepDirection)); } } if (this.getIsSingleStep()) { this.setIsPause(true); this.setIsSingleStep(false); return true; } return false; }; /** * Initialize step direction. Reverse direction (-1) or forward direction (+1). * @memberof CGoalSeek */ CGoalSeek.prototype.initStepDirection = function() { const oChangingCell = this.getChangingCell(); let sChangingVal = this.getChangingCell().getValue(); let nChangingVal = sChangingVal ? Number(sChangingVal) : 0; let nExpectedVal = this.getExpectedVal(); let nFirstChangedVal = null; // Init next changed value for find nextFormulaResult if (nChangingVal === 0) { nFirstChangedVal = 0.01 } else { nFirstChangedVal = nChangingVal + nChangingVal / 100; } // Find first and next formula result for check step direction let nFirstFormulaResult = this.calculateFormula(nChangingVal, oChangingCell); let nNextFormulaResult = this.calculateFormula(nFirstChangedVal, oChangingCell); // Init step direction if ((nFirstFormulaResult > nExpectedVal && nNextFormulaResult > nFirstFormulaResult)) { this.setStepDirection(-1); } else if (nNextFormulaResult < nFirstFormulaResult && nNextFormulaResult < nExpectedVal) { this.setStepDirection(-1); } else if (nFirstFormulaResult === nNextFormulaResult) { this.setStepDirection(-1); } else { this.setStepDirection(1); } }; /** * Returns step direction. * @memberof CGoalSeek * @returns {number} */ CGoalSeek.prototype.getStepDirection = function() { return this.nStepDirection; }; /** * Sets step direction. * @memberof CGoalSeek * @param {number} nStepDirection */ CGoalSeek.prototype.setStepDirection = function(nStepDirection) { this.nStepDirection = nStepDirection; }; /** * Returns expected value. * @memberof CGoalSeek * @returns {number} */ CGoalSeek.prototype.getExpectedVal = function() { return this.nExpectedVal; }; /** * Returns relative error. * @memberof CGoalSeek * @returns {number} */ CGoalSeek.prototype.getRelativeError = function() { return this.nRelativeError; }; /** * Sets relative error. * @memberof CGoalSeek * @param {number} nRelativeError */ CGoalSeek.prototype.setRelativeError = function(nRelativeError) { this.nRelativeError = nRelativeError; }; /** * Returns formula cell name. * @returns {string} */ CGoalSeek.prototype.getFormulaCellName = function() { return this.sFormulaCellName; }; /** * Sets formula cell name. * @param {parserFormula} oParsedFormula */ CGoalSeek.prototype.setFormulaCellName = function(oParsedFormula) { let oCellWithFormula = oParsedFormula.getParent(); let ws = oParsedFormula.getWs(); this.sFormulaCellName = ws.getRange4(oCellWithFormula.nRow, oCellWithFormula.nCol).getName(); }; /** * Returns first changing cell value in number type. * @memberof CGoalSeek * @returns {number} */ CGoalSeek.prototype.getFirstChangingValue = function() { return this.nFirstChangingVal; }; /** * Sets first changing cell value in number type. * @memberof CGoalSeek * @param {string} sChangingVal */ CGoalSeek.prototype.setFirstChangingValue = function(sChangingVal) { this.nFirstChangingVal = sChangingVal ? Number(sChangingVal) : null; }; /** * Returns a value of changing cell. * @memberof CGoalSeek * @returns {number} */ CGoalSeek.prototype.getChangingValue = function() { return this.nChangingVal }; /** * Sets a value of changing cell. * @memberof CGoalSeek * @param {number} nChangingVal */ CGoalSeek.prototype.setChangingValue = function(nChangingVal) { this.nChangingVal = nChangingVal }; /** * Returns previous value of changing cell. * @memberof CGoalSeek * @returns {number} */ CGoalSeek.prototype.getPrevValue = function() { return this.nPrevValue; }; /** * Sets previous value of changing cell. * @memberof CGoalSeek * @param {number} nPrevValue */ CGoalSeek.prototype.setPrevValue = function(nPrevValue) { this.nPrevValue = nPrevValue; }; /** * Returns a flag who recognizes should be use compare reverse (when result of calculation formula is less than expected) or not. * @memberof CGoalSeek * @returns {boolean} */ CGoalSeek.prototype.getReverseCompare = function() { return this.bReverseCompare; }; /** * Initializes a flag who recognizes should be use compare reverse (when result of calculation formula is less than expected) or not. * @memberof CGoalSeek */ CGoalSeek.prototype.initReverseCompare = function() { const oChangingCell = this.getChangingCell(); let nFirstFormulaResult = null; let nFirstChangingVal = this.getFirstChangingValue() ? Number(this.getFirstChangingValue()) : 0; this.bReverseCompare = false; nFirstFormulaResult = this.calculateFormula(nFirstChangingVal, oChangingCell); if (nFirstFormulaResult > this.getExpectedVal()) { this.bReverseCompare = true; } }; /** * Returns a flag who recognizes goal seek is already using Ridder method or not. * @memberof CGoalSeek * @returns {boolean} */ CGoalSeek.prototype.getEnabledRidder = function() { return this.bEnabledRidder }; /** * Sets a flag who recognizes goal seek is already using Ridder method or not. * @memberof CGoalSeek * @param {boolean} bEnabledRidder */ CGoalSeek.prototype.setEnabledRidder = function(bEnabledRidder) { this.bEnabledRidder = bEnabledRidder }; /** * Returns a lower border value for Ridder algorithm. * @memberof CGoalSeek * @returns {number} */ CGoalSeek.prototype.getLowBorder = function() { return this.nLow; }; /** * Sets a lower border value for Ridder algorithm. * @memberof CGoalSeek * @param {number} nLow */ CGoalSeek.prototype.setLowBorder = function(nLow) { this.nLow = nLow; }; /** * Returns an upper border value for Ridder algorithm. * @memberof CGoalSeek * @returns {number} */ CGoalSeek.prototype.getHighBorder = function() { return this.nHigh; }; /** * Sets an upper border value for Ridder algorithm. * @memberof CGoalSeek * @param {number} nHigh */ CGoalSeek.prototype.setHighBorder = function(nHigh) { this.nHigh = nHigh; }; /** * Returns error type * @memberof CGoalSeek * @param {AscCommonExcel.Worksheet} ws - checked sheet. * @param {Asc.Range} range - checked range. * @param {Asc.c_oAscSelectionDialogType} type - dialog type. * @returns {Asc.c_oAscError} */ CGoalSeek.prototype.isValidDataRef = function(ws, range, type) { let res = Asc.c_oAscError.ID.No; if (range === null) { //error text: the formula is missing a range... return Asc.c_oAscError.ID.DataRangeError; } if (!range.isOneCell()) { //error text: reference must be to a single cell... //TODO check def names res = Asc.c_oAscError.ID.MustSingleCell; } switch (type) { case Asc.c_oAscSelectionDialogType.GoalSeek_Cell: { //check formula contains let isFormula = false; let isNumberResult = true; //MustFormulaResultNumber ws && ws._getCellNoEmpty(range.r1, range.c1, function (cell) { if (cell && cell.isFormula()) { isFormula = true; if (cell.number == null) { isNumberResult = false; } } }); if (!isFormula) { res = Asc.c_oAscError.ID.MustContainFormula; } else if (!isNumberResult) { res = Asc.c_oAscError.ID.MustFormulaResultNumber; } break; } case Asc.c_oAscSelectionDialogType.GoalSeek_ChangingCell: { let isValue = true; ws && ws._getCellNoEmpty(range.r1, range.c1, function (cell) { if (cell && cell.isFormula()) { isValue = false; } }); if (!isValue) { res = Asc.c_oAscError.ID.MustContainValue; } break; } } return res; }; /** * Pauses a goal seek calculation. * @memberof CGoalSeek */ CGoalSeek.prototype.pause = function() { this.setIsPause(true); }; /** * Resumes a goal seek calculation. * @memberof CGoalSeek */ CGoalSeek.prototype.resume = function() { let oGoalSeek = this; this.setIsPause(false); this.setIntervalId(setInterval(function() { let bIsFinish = oGoalSeek.calculate(); if (bIsFinish) { clearInterval(oGoalSeek.getIntervalId()); } }, this.getDelay())); }; /** * Resumes goal calculation by one step than pause it again. * @memberof CGoalSeek */ CGoalSeek.prototype.step = function() { let oGoalSeek = this; this.setIsPause(false); this.setIsSingleStep(true); this.setIntervalId(setInterval(function() { let bIsFinish = oGoalSeek.calculate(); if (bIsFinish) { clearInterval(oGoalSeek.getIntervalId()); } }, this.getDelay())); }; // Solver // Classes for interact with UI. /** * Class representing object with input data from dialogue window of Solver tool. * @constructor * @returns {asc_CSolverParams} */ function asc_CSolverParams () { this.sObjectiveFunction = null; this.sChangingCells = null; this.nOptimizeResultTo = c_oAscOptimizeTo.max; this.sValueOf = '0'; this.aConstraints = new Map(); this.bVariablesNonNegative = true; this.nSolvingMethod = c_oAscSolvingMethod.simplexLP; this.oOptions = new asc_COptions(); this.oDefNameToAttribute = { 'solver_opt': 'sObjectiveFunction', 'solver_typ': 'nOptimizeResultTo', 'solver_val': 'sValueOf', 'solver_adj': 'sChangingCells', 'solver_neg': 'bVariablesNonNegative', 'solver_eng': 'nSolvingMethod' }; return this; } /** * Returns value of "Set Objective" parameter. * @memberof asc_CSolverParams * @returns {string} */ asc_CSolverParams.prototype.getObjectiveFunction = function () { return this.sObjectiveFunction; }; /** * Sets value of "Set Objective" parameter. * @memberof asc_CSolverParams * @param {string|null} objectiveFunction */ asc_CSolverParams.prototype.setObjectiveFunction = function (objectiveFunction) { this.sObjectiveFunction = objectiveFunction; }; /** * Returns value of "By Changing Variable Cells" parameters. * @memberof asc_CSolverParams * @returns {string} */ asc_CSolverParams.prototype.getChangingCells = function () { return this.sChangingCells; }; /** * Sets value of "By Changing Variable Cells" parameters. * @param {string|null} changingCells */ asc_CSolverParams.prototype.setChangingCells = function (changingCells) { this.sChangingCells = changingCells; }; /** * Returns value of "To" parameter. * @memberof asc_CSolverParams * @returns {c_oAscOptimizeTo} */ asc_CSolverParams.prototype.getOptimizeResultTo = function () { return this.nOptimizeResultTo; }; /** * Sets value of "To" parameter. * @memberof asc_CSolverParams * @param {c_oAscOptimizeTo} optimizeResultTo */ asc_CSolverParams.prototype.setOptimizeResultTo = function (optimizeResultTo) { this.nOptimizeResultTo = optimizeResultTo; }; /** * Returns value of "Value of" input field. * @memberof asc_CSolverParams * @param {string} sValueOf */ asc_CSolverParams.prototype.setValueOf = function (sValueOf) { this.sValueOf = sValueOf; }; /** * Sets value of "Value of" input field. * @memberof {asc_CSolverParams} * @returns {string} */ asc_CSolverParams.prototype.getValueOf = function () { return this.sValueOf; }; /** * Returns value of "Subject to the Constraints" parameter. * @memberof asc_CSolverParams * @returns {Map} */ asc_CSolverParams.prototype.getConstraints = function () { return this.aConstraints; }; /** * Adds the constraint in "Subject to the Constraints" parameter. * @memberof asc_CSolverParams * @param {number} index - key of constraint in Map object. Starts with 1 ... N. * @param {{cellRef:string, operator:c_oAscOperator, constraint:string}} constraint */ asc_CSolverParams.prototype.addConstraint = function (index, constraint) { this.aConstraints.set(index, constraint); }; /** * Edits the chosen constraint in "Subject to the Constraints" parameter. * @memberof asc_CSolverParams * @param {number} index - index of chosen constraint * @param {{cellRef:string, operator:c_oAscOperator, constraint:string}} constraint */ asc_CSolverParams.prototype.editConstraint = function (index, constraint) { this.aConstraints.set(index, constraint); }; /** * Removes the chosen constraint in "Subject to the Constraints" parameter. * @memberof asc_CSolverParams * @param {number} index */ asc_CSolverParams.prototype.removeConstraint = function (index) { this.aConstraints.delete(index); }; /** * Returns value of "Make Unconstrained Variables Non-negative" parameter. * @memberof asc_CSolverParams * @returns {boolean} */ asc_CSolverParams.prototype.getVariablesNonNegative = function () { return this.bVariablesNonNegative; }; /** * Sets value of "Make Unconstrained Variables Non-negative" parameter. * @memberof asc_CSolverParams * @param {boolean} variablesNonNegative */ asc_CSolverParams.prototype.setVariablesNonNegative = function (variablesNonNegative) { this.bVariablesNonNegative = variablesNonNegative; }; /** * Returns value of "Select a Solving Method" parameter. * @memberof asc_CSolverParams * @returns {c_oAscSolvingMethod} */ asc_CSolverParams.prototype.getSolvingMethod = function () { return this.nSolvingMethod; }; /** * Sets value of "Select a Solving Method" parameter. * @memberof asc_CSolverParams * @param {c_oAscSolvingMethod} solvingMethod */ asc_CSolverParams.prototype.setSolvingMethod = function (solvingMethod) { this.nSolvingMethod = solvingMethod; }; /** * Returns the options. * @memberof asc_CSolverParams * @returns {asc_COptions} */ asc_CSolverParams.prototype.getOptions = function () { return this.oOptions; }; /** * Resets all params and options to default value. * @memberof asc_CSolverParams */ asc_CSolverParams.prototype.resetAll = function () { this.setObjectiveFunction(null); this.setChangingCells(null); this.setOptimizeResultTo(c_oAscOptimizeTo.max); this.setValueOf('0'); this.getConstraints().clear(); this.setVariablesNonNegative(true); this.getOptions().resetAll(); }; /** * Returns object which connecting def name to attribute of class. * @memberof asc_CSolverParams * @returns {{ * solver_opt: string, solver_typ: string, solver_val: string, * solver_adj: string, solver_neg: string, solver_eng: string * }} */ asc_CSolverParams.prototype.getDefNameToAttribute = function() { return this.oDefNameToAttribute; }; /** * Returns array of hidden defined names. * @param {DependencyGraph} oDependencyFormulas * @returns {asc_CDefName[]} */ function getHiddenDefinedNamesWS (oDependencyFormulas) { const oActiveWS = oDependencyFormulas.wb.getActiveWs(); const aHiddenDefNames = []; oDependencyFormulas._foreachDefNameSheet(oActiveWS.getId(), function(oDefName) { if (oDefName.hidden) { aHiddenDefNames.push(oDefName.getAscCDefName(true)); } }); return aHiddenDefNames; } /** * Adds Def names ranges according to the values of the Solver params and options. * @memberof asc_CSolverParams * @param {Workbook} oWbModel */ asc_CSolverParams.prototype.createDefNames = function(oWbModel) { if (!oWbModel) { return; } function fillDefName(sDefName, ref) { let oOldDefName = null; if (mSolverDefNames.size) { oOldDefName = mSolverDefNames.get(sDefName); } if (ref === null) { if (oOldDefName) { const sSheetId = this.getSheetIdByIndex(oOldDefName.LocalSheetId); oWbModel.dependencyFormulas._removeDefName(sSheetId, oOldDefName.Name, null); } return; } if (typeof ref === 'boolean') { ref = ref ? 1 : 2; } let oNewDefName = new Asc.asc_CDefName(sDefName, ref, nWsId, undefined, true, undefined, undefined, true); oWbModel.editDefinesNames(oOldDefName, oNewDefName); } const oDependencyFormulas = oWbModel.dependencyFormulas; const aWsDefNames = getHiddenDefinedNamesWS(oDependencyFormulas); const oDefNameToAttribute = this.getDefNameToAttribute(); const oOptions = this.getOptions(); const oOptionsDefNameToAttribute = oOptions.getDefNameToAttribute(); const mSolverDefNames = new Map(); const nWsId = oWbModel.getActive(); // Finds existed defined names for the solver. if (aWsDefNames.length) { aWsDefNames.forEach(function(oDefName) { if (~oDefName.Name.indexOf('solver_')) { mSolverDefNames.set(oDefName.Name, oDefName); } }); } // Fills constant define names fillDefName('solver_ver', 3); fillDefName('solver_est', 1); fillDefName('solver_nwt', 1) // Fills Solver parameters for (let sDefName in oDefNameToAttribute) { fillDefName(sDefName, this[oDefNameToAttribute[sDefName]]); } // Fills constraints const mConstraints = this.getConstraints(); let nIndex = 1; if (!mConstraints.size) { return; } fillDefName('solver_num', mConstraints.size); mConstraints.forEach(function(constraint) { fillDefName('solver_lhs' + nIndex, constraint.cellRef); fillDefName('solver_rhs' + nIndex, constraint.constraint); fillDefName('solver_rel' + nIndex, constraint.operator); nIndex++; }); // Fills options for (let sDefName in oOptionsDefNameToAttribute) { fillDefName(sDefName, oOptions[oOptionsDefNameToAttribute[sDefName]]); } }; /** * Gets def names ranges for filling fields of solver params and options. * @memberof asc_CSolverParams * @param {Workbook} oWbModel */ asc_CSolverParams.prototype.getDefNames = function(oWbModel) { if (!oWbModel) { return; } function fillAttribute (oAttribute, sDefName) { let oDefName = mSolverDefNames.get(sDefName); if (oDefName) { oAttribute = typeof oAttribute === 'boolean' ? oDefName.Ref === 1 : oDefName.Ref; } return oAttribute; } const oDependencyFormulas = oWbModel.dependencyFormulas; const aWsDefNames = getHiddenDefinedNamesWS(oDependencyFormulas); const oDefNameToAttribute = this.getDefNameToAttribute(); const oOptions = this.getOptions(); const oOptionsDefNameToAttribute = oOptions.getDefNameToAttribute(); const mSolverDefNames = new Map(); if (!aWsDefNames.length) { return; } aWsDefNames.forEach(function(oDefName) { if (~oDefName.Name.indexOf("solver_")) { mSolverDefNames.set(oDefName.Name, oDefName); } }); if (!mSolverDefNames.size) { return; } // Fills solver params for (let sDefName in oDefNameToAttribute) { this[oDefNameToAttribute[sDefName]] = fillAttribute(this[oDefNameToAttribute[sDefName]], sDefName); } // Fills constraints. const nConstraintsSize = mSolverDefNames.get('solver_num') && mSolverDefNames.get('solver_num').Ref; if (nConstraintsSize === undefined) { return; } for (let i = 1; i <= nConstraintsSize; i++) { /** @type {{cellRef:string, operator: c_oAscOperator, constraint:string}} */ const oConstraint = {}; oConstraint.cellRef = fillAttribute(oConstraint.cellRef, 'solver_lhs' + i); oConstraint.operator = fillAttribute(oConstraint.operator, 'solver_rel' + i); oConstraint.constraint = fillAttribute(oConstraint.constraint, 'solver_rhs' + i); if (oConstraint.cellRef && oConstraint.operator && oConstraint.constraint) { this.addConstraint(i, oConstraint); } } // Fills options for (let sDefName in oOptionsDefNameToAttribute) { oOptions[oOptionsDefNameToAttribute[sDefName]] = fillAttribute(oOptions[oOptionsDefNameToAttribute[sDefName]], sDefName); } }; /** * Class representing options for calculating. * @constructor */ function asc_COptions () { const sNumberDecimalSeparator = AscCommon.g_oDefaultCultureInfo.NumberDecimalSeparator; // All methods this.sConstraintPrecision = '0.000001'.replace('.', sNumberDecimalSeparator); this.bAutomaticScaling = false; this.bShowIterResults = false; this.bIgnoreIntConstriants = false; this.sIntOptimal = '1'; this.sMaxTime = '2147483647'; this.sIterations = '2147483647'; this.sMaxSubproblems = '2147483647'; this.sMaxFeasibleSolution = '2147483647'; // GRG Nonlinear and Evolutionary this.sConvergence = '0.0001'.replace('.', sNumberDecimalSeparator); this.nDerivatives = c_oAscDerivativeType.forward; this.bMultistart = false; // ? this.sPopulationSize = '100'; this.sRandomSeed = '0'; this.bRequireBounds = false; this.sMutationRate = '0.075'.replace('.', sNumberDecimalSeparator); this.sEvoMaxTime = '30'; this.oDefNameToAttribute = { 'solver_pre': 'sConstraintPrecision', 'solver_scl': 'bAutomaticScaling', 'solver_sho': 'bShowIterResults', 'solver_rlx': 'bIgnoreIntConstriants', 'solver_tol': 'sIntOptimal', 'solver_tim': 'sMaxTime', 'solver_itr': 'sIterations', 'solver_nod': 'sMaxSubproblems', 'solver_mip': 'sMaxFeasibleSolution', 'solver_cvg': 'sConvergence', 'solver_drv': 'nDerivatives', 'solver_msl': 'bMultistart', 'solver_ssz': 'sPopulationSize', 'solver_rsd': 'sRandomSeed', 'solver_rbv': 'bRequireBounds', 'solver_mrt': 'sMutationRate', 'solver_mni': 'sEvoMaxTime' }; } /** * Returns value of "Constraint Precision" parameter. * @memberof asc_COptions * @returns {string} */ asc_COptions.prototype.getConstraintPrecision = function() { return this.sConstraintPrecision; }; /** * Returns value of "Use automatic Scaling" parameter. * @memberof asc_COptions * @returns {boolean} */ asc_COptions.prototype.getAutomaticScaling = function() { return this.bAutomaticScaling; }; /** * Returns value of "Show Iteration Results" parameter. * @memberof asc_COptions * @returns {boolean} */ asc_COptions.prototype.getShowIterResults = function() { return this.bShowIterResults; }; /** * Returns value of "Ignore Integer Constraints" parameter. * @memberof asc_COptions * @returns {boolean} */ asc_COptions.prototype.getIgnoreIntConstraints = function() { return this.bIgnoreIntConstriants; }; /** * Returns value of "Integer Optimality (%)" parameter. * @memberof asc_COptions * @returns {string} */ asc_COptions.prototype.getIntOptimal = function () { return this.sIntOptimal; }; /** * Returns value of "Max Time (Seconds)" parameter. * @memberof asc_COptions * @returns {string} */ asc_COptions.prototype.getMaxTime = function () { return this.sMaxTime; }; /** * Returns value of "Iterations" * @memberof asc_COptions * @returns {string} */ asc_COptions.prototype.getIterations = function () { return this.sIterations; }; /** * Returns value of "Max Subproblems" parameter. * @memberof asc_COptions * @returns {string} */ asc_COptions.prototype.getMaxSubproblems = function () { return this.sMaxSubproblems; }; /** * Returns value of "Max Feasible Solutions" parameter. * @memberof asc_COptions * @returns {string} */ asc_COptions.prototype.getMaxFeasibleSolution = function () { return this.sMaxFeasibleSolution; }; /** * Returns value of "Convergence" parameter. * @memberof asc_COptions * @returns {string} */ asc_COptions.prototype.getConvergence = function () { return this.sConvergence; }; /** * Returns value of "Derivatives" parameter. * @memberof asc_COptions * @returns {c_oAscDerivativeType} */ asc_COptions.prototype.getDerivatives = function () { return this.nDerivatives; }; /** * Returns value of "Use multistart" parameter. * @memberof asc_COptions * @returns {boolean} */ asc_COptions.prototype.getMultistart = function () { return this.bMultistart; }; /** * Returns value of "Population Size" parameter. * @memberof asc_COptions * @returns {string} */ asc_COptions.prototype.getPopulationSize = function () { return this.sPopulationSize; }; /** * Returns value of "Random seed" parameter. * @memberof asc_COptions * @returns {string} */ asc_COptions.prototype.getRandomSeed = function () { return this.sRandomSeed; }; /** * Returns value of "Require Bounds on Variables" parameter. * @memberof asc_COptions * @returns {boolean} */ asc_COptions.prototype.getRequireBounds = function () { return this.bRequireBounds; }; /** * Returns value of "Mutation Rate" parameter. * @memberof asc_COptions * @returns {string} */ asc_COptions.prototype.getMutationRate = function () { return this.sMutationRate; }; /** * Returns value of "Maximum Time without improvement" parameter. * @memberof asc_COptions * @returns {string} */ asc_COptions.prototype.getEvoMaxTime = function () { return this.sEvoMaxTime; }; /** * Returns object which connecting def name to attribute of class. * @memberof asc_COptions * @returns {{ * solver_pre: string, solver_scl: string, solver_sho: string, solver_rlx: string, solver_tol: string, * solver_tim: string,solver_itr: string, solver_nod: string,solver_mip: string, solver_cvg: string, * solver_drv: string, solver_msl: string, solver_ssz: string, solver_rsd: string, solver_rbv: string, * solver_mrt: string, solver_mni: string * }} */ asc_COptions.prototype.getDefNameToAttribute = function() { return this.oDefNameToAttribute; }; /** * Sets value of "Constraint Precision" parameter. * @memberof asc_COptions * @param {string} constraintPrecision */ asc_COptions.prototype.setConstraintPrecision = function (constraintPrecision) { this.sConstraintPrecision = constraintPrecision; }; /** * Sets value of "Use automatic Scaling" parameter. * @memberof asc_COptions * @param {boolean} automaticScaling */ asc_COptions.prototype.setAutomaticScaling = function (automaticScaling) { this.bAutomaticScaling = automaticScaling; }; /** * Sets value of "Show Iteration Results" parameter. * @memberof asc_COptions * @param {boolean} showIterResults */ asc_COptions.prototype.setShowIterResults = function (showIterResults) { this.bShowIterResults = showIterResults; }; /** * Sets value of "Ignore Integer Constraints" parameter. * @memberof asc_COptions * @param {boolean} ignoreIntConstraints */ asc_COptions.prototype.setIgnoreIntConstraints = function (ignoreIntConstraints) { this.bIgnoreIntConstriants = ignoreIntConstraints; }; /** * Sets value of "Integer Optimality (%)" parameter. * @memberof asc_COptions * @param {string} intOptimality */ asc_COptions.prototype.setIntOptimal = function (intOptimality) { this.sIntOptimal = intOptimality; }; /** * Sets value of "Max Time (Seconds)" parameter. * @memberof asc_COptions * @param {string} maxTime */ asc_COptions.prototype.setMaxTime = function (maxTime) { this.sMaxTime = maxTime; }; /** * Sets value of "Iterations" parameter * @memberof asc_COptions * @param {string} iterations */ asc_COptions.prototype.setIterations = function (iterations) { this.sIterations = iterations; }; /** * Sets value of "Max Subproblems" parameter. * @memberof asc_COptions * @param {string} maxSubproblems */ asc_COptions.prototype.setMaxSubproblems = function (maxSubproblems) { this.sMaxSubproblems = maxSubproblems; }; /** * Sets value of "Max Feasible Solution" parameter. * @memberof asc_COptions * @param {string} maxFeasibleSolution */ asc_COptions.prototype.setMaxFeasibleSolution = function (maxFeasibleSolution) { this.sMaxFeasibleSolution = maxFeasibleSolution; }; /** * Sets value of "Convergence" parameter. * @memberof asc_COptions * @param {string} convergence */ asc_COptions.prototype.setConvergence = function (convergence) { this.sConvergence = convergence; }; /** * Sets value of "Derivatives" parameter. * @memberof asc_COptions * @param {c_oAscDerivativeType} derivatives */ asc_COptions.prototype.setDerivatives = function (derivatives) { this.nDerivatives = derivatives; }; /** * Sets value of "Use Multistart" parameter. * @memberof asc_COptions * @param {boolean} multistart */ asc_COptions.prototype.setMultistart = function (multistart) { this.bMultistart = multistart; }; /** * Sets value of "Population Size" parameter. * @memberof asc_COptions * @param {string} populationSize */ asc_COptions.prototype.setPopulationSize = function (populationSize) { this.sPopulationSize = populationSize; }; /** * Sets value of "Random Seed" parameter. * @memberof asc_COptions * @param {string} randomSeed */ asc_COptions.prototype.setRandomSeed = function (randomSeed) { this.sRandomSeed = randomSeed; }; /** * Sets value of "Require Bounds on Variables" parameter. * @memberof asc_COptions * @param {boolean} requireBounds */ asc_COptions.prototype.setRequireBounds = function (requireBounds) { this.bRequireBounds = requireBounds; }; /** * Sets value of "Mutation Rate" parameter. * @memberof asc_COptions * @param {string} mutationRate */ asc_COptions.prototype.setMutationRate = function (mutationRate) { this.sMutationRate = mutationRate; }; /** * Sets value of "Maximum Time without improvement" parameter. * @memberof asc_COptions * @param {string} evoMaxTime */ asc_COptions.prototype.setEvoMaxTime = function (evoMaxTime) { this.sEvoMaxTime = evoMaxTime; }; /** * Resets all options to default value. * @memberof asc_COptions */ asc_COptions.prototype.resetAll = function() { this.setConstraintPrecision('0.000001'); this.setAutomaticScaling(false); this.setShowIterResults(false); this.setIgnoreIntConstraints(false); this.setIntOptimal('1'); this.setMaxTime('2147483647'); this.setIterations('2147483647'); this.setMaxSubproblems('2147483647'); this.setMaxFeasibleSolution('2147483647'); this.setConvergence('0.0001'); this.setDerivatives(c_oAscDerivativeType.forward); this.setMultistart(false); this.setPopulationSize('100'); this.setRandomSeed('0'); this.setRequireBounds(false); this.setMutationRate('0.075'); this.setEvoMaxTime('30'); }; /** * Class representing Solver Results dialogue window data. * @constructor * @returns {asc_CSolverResults} */ function asc_CSolverResults () { this.sStatus = null; this.sDescription = null; this.nAction = c_oAscSolverResult.keepSolverSolution; this.bReturnToSolverParams = false; return this; } /** * Returns status of Solver Results dialogue window. * @memberof asc_CSolverResults * @returns {string} */ asc_CSolverResults.prototype.getStatus = function() { return this.sStatus; }; /** * Returns status description of Solver Results dialogue window. * @memberof asc_CSolverResults * @returns {string} */ asc_CSolverResults.prototype.getDescription = function() { return this.sDescription; }; /** * Returns current action of Solver Results dialogue window. * @memberof asc_CSolverResults * @returns {c_oAscSolverResult} */ asc_CSolverResults.prototype.getAction = function() { return this.nAction; }; /** * Returns state of "Return to solver params" checkbox of Solver Results dialogue window. * @memberof asc_CSolverResults * @returns {boolean} */ asc_CSolverResults.prototype.getReturnToSolverParams = function() { return this.bReturnToSolverParams; }; /** * Sets status of Solver Results dialogue window. * @memberof asc_CSolverResults * @param {string} sStatus */ asc_CSolverResults.prototype.setStatus = function(sStatus) { this.sStatus = sStatus; }; /** * Sets status description of Solver Results dialogue window. * @memberof asc_CSolverResults * @param {string} sDescription */ asc_CSolverResults.prototype.setDescription = function(sDescription) { this.sDescription = sDescription; }; /** * Sets current action of Solver Results dialogue window. * @memberof asc_CSolverResults * @param {c_oAscSolverResult} nAction */ asc_CSolverResults.prototype.setAction = function(nAction) { this.nAction = nAction; }; /** * Sets state of "Return to solver params" checkbox of Solver Results dialogue window. * @memberof asc_CSolverResults * @param {boolean} bReturnToSolverParams */ asc_CSolverResults.prototype.setReturnToSolverParams = function(bReturnToSolverParams) { this.bReturnToSolverParams = bReturnToSolverParams; }; // Classes and functions for main logic of Solver feature /** * Returns worksheet that has selected cell(s) from parameters. * @param {string} sRef - selected cell(s) reference from parameters * @param {Worksheet} oWs * @returns {Worksheet} */ function actualWsByRef (sRef, oWs) { let oActualWs = oWs; if (~sRef.indexOf("!")) { const sSheetName = sRef.split("!")[0].replace(/'/g, ""); if (sSheetName !== oActualWs.getName()) { oActualWs = oWs.workbook.getWorksheetByName(sSheetName); } } return oActualWs; } /** * Converts absolute reference containing the worksheet name to reference with only selected cell(s) name. * @param {string} sRef - selected cell(s) reference from parameters * @returns {string} */ function convertToAbsoluteRef (sRef) { let sConvertedRef = sRef; if (~sRef.indexOf("!")) { sConvertedRef = sRef.split("!")[1]; } return sConvertedRef; } /** * Class representing a constraints option for Solver. * @param {Range|Cell} oCell * @param {c_oAscOperator} nOperator * @param {Range|number|string} constraint * @constructor */ function CConstraint (oCell, nOperator, constraint) { this.oCell = oCell; this.nOperator = nOperator; this.constraint = constraint; } /** * Returns cell reference. * @memberof CConstraint * @returns {Range|Cell} */ CConstraint.prototype.getCell = function() { return this.oCell; }; /** * Returns comparison operator. * @memberof CConstraint * @returns {c_oAscOperator} */ CConstraint.prototype.getOperator = function() { return this.nOperator; }; /** * Returns constraint. Element that comparisons with reference cell. * @memberof CConstraint * @returns {Range|number|string} */ CConstraint.prototype.getConstraint = function() { return this.constraint; }; /** * Class representing logic of solving linear programming by Simplex method. * @param {CSolver} oModel * @constructor */ function CSimplexTableau (oModel) { this.oModel = oModel; this.aMatrix = null; this.nWidth = 0; this.nHeight = 0; this.aVariablesIndexes = null; this.aConstraintsIndexes = null; this.nObjectiveRowIndex = 0; this.nRhsColumn = 0; this.oVarIndexByCellName = null; this.oUnrestrictedVars = null; // Solution attributes this.nLastColumnId = null; this.nLastRowId = null; this.aVarIndexesCycle = []; this.mRepeatedVars = new Map(); this.bFeasible = false; this.bSolutionIsFound = false; this.aVarIndexByRow = null; this.aVarIndexByCol = null; this.aRowByVarIndex = null; this.aColByVarIndex = null; this.nPrecision = 1e-8; this.nLastElementIndex = 0; this.oVariables = null; this.nVars = 0; this.bBounded = true; this.nUnboundedVarIndex = null; this.nBranchAndCutIters = 0; } /** * Initializes a start data for calculating logic. * @memberof CSimplexTableau */ CSimplexTableau.prototype.init = function () { const oModel = this.getModel(); const oOptions = oModel.getOptions(); const oChangingCells = oModel.getChangingCell(); const oBboxChangingCells = oChangingCells.bbox; const nTotalCountVariables = oBboxChangingCells.getWidth() * oBboxChangingCells.getHeight(); const aConstraints = oModel.getConstraints(); const aSimplexConstraints = this.getSimplexConstraints(aConstraints); const nTotalCountConstraints = aSimplexConstraints.length; const nOptionPrecision = Number(oOptions.getConstraintPrecision().replace(/,/g, ".")); if (!isNaN(nOptionPrecision)) { this.setPrecision(nOptionPrecision); } this.setVariables(oChangingCells); // Init width and height of matrix. this.setWidth(nTotalCountVariables + 1); this.setHeight(nTotalCountConstraints + 1); // Init indexes of variables and constraints this.setConstraintsIndexes(this.fillConstraintsIndexes(nTotalCountConstraints)); this.setVariablesIndexes(this.fillVariablesIndexes(nTotalCountVariables, nTotalCountConstraints)); // Init matrix const aMatrix = this.createMatrix(); const nHeight = this.getHeight(); const nWidth = this.getWidth(); this.setVarIndexByRow(new Array(nHeight)); this.setVarIndexByCol(new Array(nWidth)); this.addVarIndexByRowElem(-1, 0); this.addVarIndexByColElem(-1, 0); this.setVarsCount(nWidth + nHeight - 2); this.setRowByVarIndex(new Array(this.getVarsCount())); this.setColByVarIndex(new Array(this.getVarsCount())); this.setLastElementIndex(this.getVarsCount()); // Fills matrix this.fillMatrix(aMatrix, aSimplexConstraints); // Init last index row and column of matrix this.setLastColumnIndex(nWidth - 1); this.setLastRowIndex(nHeight - 1); }; /** * Calculates solution using Simplex LP method. * @memberof CSimplexTableau * @returns {boolean} The flag who recognizes end a loop of solver calculation. True - stop a loop, false - continue a loop. * @see {@link https://en.wikibooks.org/wiki/Operations_Research/The_Simplex_Method#The_Simplex_method|Theory material} */ CSimplexTableau.prototype.calculate = function () { let bStopCalculating = false; if (!this.getFeasible() && !bStopCalculating) { bStopCalculating = this.phase1(); } if (this.getFeasible() && !bStopCalculating) { // current solution is feasible bStopCalculating = this.phase2(); } return bStopCalculating; }; /** * Obtains a Basic Feasible Solution (BFS) * Convert a non-standard form tableau to a standard form tableau by eliminating * all negative values on the Right Hand Side (RHS) * This results in a Basic Feasible Solution (BFS) * @memberof CSimplexTableau * @returns {boolean} The flag who recognizes end a loop of solver calculation. True - stop a loop, false - continue a loop. */ CSimplexTableau.prototype.phase1 = function () { const oModel = this.getModel(); const aMatrix = this.getMatrix(); const nRhsColumnId = this.getRhsColumn(); const nLastColumnId = this.getLastColumnIndex(); const nLastRowId = this.getLastRowIndex(); const aVarIndexesCycle = this.getVarIndexesCycle(); let nLeavingRowIndex = 0; let nRhsValue = -this.getPrecision(); // Step 1: Find pivot row. Selecting leaving variable (feasibility condition). Basic variable with most negative value. for (let i = 1; i <= nLastRowId; i++) { const nValue = aMatrix[i][nRhsColumnId]; if (nValue < nRhsValue) { nRhsValue = nValue; nLeavingRowIndex = i; } } // If the leaving row isn't found, for 1st phase, found a feasible solution. Finished 1st phase. if (nLeavingRowIndex === 0) { this.setFeasible(true); return false; } // Step 2: Find pivot column. Selecting entering variable. const aObjectiveRow = aMatrix[this.getObjectiveRowIndex()]; const aLeavingRow = aMatrix[nLeavingRowIndex]; let nEnteringColumnIndex = 0; let nMaxQuotient = -Infinity; for (let i = 1; i <= nLastColumnId; i++) { const nCoefficient = aLeavingRow[i]; if (nCoefficient < -this.getPrecision()) { const nQuotient = -aObjectiveRow[i] / nCoefficient; if (nMaxQuotient < nQuotient) { nMaxQuotient = nQuotient; nEnteringColumnIndex = i; } } } // If the entering row isn't found, feasible solution isn't found too. if (nEnteringColumnIndex === 0) { this.setFeasible(false); //TODO call api to show error about solution isn't found return true; } // Check for cycles const aVarIndexByRow = this.getVarIndexByRow(); const aVarIndexByCol = this.getVarIndexByCol(); aVarIndexesCycle.push([aVarIndexByRow[nLeavingRowIndex], aVarIndexByCol[nEnteringColumnIndex]]); if (this.checkForCycles(aVarIndexesCycle)) { this.setFeasible(false); this.clearVarIndexesCycle(); this.getRepeatedVars().clear(); //TODO call api to show error about solution isn't found return true } this.pivot(nLeavingRowIndex, nEnteringColumnIndex); oModel.increaseCurrentAttempt(); return false; }; /** * Runs simplex on Initial Basic Feasible Solution (BFS) * Apply simplex to obtain optimal solution used as phase2 of the simplex * @memberof CSimplexTableau * @returns {boolean} The flag who recognizes end a loop of solver calculation. True - stop a loop, false - continue a loop. */ CSimplexTableau.prototype.phase2 = function () { const oModel = this.getModel(); const aMatrix = this.getMatrix(); const nRhsColumnId = this.getRhsColumn(); const nLastColumnId = this.getLastColumnIndex(); const nLastRowId = this.getLastRowIndex(); const aVarIndexesCycle = this.getVarIndexesCycle(); const nPrecision = this.getPrecision(); const aObjectiveRow = aMatrix[this.getObjectiveRowIndex()]; let nEnteringColumnIndex = 0; let nEnteringValue = nPrecision; let bReducedCostNegative = false; for (let col = 1; col <= nLastColumnId; col++) { const nObjectiveCoefValue = aObjectiveRow[col]; if (nObjectiveCoefValue > nEnteringValue) { nEnteringValue = nObjectiveCoefValue; nEnteringColumnIndex = col; bReducedCostNegative = false; } } // If no entering column could be found we're done with phase 2. Solution has been found. if (nEnteringColumnIndex === 0) { this.setSolutionIsFound(true); oModel.increaseCurrentFeasibleCount(); return true; } // Selecting leaving variable let nLeavingRowIndex = 0; let nMinQuotient = Infinity; for (let row = 1; row <= nLastRowId; row++) { const aRow = aMatrix[row]; const nRhsValue = aRow[nRhsColumnId]; const nColValue = aRow[nEnteringColumnIndex]; if (-nPrecision < nColValue && nColValue < nPrecision) { continue; } if (nColValue > 0 && nPrecision > nRhsValue && nRhsValue > -nPrecision) { nMinQuotient = 0; nLeavingRowIndex = row; break; } const nQuotient = bReducedCostNegative ? -nRhsValue / nColValue : nRhsValue / nColValue; if (nQuotient > nPrecision && nMinQuotient > nQuotient) { nMinQuotient = nQuotient; nLeavingRowIndex = row; } } if (nMinQuotient === Infinity) { // Optimal value is -Infinity or solution hasn't been found. this.setSolutionIsFound(false); this.setBounded(false); this.setUnboundVarIndex(this.getVarIndexByCol()[nEnteringColumnIndex]); //TODO call api to show error about solution isn't found return true; } // Check for cycles const aVarIndexByRow = this.getVarIndexByRow(); const aVarIndexByCol = this.getVarIndexByCol(); aVarIndexesCycle.push([aVarIndexByRow[nLeavingRowIndex], aVarIndexByCol[nEnteringColumnIndex]]); if (this.checkForCycles(aVarIndexesCycle)) { this.setFeasible(false); this.clearVarIndexesCycle(); this.getRepeatedVars().clear(); //TODO call api to show error about solution isn't found return true } this.pivot(nLeavingRowIndex, nEnteringColumnIndex); oModel.increaseCurrentAttempt(); return false; }; /** * Returns the main model object with the necessary data for solving a task by the Simplex method. * @memberof CSimplexTableau * @returns {CSolver} */ CSimplexTableau.prototype.getModel = function () { return this.oModel; }; /** * Returns constraint precision. * @memberof CSimplexTableau * @returns {number} */ CSimplexTableau.prototype.getPrecision = function () { return this.nPrecision; }; /** * Sets constraint precision. * @memberof CSimplexTableau * @param {number} nPrecision */ CSimplexTableau.prototype.setPrecision = function (nPrecision) { this.nPrecision = nPrecision; }; /** * Returns variables of model. It's the changing variable cells. * @memberof CSimplexTableau * @returns {Range} */ CSimplexTableau.prototype.getVariables = function () { return this.oVariables; }; /** * Sets variables of model. It's the changing variable cells. * @memberof CSimplexTableau * @param {Range} oVariables */ CSimplexTableau.prototype.setVariables = function (oVariables) { this.oVariables = oVariables; }; /** * Returns width of matrix. * @memberof * @returns {number} */ CSimplexTableau.prototype.getWidth = function () { return this.nWidth; }; /** * Sets width of matrix. * @memberof * @param {number} nWidth */ CSimplexTableau.prototype.setWidth = function (nWidth) { this.nWidth = nWidth; }; /** * Creates new array with constraints suitable for simplex calculate logic. * Method extracts ranges of ref cells and values of constraints for making a flat array. * @memberof CSimplexTableau * @param {CConstraint[]}aModelConstraints * @returns {CConstraint[]} */ CSimplexTableau.prototype.getSimplexConstraints = function (aModelConstraints) { /** @type {CConstraint[]} */ const aSimplexConstraints = []; for (let index = 0, length = aModelConstraints.length; index < length; index++) { const oConstraint = aModelConstraints[index]; const nOperator = oConstraint.getOperator(); const oRefCellsRange = oConstraint.getCell(); const constraintData = oConstraint.getConstraint(); let nConstraintIter = 0; let oConstraintBbox, bVertical, oConstraintWs; if (typeof constraintData === 'string') { continue; } if (typeof constraintData === 'object') { oConstraintBbox = constraintData.bbox; bVertical = oConstraintBbox.c1 === oConstraintBbox.c2; oConstraintWs = constraintData.worksheet; } oRefCellsRange._foreachNoEmpty(function (oRefCell) { /** @type {Cell} */ const oCell = oRefCell.clone(); let oNewConstraint, nConstraintVal; if (oRefCell.isFormula()) { oCell.formulaParsed = oRefCell.getFormulaParsed().clone(); } if (typeof constraintData === 'object') { const nRowConstraint = bVertical ? oConstraintBbox.r1 + nConstraintIter : oConstraintBbox.r1; const nColConstraint = bVertical ? oConstraintBbox.c1 : oConstraintBbox.c1 + nConstraintIter; if (nRowConstraint <= oConstraintBbox.r2 && nColConstraint <= oConstraintBbox.c2) { oConstraintWs._getCell(nRowConstraint, nColConstraint, function (oConstraintCell) { nConstraintVal = oConstraintCell.getNumberValue(); }); nConstraintIter++; } } else { nConstraintVal = constraintData; } if (nOperator === c_oAscOperator['=']) { // Changes the equal ("=") constraint to 2 constraints - ">=" and "<=". // Firstly adds a constraint with >= operator, then add <=. let nNewOperator = c_oAscOperator['>=']; oNewConstraint = new CConstraint(oCell, nNewOperator, nConstraintVal); aSimplexConstraints.push(oNewConstraint); nNewOperator = c_oAscOperator['<=']; oNewConstraint = new CConstraint(oCell, nNewOperator, nConstraintVal); aSimplexConstraints.push(oNewConstraint); } else { oNewConstraint = new CConstraint(oCell, nOperator, nConstraintVal); aSimplexConstraints.push(oNewConstraint); } }); } return aSimplexConstraints; }; /** * Returns height of matrix. * @memberof CSimplexTableau * @returns {number} */ CSimplexTableau.prototype.getHeight = function () { return this.nHeight; }; /** * Sets height of matrix. * @memberof CSimplexTableau * @param {number} nHeight */ CSimplexTableau.prototype.setHeight = function (nHeight) { this.nHeight = nHeight; }; /** * Returns array with indexes of constraints. * @memberof CSimplexTableau * @returns {number[]} */ CSimplexTableau.prototype.getConstraintsIndexes = function () { return this.aConstraintsIndexes; }; /** * Sets array of constraints indexes * @memberof CSimplexTableau * @param {number[]} aConstraintsIndexes */ CSimplexTableau.prototype.setConstraintsIndexes = function (aConstraintsIndexes) { this.aConstraintsIndexes = aConstraintsIndexes; }; /** * Fills array with indexes of constraints. * @memberof CSimplexTableau * @param {number} nTotalCountConstraints * @returns {number[]} */ CSimplexTableau.prototype.fillConstraintsIndexes = function (nTotalCountConstraints) { const aConstraintsIndexes = []; for (let i = 0; i < nTotalCountConstraints; i++) { aConstraintsIndexes.push(i); } return aConstraintsIndexes; }; /** * Returns array with indexes of variables. * @memberof CSimplexTableau * @returns {number[]} */ CSimplexTableau.prototype.getVariablesIndexes = function () { return this.aVariablesIndexes; }; /** * Sets array of constraints indexes * @memberof CSimplexTableau * @param {number[]} aVariablesIndexes */ CSimplexTableau.prototype.setVariablesIndexes = function (aVariablesIndexes) { this.aVariablesIndexes = aVariablesIndexes; }; /** * Fills array with indexes of variables. * @memberof CSimplexTableau * @param {number} nTotalCountVariables * @param {number} nLastIndexElement * @returns {number[]} */ CSimplexTableau.prototype.fillVariablesIndexes = function (nTotalCountVariables, nLastIndexElement) { const aVariablesIndexes = []; for (let j = 0; j < nTotalCountVariables; j++) { aVariablesIndexes.push(nLastIndexElement++); } return aVariablesIndexes; }; /** * @memberof CSimplexTableau * @returns {number[][]} */ CSimplexTableau.prototype.getMatrix = function () { return this.aMatrix; }; /** * Sets empty matrix that needs to be filled using addRow method. * @memberof CSimplexTableau * @param {[]}aMatrix */ CSimplexTableau.prototype.setMatrix = function (aMatrix) { this.aMatrix = aMatrix; }; /** * Adds rows for filling matrix. * @memberof CSimplexTableau * @param {number[]} aRow * @param {number} nRowIndex */ CSimplexTableau.prototype.addRow = function (aRow, nRowIndex) { const aMatrix = this.getMatrix(); aMatrix[nRowIndex] = aRow; }; /** * Builds an empty matrix. * @memberof CSimplexTableau * @returns {number[][]} */ CSimplexTableau.prototype.createMatrix = function () { const nWidth = this.getWidth(); const nHeight = this.getHeight(); const tmpRow = new Array(nWidth); for (let i = 0, length = nWidth; i < length; i++) { tmpRow[i] = 0; } this.setMatrix(new Array(nHeight)); for (let j = 0, length = nHeight; j < length; j++) { this.addRow(tmpRow.slice(), j); } return this.getMatrix(); }; /** * Extracts arguments from parserFormula. * @memberof CSimplexTableau * @param {[]} aOutStackFormula - arguments of formula * @param {boolean} bIsEmpty - flag recognizes whether empty arguments are allowed * @returns {Cell[]} */ CSimplexTableau.prototype.getArgsFormula = function (aOutStackFormula, bIsEmpty) { const aArgsFormula = []; AscCommonExcel.foreachRefElements(function (oRange) { oRange._foreachNoEmpty(function (oCell) { if (oCell.isFormula() || oCell.getNumberValue() || bIsEmpty) { const oTempCell = oCell.clone(); if (oCell.isFormula()) { oTempCell.formulaParsed = oCell.getFormulaParsed().clone(); } aArgsFormula.push(oTempCell); } }) }, aOutStackFormula); return aArgsFormula; }; /** * Returns an object that stores the variable index by cell name key. * @memberof CSimplexTableau * @returns {{}} */ CSimplexTableau.prototype.getVarIndexByCellName = function () { if (!this.oVarIndexByCellName) { this.oVarIndexByCellName = {}; } return this.oVarIndexByCellName; }; /** * Fills matrix by variable and constraints data. * @memberof CSimplexTableau * @param {number[][]} aMatrix * @param {CConstraint[]} aConstraints */ CSimplexTableau.prototype.fillMatrix = function (aMatrix, aConstraints) { const oThis = this; const aConstraintsIndexes = this.getConstraintsIndexes(); const aVariablesIndexes = this.getVariablesIndexes(); const aObjectiveFuncRow = aMatrix[0]; const oModel = this.getModel(); const oVariables = this.getVariables(); const nCoeff = oModel.getOptimizeResultTo() === c_oAscOptimizeTo.min ? -1 : 1; // Extracts coefficients from objective function const oObjectiveFunc = oModel.getParsedFormula(); const aArgsObjectiveFunc = this.getArgsFormula(oObjectiveFunc.outStack, false); // Links variable with variable's index let nIter = 0; const oVarIndexByCellName = this.getVarIndexByCellName(); oVariables._foreachNoEmpty(function (oCell) { oVarIndexByCellName[oCell.getName()] = aVariablesIndexes[nIter]; nIter++; }); // Fills the matrix's first row coefficients of the objective function for (let i = 0, length = aArgsObjectiveFunc.length; i < length; i++) { function getVal (nValue) { aObjectiveFuncRow[i + 1] = nValue * nCoeff; oThis.addRowByVarIndexElem(-1, nVarIndex); oThis.addColByVarIndexElem(i + 1, nVarIndex); oThis.addVarIndexByColElem(nVarIndex, i + 1); } const oArg = aArgsObjectiveFunc[i]; let nVarIndex = aVariablesIndexes[i]; if (oArg.isFormula() && !oArg.getNumberValue()) { // Tries to find the coefficient in linked cells const aArgsFormula = this.getArgsFormula(oArg.getFormulaParsed().outStack, false); aArgsFormula.forEach(function (oCell) { const nValue = oCell.getNumberValue(); if (nValue) { getVal(nValue); } }); } else if (oArg.getNumberValue()) { getVal(oArg.getNumberValue()); } } // Fills remain matrix's rows by constraints const FIRST_COLUMN_INDEX = 0; const aColByVarIndex = this.getColByVarIndex(); let nRowIndex = 1; let coefficient = 1; for (let j = 0, length = aConstraints.length; j < length; j++) { function getVariable(oCell) { const nColumn = aColByVarIndex[oVarIndexByCellName[oCell.getName()]]; aRow[nColumn] = nOperator === c_oAscOperator['<='] ? coefficient : -coefficient; } const oConstraint = aConstraints[j]; const nOperator = oConstraint.getOperator(); const oRefCell = oConstraint.getCell(); /**@type {number} */ const nConstraintValue = oConstraint.getConstraint(); const nConstraintIndex = aConstraintsIndexes[j]; this.addRowByVarIndexElem(nRowIndex, nConstraintIndex); this.addColByVarIndexElem(-1, nConstraintIndex); this.addVarIndexByRowElem(nConstraintIndex, nRowIndex); const aRow = aMatrix[nRowIndex++]; // Work with terms if (oRefCell.isFormula()) { const aArgsFormula = this.getArgsFormula(oRefCell.getFormulaParsed().outStack, true); aArgsFormula.forEach(function (oLinkedCell) { if (oVariables.bbox.contains(oLinkedCell.nCol, oLinkedCell.nRow)) { if (oLinkedCell.isFormula()) {// Try to find coefficient for variable const aOutStack = oLinkedCell.getFormulaParsed().outStack; const aArgsLinkFormula = oThis.getArgsFormula(aOutStack, false); aArgsLinkFormula.forEach(function (oCell) { if (oCell.getNumberValue()) { coefficient = oCell.getNumberValue(); } }); } getVariable(oLinkedCell); } }); } else if (oVariables.bbox.contains(oRefCell.nCol, oRefCell.nRow)) { getVariable(oRefCell); } aRow[FIRST_COLUMN_INDEX] = nOperator === c_oAscOperator['<='] ? nConstraintValue : -nConstraintValue; } }; /** * @memberof CSimplexTableau * @returns {number[]} */ CSimplexTableau.prototype.getVarIndexByRow = function () { return this.aVarIndexByRow; }; /** * Sets an empty array for attribute aVarIndexByRow * @memberof CSimplexTableau * @param {number[]} aVarIndexByRow */ CSimplexTableau.prototype.setVarIndexByRow = function (aVarIndexByRow) { this.aVarIndexByRow = aVarIndexByRow; }; /** * Adds element to varIndexByRow array. * @memberof CSimplexTableau * @param {number} nValue * @param {number} nIndex */ CSimplexTableau.prototype.addVarIndexByRowElem = function (nValue, nIndex) { this.aVarIndexByRow[nIndex] = nValue; }; /** * Returns array of indexes of variables by matrix's columns. * @memberof CSimplexTableau * @returns {number[]} */ CSimplexTableau.prototype.getVarIndexByCol = function () { return this.aVarIndexByCol; }; /** * Sets an empty array for attribute aVarIndexByCol. * @memberOf CSimplexTableau * @param {[]} aVarIndexByCol */ CSimplexTableau.prototype.setVarIndexByCol = function (aVarIndexByCol) { this.aVarIndexByCol = aVarIndexByCol; }; /** * Adds element to varIndexByCol array. * @memberof CSimplexTableau * @param {number} nValue * @param {number} nIndex */ CSimplexTableau.prototype.addVarIndexByColElem = function (nValue, nIndex) { this.aVarIndexByCol[nIndex] = nValue; }; /** * Returns total count of variables including constraints. * @memberof CSimplexTableau * @returns {number} */ CSimplexTableau.prototype.getVarsCount = function () { return this.nVars; }; /** * Sets total count of variables including constraints. * @memberof CSimplexTableau * @param {number} nVars */ CSimplexTableau.prototype.setVarsCount = function (nVars) { this.nVars = nVars; }; /** * Returns array of row's indexes by variable' index. Shows which row of matrix located variable. * @memberof CSimplexTableau * @returns {number[]} */ CSimplexTableau.prototype.getRowByVarIndex = function () { return this.aRowByVarIndex; }; /** * Sets empty array of row's indexes by variable' index. Shows which row of matrix located variable. * @memberof CSimplexTableau * @param {[]}aRowByVarIndex */ CSimplexTableau.prototype.setRowByVarIndex = function (aRowByVarIndex) { this.aRowByVarIndex = aRowByVarIndex; }; /** * Adds element to array of row's indexes by variable' index. Shows which row of matrix located variable. * @memberof CSimplexTableau * @param {number} nValue * @param {number} nIndex */ CSimplexTableau.prototype.addRowByVarIndexElem = function (nValue, nIndex) { this.aRowByVarIndex[nIndex] = nValue; }; /** * Returns array of column's indexes by variable' index. Shows which column of matrix located variable. * @memberof CSimplexTableau * @returns {number[]} */ CSimplexTableau.prototype.getColByVarIndex = function () { return this.aColByVarIndex; }; /** * Sets empty array of column's indexes by variable' index. Shows which column of matrix located variable. * @param {[]} aColByVarIndex */ CSimplexTableau.prototype.setColByVarIndex = function (aColByVarIndex) { this.aColByVarIndex = aColByVarIndex; }; /** * Adds element to array of column's indexes by variable' index. Shows which column of matrix located variable. * @memberof CSimplexTableau * @param {number} nValue * @param {number} nIndex */ CSimplexTableau.prototype.addColByVarIndexElem = function (nValue, nIndex) { this.aColByVarIndex[nIndex] = nValue; }; /** * Returns index of the matrix's last element. * @memberof CSimplexTableau * @returns {number} */ CSimplexTableau.prototype.getLastElementIndex = function () { return this.nLastElementIndex; }; /** * Sets index of the matrix's last element. * @memberof CSimplexTableau * @param {number} nLastElementIndex */ CSimplexTableau.prototype.setLastElementIndex = function (nLastElementIndex) { this.nLastElementIndex = nLastElementIndex; }; /** * Returns the index of last column in matrix. * @memberof CSimplexTableau * @returns {number} */ CSimplexTableau.prototype.getLastColumnIndex = function () { return this.nLastColumnId }; /** * Sets the index of last column in matrix. * @memberof CSimplexTableau * @param {number} nLastColumnIndex */ CSimplexTableau.prototype.setLastColumnIndex = function (nLastColumnIndex) { this.nLastColumnId = nLastColumnIndex; }; /** * Returns the index of last row in matrix. * @memberof CSimplexTableau * @returns {number} */ CSimplexTableau.prototype.getLastRowIndex = function () { return this.nLastRowId; }; /** * Sets the index of last row in matrix. * @memberof CSimplexTableau * @param {number} nLastRowIndex */ CSimplexTableau.prototype.setLastRowIndex = function (nLastRowIndex) { this.nLastRowId = nLastRowIndex; }; /** * Returns bounded attribute * @memberof CSimplexTableau * @returns {boolean} */ CSimplexTableau.prototype.getBounded = function () { return this.bBounded; }; /** * Sets bounded attribute * @memberof CSimplexTableau * @param {boolean} bBounded */ CSimplexTableau.prototype.setBounded = function (bBounded) { this.bBounded = bBounded; }; /** * Returns feasible attribute * @memberof CSimplexTableau * @returns {boolean} */ CSimplexTableau.prototype.getFeasible = function () { return this.bFeasible; }; /** * Sets feasible attribute * @memberof CSimplexTableau * @param {boolean} bFeasible */ CSimplexTableau.prototype.setFeasible = function (bFeasible) { this.bFeasible = bFeasible; }; /** * Returns the index's column with the right-hand side value of the constraint or objective function. * @memberof CSimplexTableau * @returns {number} */ CSimplexTableau.prototype.getRhsColumn = function () { return this.nRhsColumn; }; /** * Returns an array with the index's variables in cycle. * @memberof CSimplexTableau * @returns {[number,number][]} */ CSimplexTableau.prototype.getVarIndexesCycle = function () { return this.aVarIndexesCycle; }; /** * Clears elements of array with the index's variables in cycle */ CSimplexTableau.prototype.clearVarIndexesCycle = function () { this.aVarIndexesCycle = []; }; /** * Returns repeated basic vars. * This Map is used for checking basic variables are repeated. * If the whole basic table is repeated. It's a cycle * The map stores here indexes of repeated basic variables. * @memberof CSimplexTableau * @returns {Map} */ CSimplexTableau.prototype.getRepeatedVars = function () { return this.mRepeatedVars; }; /** * Checks whether the same sequence of basic variables occurred twice in a row. * @memberOf CSimplexTableau * @param {[number,number][]} aVarIndexesCycle * @returns {boolean} */ CSimplexTableau.prototype.checkForCycles = function (aVarIndexesCycle) { const mRepeatedVars = this.getRepeatedVars(); for (let firstIndex = 0, len1 = aVarIndexesCycle.length - 1; firstIndex < len1; firstIndex++) { for (let secondIndex = firstIndex + 1, len2 = aVarIndexesCycle.length; secondIndex < len2; secondIndex++) { const aFirstVarIndexes = aVarIndexesCycle[firstIndex]; const aSecondVarIndexes = aVarIndexesCycle[secondIndex]; if (aFirstVarIndexes[0] === aSecondVarIndexes[0] && aFirstVarIndexes[1] === aSecondVarIndexes[1]) { // Continues the logic of recognizing a cycle only when the whole basic variable is repeated the second time in a row. if (secondIndex - firstIndex > len2 - secondIndex) { // Check and fill (if it's missing) the indexes of repeated basic variables. if (!mRepeatedVars.has(firstIndex + secondIndex)) { mRepeatedVars.set(firstIndex + secondIndex, [firstIndex, secondIndex]); } break; } let bCycleFound = true; // Checks whether the last indexes of the basic variable from the first loop (presumably) // and the last indexes from the whole array of passed basic variables are equal. const aFirstLastIdsVar = aVarIndexesCycle[secondIndex - 1]; const aSecondLastIdsVar = aVarIndexesCycle[len2 - 1]; if (aFirstLastIdsVar[0] !== aSecondLastIdsVar[0] && aFirstLastIdsVar[1] !== aSecondLastIdsVar[1]) { bCycleFound = false; continue; } // Check and fill (if it's missing) the last indexed of repeated basic variables. if (!mRepeatedVars.has((secondIndex - 1) - (len2 - 1))) { mRepeatedVars.set((secondIndex - 1) - (len2 - 1), [secondIndex - 1, len2 - 1]); } if (bCycleFound && len2 - secondIndex === mRepeatedVars.size) { return true; } } } } return false; }; /** * Executes pivot operations over a matrix, on a given row and column. * @memberof CSimplexTableau * @param {number} nLeavingRowIndex * @param {number} nEnteringColumnIndex */ CSimplexTableau.prototype.pivot = function (nLeavingRowIndex, nEnteringColumnIndex) { const aMatrix = this.getMatrix(); const aPivotRow = aMatrix[nLeavingRowIndex]; const nQuotient = aMatrix[nLeavingRowIndex][nEnteringColumnIndex]; const nLastRow = this.getLastRowIndex(); const nLastColumn = this.getLastColumnIndex(); const aVarIndexByRow = this.getVarIndexByRow(); const aVarIndexByCol = this.getVarIndexByCol(); const aRowByVarIndex = this.getRowByVarIndex(); const aColByVarIndex = this.getColByVarIndex(); const nLeavingBasicIndex = aVarIndexByRow[nLeavingRowIndex]; const nEnteringBasicIndex = aVarIndexByCol[nEnteringColumnIndex]; aVarIndexByRow[nLeavingRowIndex] = nEnteringBasicIndex; aVarIndexByCol[nEnteringColumnIndex] = nLeavingBasicIndex; aRowByVarIndex[nEnteringBasicIndex] = nLeavingRowIndex; aRowByVarIndex[nLeavingBasicIndex] = -1; aColByVarIndex[nEnteringBasicIndex] = -1; aColByVarIndex[nLeavingBasicIndex] = nEnteringColumnIndex; // Step 1: Transforming pivot row. Calculate new pivot row using formula: newPivotRowElem = oldPivotRowElem / quotient (leading element) /** @type {number[]} */ const aNonZeroColumns = []; let nNonZeroColumnId = 0; for (let col = 0; col <= nLastColumn; col++) { // Checks that the value of pivotRow isn't zero, considering variation in calculation. if (!(aPivotRow[col] >= -1e-16 && aPivotRow[col] <= 1e-16)) { aPivotRow[col] /= nQuotient; aNonZeroColumns[nNonZeroColumnId++] = col; } else { aPivotRow[col] = 0; } } aPivotRow[nEnteringColumnIndex] = 1 / nQuotient; // Step 2: Calculate new rows using formula: newRow = oldRow - oldCoeffOfEnteringVar * newPivotRow for (let row = 0; row <= nLastRow; row++) { if (row === nLeavingRowIndex) { continue; } // Checks that the value of row isn't zero, considering variation in calculation. if (aMatrix[row][nEnteringColumnIndex] >= -1e-16 && aMatrix[row][nEnteringColumnIndex] <= 1e-16) { continue; } const aRow = aMatrix[row]; const nCoefficient = aRow[nEnteringColumnIndex]; if (!(nCoefficient >= -1e-16 && nCoefficient <= 1e-16)) { for (let nonZeroColId = 0; nonZeroColId < nNonZeroColumnId; nonZeroColId++) { let nCol = aNonZeroColumns[nonZeroColId]; const nPivotRowElem = aPivotRow[nCol]; if (!(nPivotRowElem >= -1e-16 && nPivotRowElem <= 1e-16)) { aRow[nCol] -= nCoefficient * nPivotRowElem; } else if (nPivotRowElem !== 0) { aPivotRow[nCol] = 0; } } aRow[nEnteringColumnIndex] = -nCoefficient / nQuotient; } else if (nCoefficient !== 0) { aRow[nEnteringColumnIndex] = 0; } } }; /** * Returns index of row with objective function data. * @memberof CSimplexTableau * @returns {number} */ CSimplexTableau.prototype.getObjectiveRowIndex = function () { return this.nObjectiveRowIndex; }; /** * Returns a flag whether a solution has been found. * @memberof CSimplexTableau * @returns {boolean} */ CSimplexTableau.prototype.getSolutionIsFound = function () { return this.bSolutionIsFound; }; /** * Sets a flag whether a solution has been found. * @memberof CSimplexTableau * @param {boolean} bSolutionIsFound */ CSimplexTableau.prototype.setSolutionIsFound = function (bSolutionIsFound) { this.bSolutionIsFound = bSolutionIsFound; }; /** * Returns unbounded index of variable. * @memberof CSimplexTableau * @returns {number} */ CSimplexTableau.prototype.getUnboundVarIndex = function () { return this.nUnboundedVarIndex; }; /** * Sets unbounded index of variable. * @memberof CSimplexTableau * @param {number} nUnboundVarIndex */ CSimplexTableau.prototype.setUnboundVarIndex = function (nUnboundVarIndex) { this.nUnboundedVarIndex = nUnboundVarIndex; }; /** * Updates "Changing variables Cells" values. * @memberof CSimplexTableau */ CSimplexTableau.prototype.updateVariableValues = function () { const oVariablesCells = this.getVariables(); const nRoundingCoefficient = Math.round(1 / this.getPrecision()); const oVarIndexByCellName = this.getVarIndexByCellName(); const aRowByVarIndex = this.getRowByVarIndex(); const nRhsColumn = this.getRhsColumn(); const aMatrix = this.getMatrix(); const sRegNumDecimalSeparator = this.getModel().getRegNumDecimalSeparator(); oVariablesCells._foreachNoEmpty(function (oCell) { const sCellName = oCell.getName(); const nVarIndex = oVarIndexByCellName[sCellName]; const nRowId = aRowByVarIndex[nVarIndex]; if (nRowId !== -1) { const nVarValue = aMatrix[nRowId][nRhsColumn]; const nResult = Math.round((nVarValue + Number.EPSILON) * nRoundingCoefficient) / nRoundingCoefficient; oCell.setValue(String(nResult).replace('.', sRegNumDecimalSeparator)); } }); }; // Main class of solver feature /** * Class representing a solver feature. * @param {asc_CSolverParams} oParams - solver parameters * @param {Worksheet} oWs * @constructor */ function CSolver (oParams, oWs) { const oParsedFormula = this.convertToCell(oParams.getObjectiveFunction(), oWs).getFormulaParsed(); const oChangingCellsWs = actualWsByRef(oParams.getChangingCells(), oWs); const sChangingCells = convertToAbsoluteRef(oParams.getChangingCells()); CBaseAnalysis.call(this, oParsedFormula, oChangingCellsWs.getRange2(sChangingCells)); // Solver parameters this.nOptimizeResultTo = oParams.getOptimizeResultTo(); this.nValueOf = oParams.getValueOf() !== null ? parseFloat(oParams.getValueOf().replace(/,/g, ".")) : null; this.aConstraints = this.initConstraints(oParams.getConstraints(), oWs); this.bIsVarsNonNegative = oParams.getVariablesNonNegative(); this.nSolvingMethod = oParams.getSolvingMethod(); // Attributes for calculating logic this.nStartTime = null; this.nCurrentSubProblem = 0; this.nCurrentFeasibleCount = 0; this.nGradient = null; this.oSimplexTableau = null; this.oStartChangingCells = null; // Calculating option this.oOptions = oParams.getOptions(); // Updating nMaxIterations attribute of super class to value from calculating options. this.setMaxIterations(parseFloat(this.oOptions.getIterations())); } CSolver.prototype = Object.create(CBaseAnalysis.prototype); CSolver.prototype.constructor = CSolver; /** * Prepares data for calculating. * @memberof CSolver */ CSolver.prototype.prepare = function () { const oThis = this; const aConstraints = this.getConstraints(); const nSolutionMethod = this.getSolvingMethod(); const oChangingCells = this.getChangingCell(); // Fills empty cells to 0 value for changing cells oChangingCells._foreachNoEmpty(function (oCell) { oThis.setStartChangingCells(oCell.getName(), oCell.getValueWithoutFormat()); // Saves original data if (oCell.getNumberValue() === null) { if (oCell.getType() !== CellValueType.Number) { oCell.setTypeInternal(CellValueType.Number); } oCell.setValueNumberInternal(0); } }); switch (nSolutionMethod) { case c_oAscSolvingMethod.grgNonlinear: break; case c_oAscSolvingMethod.simplexLP: this.setSimplexTableau(new CSimplexTableau(this)) const oSimplexTableau = this.getSimplexTableau(); oSimplexTableau.init(aConstraints); break; case c_oAscSolvingMethod.evolutionary: break; } }; /** * Main logic of solver calculating. * Runs only in sync or async loop. * @memberof CSolver * @returns {boolean} The flag who recognizes end a loop of solver calculation. True - stop a loop, false - continue a loop. */ CSolver.prototype.calculate = function () { if (this.getIsPause()) { return true; } const nSolutionMethod = this.getSolvingMethod(); let bCompleteCalculation = false; if (this.isFinishCalculating()) { return true; } switch (nSolutionMethod) { case c_oAscSolvingMethod.grgNonlinear: bCompleteCalculation = this.grgOptimization(); break; case c_oAscSolvingMethod.simplexLP: bCompleteCalculation = this.simplexOptimization(); break; case c_oAscSolvingMethod.evolutionary: bCompleteCalculation = this.evolutionOptimization(); break; } if (this.getIsSingleStep()) { this.setIsPause(true); this.setIsSingleStep(false); return true; } return bCompleteCalculation; }; /** * Converts cell reference from UI to Cell object. * @memberof CSolver * @param {string} sCellRef * @param {Worksheet}oWs * @returns {Cell} */ CSolver.prototype.convertToCell = function (sCellRef, oWs) { const oCellWs = actualWsByRef(sCellRef, oWs); const sCellActualRef = convertToAbsoluteRef(sCellRef); const oCellRange = oCellWs.getCell2(sCellActualRef); let oCell = null; oCellWs._getCell(oCellRange.bbox.r1, oCellRange.bbox.c1, function (oElem) { oCell = oElem; }); return oCell; }; /** * Initializes the constraints array with necessary for solving data. * @memberof CSolver * @param {Map} oConstraints * @param {Worksheet} oWs * @returns {CConstraint[]} */ CSolver.prototype.initConstraints = function (oConstraints, oWs) { const aExcludeWords = ['integer', 'binary', 'AllDifferent']; const aConstraints = []; oConstraints.forEach(function (oConstraint) { const oConstraintsWs = actualWsByRef(oConstraint.constraint, oWs); const sConstraintsActualRef = convertToAbsoluteRef(oConstraint.constraint); const oCellRefWs = actualWsByRef(oConstraint.cellRef, oWs); const sCellRefActualRef = convertToAbsoluteRef(oConstraint.cellRef); let constraintData = oConstraintsWs.getRange2(sConstraintsActualRef); if (constraintData == null) { constraintData = aExcludeWords.includes(oConstraint.constraint) ? oConstraint.constraint : Number(oConstraint.constraint.replace(/,/g, ".")); } aConstraints.push(new CConstraint(oCellRefWs.getRange2(sCellRefActualRef), oConstraint.operator, constraintData)); }); if (this.getVariablesNonNegative()) { aConstraints.push(new CConstraint(this.getChangingCell(), c_oAscOperator['>='], 0)); } return aConstraints; }; /** * Calculates and returns an array of constraint results. * @memberof CSolver * @returns {boolean[]} */ CSolver.prototype.calculateConstraints = function () { // todo Need to rework. Is it needed? const oThis = this; /** @type {boolean[]} */ const aConstraintsResult = []; const aConstraints = this.getConstraints(); const oOptions = this.getOptions(); const nConstraintPrecision = Number(oOptions.getConstraintPrecision().replace(/,/g, ".")); const bIgnoreIntConstraints = oOptions.getIgnoreIntConstraints(); for (let i = 0, length = aConstraints.length; i < length; i++) { const oConstraintCell = aConstraints[i].getCell(); let bResult = false; /** @type {Cell} */ let oPrevConstraintCell = null; oConstraintCell._foreachNoEmpty(function (oElem, nIndex, nCol, nStartRow) { if (oElem.getNumberValue() !== null) { const constraintData = aConstraints[i].getConstraint(); const nOperator = aConstraints[i].getOperator(); const oWs = oElem.ws; let constraintValue = typeof constraintData === 'object' ? oThis.convertToCell(constraintData.getName(), oWs).getNumberValue() : constraintData; if (constraintValue === 'integer' && !bIgnoreIntConstraints) { bResult = oElem.getNumberValue() === parseInt(oElem.getNumberValue()); } else if (constraintValue === 'binary' && !bIgnoreIntConstraints) { bResult = oElem.getNumberValue() === 0 || oElem.getNumberValue() === 1; } else if (constraintValue === 'AllDifferent' && !bIgnoreIntConstraints && nIndex !== nStartRow) { bResult = oPrevConstraintCell.getNumberValue() !== oElem.getNumberValue(); } else { switch (nOperator) { case c_oAscOperator['=']: let nDiff = constraintValue - oElem.getNumberValue(); bResult = nDiff < nConstraintPrecision; break; case c_oAscOperator['>=']: bResult = oElem.getNumberValue() >= constraintValue - nConstraintPrecision; break; case c_oAscOperator['<=']: bResult = oElem.getNumberValue() <= constraintValue + nConstraintPrecision; break; } } oPrevConstraintCell = oElem; if (!bResult) { return true; // break loop } } }); aConstraintsResult.push(bResult); } return aConstraintsResult; }; /** * Checks whether limits are exceeded from options. * e.g., exceeds of maximum: iterations, time, subproblems, etc. * @memberof CSolver * @returns {boolean} */ CSolver.prototype.isFinishCalculating = function () { const nCurrentTime = Date.now(); const nMaxIterations = this.getMaxIterations(); const oOptions = this.getOptions(); const nTimeMax = parseFloat(oOptions.getMaxTime()); const nMaxSubproblems = parseFloat(oOptions.getMaxSubproblems()); const nMaxFeasibleSolution = parseFloat(oOptions.getMaxFeasibleSolution()); let bIsTimeMax = false; let bIterationIsReached = false; let bMaxSubproblems = false; let bMaxFeasibleSolution = false; if (!isNaN(nTimeMax)) { bIsTimeMax = nCurrentTime - this.getStartTime() >= nTimeMax * 1000; } if (!isNaN(nMaxIterations)) { bIterationIsReached = this.getCurrentAttempt() >= nMaxIterations; } if (!isNaN(nMaxSubproblems)) { bMaxSubproblems = this.getCurrentSubProblem() >= nMaxSubproblems; } if (!isNaN(nMaxFeasibleSolution)) { bMaxFeasibleSolution = this.getCurrentFeasibleCount() >= nMaxFeasibleSolution; } return bIsTimeMax || bIterationIsReached || bMaxSubproblems || bMaxFeasibleSolution; }; /** * Tries to find solution by GRG (Generalized reduced gradient) method. * Uses for non-linear programming tasks. * @memberof CSolver * @returns {boolean} The flag who recognizes end a loop of solver calculation. True - stop a loop, false - continue a loop. */ CSolver.prototype.grgOptimization = function () { const oChangingCells = this.getChangingCell(); const aConstraints = this.getConstraints(); const nOptimizeResultTo = this.getOptimizeResultTo(); const oOptions = this.getOptions(); const nDerivatives = oOptions.getDerivatives(); oChangingCells._foreachNoEmpty(function (oChangingCell) { }); }; /** * Tries to find solution by Simplex method. * Uses for LP tasks. * @memberof CSolver * @returns {boolean} The flag who recognizes end a loop of solver calculation. True - stop a loop, false - continue a loop. */ CSolver.prototype.simplexOptimization = function () { const oSimplexTableau = this.getSimplexTableau(); const oOptions = this.getOptions(); const bShowIterResults = oOptions.getShowIterResults(); let bCompleteCalculation = oSimplexTableau.calculate(); if (bShowIterResults && !bCompleteCalculation) { this.fillResult(); } if (bCompleteCalculation && oSimplexTableau.getSolutionIsFound()) { this.fillResult(); } return bCompleteCalculation; }; /** * Tries to find solution by Evolutionary method. * Uses for non-smooth solver problems. * @memberof CSolver * @returns {boolean} The flag who recognizes end a loop of solver calculation. True - stop a loop, false - continue a loop. */ CSolver.prototype.evolutionOptimization = function () { }; /** * Resumes calculation by one step than pause it again. * @memberof CSolver */ CSolver.prototype.step = function () { let oSolver = this; this.setIsPause(false); this.setIsSingleStep(true); this.setIntervalId(setInterval(function() { let bIsFinish = oSolver.calculate(); if (bIsFinish) { clearInterval(oSolver.getIntervalId()); } }, this.getDelay())); }; /** * Fills "Changing Variable Cells" values from result of calculation. * @memberOf CSolver */ CSolver.prototype.fillResult = function () { const nSolutionMethod = this.getSolvingMethod(); switch (nSolutionMethod) { case c_oAscSolvingMethod.grgNonlinear: break; case c_oAscSolvingMethod.simplexLP: const oSimplexTableau = this.getSimplexTableau(); oSimplexTableau.updateVariableValues(); break; case c_oAscSolvingMethod.evolutionary: break; } }; /** * Returns value of "Optimize to" parameter * @memberof CSolver * @returns {c_oAscOptimizeTo} */ CSolver.prototype.getOptimizeResultTo = function () { return this.nOptimizeResultTo; }; /** * Returns converted to number type value of "Value of" input field from "To" parameter. * @memberof CSolver * @returns {null|number} */ CSolver.prototype.getValueOf = function () { return this.nValueOf; }; /** * Returns the array of constraints. * @memberof CSolver * @returns {CConstraint[]} */ CSolver.prototype.getConstraints = function () { return this.aConstraints; }; /** * Returns value of "Make Unconstrained Variables Non-Negative". * @memberof CSolver * @returns {boolean} */ CSolver.prototype.getVariablesNonNegative = function () { return this.bIsVarsNonNegative; }; /** * Returns the solving method. * @memberof CSolver * @returns {c_oAscSolvingMethod} */ CSolver.prototype.getSolvingMethod = function () { return this.nSolvingMethod; }; /** * Returns solver options. * @memberof CSolver * @returns {asc_COptions} */ CSolver.prototype.getOptions = function () { return this.oOptions; }; /** * Sets time from start calculation process. * @memberof CSolver * @param {number} nStartTime */ CSolver.prototype.setStartTime = function (nStartTime) { this.nStartTime = nStartTime; }; /** * Returns time from start calculation process. * @returns {number} */ CSolver.prototype.getStartTime = function () { return this.nStartTime; }; /** * Sets current number of subproblem. * @memberof CSolver * @param {number} nCurrentSubProblem */ CSolver.prototype.setCurrentSubProblem = function (nCurrentSubProblem) { this.nCurrentSubProblem = nCurrentSubProblem; }; /** * Returns current number of subproblem. * @memberof CSolver * @returns {number} */ CSolver.prototype.getCurrentSubProblem = function () { return this.nCurrentSubProblem; }; /** * Increases number of subproblem. * @memberof CSolver */ CSolver.prototype.increaseCurrentSubProblem = function () { this.nCurrentSubProblem++; }; /** * Return current count of feasible solution. * @memberof CSolver * @returns {number} */ CSolver.prototype.getCurrentFeasibleCount = function () { return this.nCurrentFeasibleCount; }; /** * Increases current count of feasible solution. * @memberof CSolver */ CSolver.prototype.increaseCurrentFeasibleCount = function () { this.nCurrentFeasibleCount++; }; /** * Sets result of gradient for determine direction of motion for nonbasic variables. * @memberof CSolver * @param {number} nGradient */ CSolver.prototype.setGradient = function (nGradient) { this.nGradient = nGradient; }; /** * Returns result of gradient for determine direction of motion for nonbasic variables. * @memberof CSolver * @returns {number} */ CSolver.prototype.getGradient = function () { return this.nGradient; }; /** * Sets simplex tableau object. Using for calculating by simplex method. * @memberof CSolver * @param {CSimplexTableau} oSimplexTableau */ CSolver.prototype.setSimplexTableau = function (oSimplexTableau) { this.oSimplexTableau = oSimplexTableau; }; /** * Gets simplex tableau object. Using for calculating by simplex method. * @memberof CSolver * @returns {CSimplexTableau} */ CSolver.prototype.getSimplexTableau = function () { return this.oSimplexTableau; }; /** * Sets start range with original data * @memberof CSolver * @param {string} sCellName * @param {string} sValue */ CSolver.prototype.setStartChangingCells = function (sCellName, sValue) { if (this.oStartChangingCells == null) { this.oStartChangingCells = {}; } this.oStartChangingCells[sCellName] = sValue; }; /** * Returns start range with original data. * @memberof CSolver * @returns {{cellName:string, value:string}} */ CSolver.prototype.getStartChangingCells = function () { return this.oStartChangingCells; }; /** * Returns error type * @memberof CSolver * @param {AscCommonExcel.Worksheet} ws - checked sheet. * @param {Asc.Range} range - checked range. * @param {Asc.c_oAscSelectionDialogType} type - dialog type. * @returns {Asc.c_oAscError} */ CSolver.prototype.isValidDataRef = function(ws, range, type) { let res = Asc.c_oAscError.ID.No; if (range && !range.isOneCell()) { //error text: reference must be to a single cell... //TODO check def names res = Asc.c_oAscError.ID.MustSingleCell; } switch (type) { case Asc.c_oAscSelectionDialogType.GoalSeek_Cell: { //check formula contains let isFormula = false; let isNumberResult = true; //MustFormulaResultNumber ws && ws._getCellNoEmpty(range.r1, range.c1, function (cell) { if (cell && cell.isFormula()) { isFormula = true; if (cell.number == null) { isNumberResult = false; } } }); if (!isFormula) { res = Asc.c_oAscError.ID.MustContainFormula; } else if (!isNumberResult) { res = Asc.c_oAscError.ID.MustFormulaResultNumber; } break; } } return res; }; // Export window['AscCommonExcel'] = window['AscCommonExcel'] || {}; window['AscCommonExcel'].CGoalSeek = CGoalSeek; window['AscCommonExcel'].CSolver = CSolver; window['AscCommonExcel'].actualWsByRef = actualWsByRef; window['AscCommonExcel'].convertToAbsoluteRef = convertToAbsoluteRef; // Collections and classes for UI part window['AscCommonExcel'].c_oAscDerivativeType = c_oAscDerivativeType; window['AscCommonExcel'].c_oAscOperator = c_oAscOperator; window['AscCommonExcel'].c_oAscOptimizeTo = c_oAscOptimizeTo; window['AscCommonExcel'].c_oAscSolvingMethod = c_oAscSolvingMethod; window['AscCommonExcel'].c_oAscSolverResult = c_oAscSolverResult; window['AscCommonExcel'].c_oResultStatus = c_oResultStatus; window['AscCommonExcel'].asc_CSolverParams = asc_CSolverParams; window['AscCommonExcel'].asc_CSolverResults = asc_CSolverResults; })(window);