├── LICENSE ├── README.md ├── docs └── images │ ├── abm_disease.png │ ├── lake_volume.png │ ├── pop_growth.png │ ├── population_countries.png │ └── sir_disease.png ├── package.json ├── src ├── AggregateSeries.js ├── CalcMap.js ├── Constants.js ├── DNA.js ├── Functions.js ├── ModelNode.js ├── Modeler.js ├── Primitives.js ├── Simulator.js ├── TaskScheduler.js ├── Utilities.js ├── api │ ├── Blocks.js │ ├── Model.js │ ├── Results.js │ └── SimulationError.js ├── formula │ ├── CalcFunctions.js │ ├── Formula.js │ ├── Material.js │ ├── ModelError.js │ ├── Rand.js │ ├── SyntaxCheck.js │ ├── Units.js │ ├── Utilities.js │ ├── Vector.js │ └── grammar │ │ ├── FormulaLexer.js │ │ └── FormulaParser.js └── types.js ├── test ├── TestUtilities.js ├── config.json └── suites │ ├── agents.test.js │ ├── distributions.test.js │ ├── equations.test.js │ ├── folders.test.js │ ├── import.test.js │ ├── macros.test.js │ ├── misc.test.js │ ├── primitive_get_set.test.js │ ├── results_formatting.test.js │ ├── simulate_async.test.js │ ├── simulation.test.js │ ├── simulation_errors.test.js │ ├── simulation_get_set.test.js │ ├── subscripting.test.js │ ├── time_shift.test.js │ ├── types.test.js │ ├── unit_constraints.test.js │ └── unit_functions.test.js └── vendor ├── antlr4-all.js ├── avl ├── LICENSE ├── avl.js └── utils.js ├── bigjs ├── LICENSE └── big.js ├── graph.js ├── jstat ├── LICENSE └── jstat.js ├── random.js ├── toposort.js └── xmldom ├── LICENSE ├── conventions.js ├── dom-parser.js ├── dom.js ├── entities.js └── sax.js /docs/images/abm_disease.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottfr/simulation/bc51ea795ae3fda1885c1ab710cb99957ae51939/docs/images/abm_disease.png -------------------------------------------------------------------------------- /docs/images/lake_volume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottfr/simulation/bc51ea795ae3fda1885c1ab710cb99957ae51939/docs/images/lake_volume.png -------------------------------------------------------------------------------- /docs/images/pop_growth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottfr/simulation/bc51ea795ae3fda1885c1ab710cb99957ae51939/docs/images/pop_growth.png -------------------------------------------------------------------------------- /docs/images/population_countries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottfr/simulation/bc51ea795ae3fda1885c1ab710cb99957ae51939/docs/images/population_countries.png -------------------------------------------------------------------------------- /docs/images/sir_disease.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottfr/simulation/bc51ea795ae3fda1885c1ab710cb99957ae51939/docs/images/sir_disease.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simulation", 3 | "version": "6.0.0", 4 | "author": "Scott Fortmann-Roe", 5 | "license": "AGPL", 6 | "description": "simulation is a multi-method simulation package for physical or social systems. Use it to create models for the environment, business, or other domains.", 7 | "keywords": [ 8 | "simulation", 9 | "modeling", 10 | "differential equations", 11 | "system dynamics", 12 | "agent based modeling", 13 | "ABM" 14 | ], 15 | "homepage": "https://github.com/scottfr/simulation", 16 | "type": "module", 17 | "main": "src/api/Model.js", 18 | "exports": { 19 | ".": "./src/api/Model.js" 20 | }, 21 | "scripts": { 22 | "test": "NODE_OPTIONS=--experimental-vm-modules jest --config=test/config.json --watch" 23 | } 24 | } -------------------------------------------------------------------------------- /src/AggregateSeries.js: -------------------------------------------------------------------------------- 1 | import { div, evaluateNode, minus, toNum } from "./formula/Formula.js"; 2 | import { Material } from "./formula/Material.js"; 3 | 4 | 5 | export class AggregateSeries { 6 | /** 7 | * @param {import("./Simulator").Simulator} simulate 8 | * @param {Material} mSpacing 9 | */ 10 | constructor(simulate, mSpacing) { 11 | this.simulate = simulate; 12 | 13 | /** @type {Material} */ 14 | this.spacing = mSpacing; 15 | /** @type {Material[]} */ 16 | this.oldValues = []; 17 | } 18 | 19 | 20 | get(data) { 21 | let index = 0; 22 | 23 | if (this.spacing === null) { 24 | index = 0; 25 | } else if (this.spacing.value < 0) { 26 | // we treat negative spacing as the same as no defined spacing 27 | index = 0; 28 | } else if (this.spacing.value === 0) { 29 | index = Math.floor(div(minus(this.simulate.time(), this.simulate.timeStart), this.simulate.userTimeStep).value); 30 | } else { 31 | index = Math.floor(div(minus(this.simulate.time(), this.simulate.timeStart), this.spacing.forceUnits(this.simulate.timeUnits)).value); 32 | } 33 | 34 | while (this.oldValues.length - 1 < index) { 35 | let value = evaluateNode(data.node, data.scope, this.simulate); 36 | 37 | // ensure things like primitives or functions are converted to values 38 | this.oldValues.push(toNum(value)); 39 | } 40 | 41 | if (this.oldValues[index].fullClone) { 42 | return this.oldValues[index].fullClone(); 43 | } else { 44 | return this.oldValues[index]; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/CalcMap.js: -------------------------------------------------------------------------------- 1 | 2 | export let fn = { 3 | /** 4 | * @param {number} a 5 | * @param {number} b 6 | * @returns 7 | */ 8 | "+": function (a, b) { 9 | return a + b; 10 | }, 11 | 12 | /** 13 | * @param {number} a 14 | * @param {number=} b 15 | * @returns 16 | */ 17 | "-": function (a, b) { 18 | if (b !== undefined) { 19 | return a - b; 20 | } else { 21 | return -a; 22 | } 23 | }, 24 | 25 | /** 26 | * @param {number} a 27 | * @param {number} b 28 | * @returns 29 | */ 30 | "*": function (a, b) { 31 | return a * b; 32 | }, 33 | 34 | /** 35 | * @param {number} a 36 | * @param {number} b 37 | * @returns 38 | */ 39 | "/": function (a, b) { 40 | return a / b; 41 | }, 42 | 43 | "=": function (a, b) { 44 | return a === b; 45 | }, 46 | 47 | /** 48 | * @param {number} a 49 | * @param {number} b 50 | * @returns 51 | */ 52 | "<": function (a, b) { 53 | return a < b; 54 | }, 55 | 56 | /** 57 | * @param {number} a 58 | * @param {number} b 59 | * @returns 60 | */ 61 | "<=": function (a, b) { 62 | return a <= b; 63 | }, 64 | 65 | /** 66 | * @param {number} a 67 | * @param {number} b 68 | * @returns 69 | */ 70 | ">": function (a, b) { 71 | return a > b; 72 | }, 73 | 74 | /** 75 | * @param {number} a 76 | * @param {number} b 77 | * @returns 78 | */ 79 | ">=": function (a, b) { 80 | return a >= b; 81 | }, 82 | 83 | /** 84 | * @param {number} a 85 | * @param {number} b 86 | * @returns 87 | */ 88 | "mod": function (a, b) { 89 | return a % b; 90 | }, 91 | 92 | /** 93 | * @param {number} a 94 | * @param {number} b 95 | * @returns 96 | */ 97 | "expt": function (a, b) { 98 | return Math.pow(a, b); 99 | }, 100 | "abs": Math.abs, 101 | "sin": Math.sin, 102 | "asin": Math.asin, 103 | "cos": Math.cos, 104 | "acos": Math.acos, 105 | "tan": Math.tan, 106 | "atan": Math.atan, 107 | "sqrt": Math.sqrt, 108 | 109 | /** 110 | * @param {number} a 111 | * @param {number=} b 112 | * @returns 113 | */ 114 | "log": function (a, b) { 115 | if (b !== undefined) { 116 | return Math.log(a) / Math.log(b); 117 | } else { 118 | return Math.log(a); 119 | } 120 | }, 121 | "exp": Math.exp, 122 | "round": Math.round, 123 | "floor": Math.floor, 124 | "ceiling": Math.ceil 125 | }; 126 | -------------------------------------------------------------------------------- /src/Constants.js: -------------------------------------------------------------------------------- 1 | import { DOMImplementation } from "../vendor/xmldom/dom.js"; 2 | 3 | /** @type {Object} */ 4 | export const nodeBase = Object.create(null); 5 | export const defaultSolver = JSON.stringify({ 6 | enabled: false, 7 | algorithm: "RK1", 8 | timeStep: 1 9 | }); 10 | 11 | // If we're in a window use the standard doc implementation, otherwise (e.g. node), use xmldom 12 | let DI = typeof window === "undefined" ? new DOMImplementation() : document.implementation; 13 | 14 | let doc = DI.createDocument("", "", null); 15 | 16 | nodeBase.text = doc.createElement("Text"); 17 | nodeBase.text.setAttribute("name", "Text Area"); 18 | nodeBase.text.setAttribute("LabelPosition", "Middle"); 19 | 20 | nodeBase.folder = doc.createElement("Folder"); 21 | nodeBase.folder.setAttribute("name", "New Folder"); 22 | nodeBase.folder.setAttribute("Note", ""); 23 | nodeBase.folder.setAttribute("Type", "None"); 24 | nodeBase.folder.setAttribute("Solver", defaultSolver); 25 | nodeBase.folder.setAttribute("Image", "None"); 26 | nodeBase.folder.setAttribute("FlipHorizontal", "false"); 27 | nodeBase.folder.setAttribute("FlipVertical", "false"); 28 | nodeBase.folder.setAttribute("LabelPosition", "Middle"); 29 | nodeBase.folder.setAttribute("AgentBase", ""); 30 | 31 | nodeBase.ghost = doc.createElement("Ghost"); 32 | nodeBase.ghost.setAttribute("Source", ""); 33 | 34 | nodeBase.picture = doc.createElement("Picture"); 35 | nodeBase.picture.setAttribute("name", ""); 36 | nodeBase.picture.setAttribute("Note", ""); 37 | nodeBase.picture.setAttribute("Image", "Growth"); 38 | nodeBase.picture.setAttribute("FlipHorizontal", "false"); 39 | nodeBase.picture.setAttribute("FlipVertical", "false"); 40 | nodeBase.picture.setAttribute("LabelPosition", "Bottom"); 41 | 42 | nodeBase.display = doc.createElement("Display"); 43 | nodeBase.display.setAttribute("name", "Default Display"); 44 | nodeBase.display.setAttribute("Note", ""); 45 | nodeBase.display.setAttribute("Type", "Time Series"); 46 | nodeBase.display.setAttribute("xAxis", "Time (%u)"); 47 | nodeBase.display.setAttribute("yAxis", ""); 48 | nodeBase.display.setAttribute("yAxis2", ""); 49 | nodeBase.display.setAttribute("showMarkers", "false"); 50 | nodeBase.display.setAttribute("showLines", "true"); 51 | nodeBase.display.setAttribute("showArea", "false"); 52 | nodeBase.display.setAttribute("Primitives", ""); 53 | nodeBase.display.setAttribute("Primitives2", ""); 54 | nodeBase.display.setAttribute("AutoAddPrimitives", "false"); 55 | nodeBase.display.setAttribute("ScatterplotOrder", "X Primitive, Y Primitive"); 56 | nodeBase.display.setAttribute("Image", "Display"); 57 | nodeBase.display.setAttribute("FlipHorizontal", "false"); 58 | nodeBase.display.setAttribute("FlipVertical", "false"); 59 | nodeBase.display.setAttribute("LabelPosition", "Bottom"); 60 | 61 | function setValuedProperties(cell) { 62 | cell.setAttribute("Units", "Unitless"); 63 | cell.setAttribute("MaxConstraintUsed", "false"); 64 | cell.setAttribute("MinConstraintUsed", "false"); 65 | cell.setAttribute("MaxConstraint", "100"); 66 | cell.setAttribute("MinConstraint", "0"); 67 | cell.setAttribute("ShowSlider", "false"); 68 | cell.setAttribute("SliderMax", 100); 69 | cell.setAttribute("SliderMin", 0); 70 | cell.setAttribute("SliderStep", ""); 71 | } 72 | 73 | nodeBase.stock = doc.createElement("Stock"); 74 | nodeBase.stock.setAttribute("name", "New Stock"); 75 | nodeBase.stock.setAttribute("Note", ""); 76 | nodeBase.stock.setAttribute("InitialValue", "0"); 77 | nodeBase.stock.setAttribute("StockMode", "Store"); 78 | nodeBase.stock.setAttribute("Delay", "10"); 79 | nodeBase.stock.setAttribute("Volume", "100"); 80 | nodeBase.stock.setAttribute("NonNegative", "false"); 81 | setValuedProperties(nodeBase.stock); 82 | nodeBase.stock.setAttribute("Image", "None"); 83 | nodeBase.stock.setAttribute("FlipHorizontal", "false"); 84 | nodeBase.stock.setAttribute("FlipVertical", "false"); 85 | nodeBase.stock.setAttribute("LabelPosition", "Middle"); 86 | 87 | nodeBase.state = doc.createElement("State"); 88 | nodeBase.state.setAttribute("name", "New State"); 89 | nodeBase.state.setAttribute("Note", ""); 90 | nodeBase.state.setAttribute("Active", "false"); 91 | nodeBase.state.setAttribute("Residency", "0"); 92 | nodeBase.state.setAttribute("Image", "None"); 93 | nodeBase.state.setAttribute("FlipHorizontal", "false"); 94 | nodeBase.state.setAttribute("FlipVertical", "false"); 95 | nodeBase.state.setAttribute("LabelPosition", "Middle"); 96 | 97 | nodeBase.transition = doc.createElement("Transition"); 98 | nodeBase.transition.setAttribute("name", "Transition"); 99 | nodeBase.transition.setAttribute("Note", ""); 100 | nodeBase.transition.setAttribute("Trigger", "Timeout"); 101 | nodeBase.transition.setAttribute("Value", "1"); 102 | nodeBase.transition.setAttribute("Repeat", "false"); 103 | nodeBase.transition.setAttribute("Recalculate", "false"); 104 | setValuedProperties(nodeBase.transition); 105 | 106 | nodeBase.action = doc.createElement("Action"); 107 | nodeBase.action.setAttribute("name", "New Action"); 108 | nodeBase.action.setAttribute("Note", ""); 109 | nodeBase.action.setAttribute("Trigger", "Probability"); 110 | nodeBase.action.setAttribute("Value", "0.5"); 111 | nodeBase.action.setAttribute("Repeat", "true"); 112 | nodeBase.action.setAttribute("Recalculate", "false"); 113 | nodeBase.action.setAttribute("Action", "Self.Move({Rand(), Rand()})"); 114 | 115 | nodeBase.agents = doc.createElement("Agents"); 116 | nodeBase.agents.setAttribute("name", "New Agent Population"); 117 | nodeBase.agents.setAttribute("Note", ""); 118 | nodeBase.agents.setAttribute("Size", "100"); 119 | nodeBase.agents.setAttribute("GeoWrap", "false"); 120 | nodeBase.agents.setAttribute("GeoDimUnits", "Unitless"); 121 | nodeBase.agents.setAttribute("GeoWidth", "200"); 122 | nodeBase.agents.setAttribute("GeoHeight", "100"); 123 | nodeBase.agents.setAttribute("Placement", "Random"); 124 | nodeBase.agents.setAttribute("PlacementFunction", "{Rand()*Width(Self), Rand()*Height(Self)}"); 125 | nodeBase.agents.setAttribute("Network", "None"); 126 | nodeBase.agents.setAttribute("NetworkFunction", "RandBoolean(0.02)"); 127 | nodeBase.agents.setAttribute("Agent", ""); 128 | nodeBase.agents.setAttribute("Image", "None"); 129 | nodeBase.agents.setAttribute("FlipHorizontal", "false"); 130 | nodeBase.agents.setAttribute("FlipVertical", "false"); 131 | nodeBase.agents.setAttribute("LabelPosition", "Middle"); 132 | nodeBase.agents.setAttribute("ShowSlider", "false"); 133 | nodeBase.agents.setAttribute("SliderMax", "100"); 134 | nodeBase.agents.setAttribute("SliderMin", "0"); 135 | nodeBase.agents.setAttribute("SliderStep", "1"); 136 | 137 | nodeBase.variable = doc.createElement("Variable"); 138 | nodeBase.variable.setAttribute("name", "New Variable"); 139 | nodeBase.variable.setAttribute("Note", ""); 140 | nodeBase.variable.setAttribute("Equation", "0"); 141 | setValuedProperties(nodeBase.variable); 142 | nodeBase.variable.setAttribute("Image", "None"); 143 | nodeBase.variable.setAttribute("FlipHorizontal", "false"); 144 | nodeBase.variable.setAttribute("FlipVertical", "false"); 145 | nodeBase.variable.setAttribute("LabelPosition", "Middle"); 146 | 147 | nodeBase.button = doc.createElement("Button"); 148 | nodeBase.button.setAttribute("name", "New Button"); 149 | nodeBase.button.setAttribute("Note", ""); 150 | nodeBase.button.setAttribute("Function", "showMessage(\"Button action triggered!\\n\\nIf you want to edit this Action, click on the button while holding down the Shift key on your keyboard.\")"); 151 | nodeBase.button.setAttribute("Image", "None"); 152 | nodeBase.button.setAttribute("FlipHorizontal", "false"); 153 | nodeBase.button.setAttribute("FlipVertical", "false"); 154 | nodeBase.button.setAttribute("LabelPosition", "Middle"); 155 | 156 | nodeBase.converter = doc.createElement("Converter"); 157 | nodeBase.converter.setAttribute("name", "New Converter"); 158 | nodeBase.converter.setAttribute("Note", ""); 159 | nodeBase.converter.setAttribute("Source", "Time"); 160 | nodeBase.converter.setAttribute("Data", "0,0; 1,1; 2,4; 3,9"); 161 | nodeBase.converter.setAttribute("Interpolation", "Linear"); 162 | setValuedProperties(nodeBase.converter); 163 | nodeBase.converter.setAttribute("Image", "None"); 164 | nodeBase.converter.setAttribute("FlipHorizontal", "false"); 165 | nodeBase.converter.setAttribute("FlipVertical", "false"); 166 | nodeBase.converter.setAttribute("LabelPosition", "Middle"); 167 | 168 | nodeBase.flow = doc.createElement("Flow"); 169 | nodeBase.flow.setAttribute("name", "Flow"); 170 | nodeBase.flow.setAttribute("Note", ""); 171 | nodeBase.flow.setAttribute("FlowRate", "0"); 172 | nodeBase.flow.setAttribute("OnlyPositive", "true"); 173 | nodeBase.flow.setAttribute("TimeIndependent", "false"); 174 | setValuedProperties(nodeBase.flow); 175 | 176 | nodeBase.link = doc.createElement("Link"); 177 | nodeBase.link.setAttribute("name", "Link"); 178 | nodeBase.link.setAttribute("Note", ""); 179 | nodeBase.link.setAttribute("BiDirectional", "false"); 180 | 181 | nodeBase.setting = doc.createElement("Setting"); 182 | nodeBase.setting.setAttribute("Note", ""); 183 | nodeBase.setting.setAttribute("Version", "37"); 184 | nodeBase.setting.setAttribute("Throttle", "1"); 185 | nodeBase.setting.setAttribute("TimeLength", "100"); 186 | nodeBase.setting.setAttribute("TimeStart", "0"); 187 | nodeBase.setting.setAttribute("TimeStep", "1"); 188 | nodeBase.setting.setAttribute("TimeUnits", "Years"); 189 | nodeBase.setting.setAttribute("Units", ""); 190 | nodeBase.setting.setAttribute("SolutionAlgorithm", "RK1"); 191 | nodeBase.setting.setAttribute("BackgroundColor", "white"); 192 | nodeBase.setting.setAttribute("Macros", ""); 193 | nodeBase.setting.setAttribute("SensitivityPrimitives", ""); 194 | nodeBase.setting.setAttribute("SensitivityRuns", "50"); 195 | nodeBase.setting.setAttribute("SensitivityBounds", "50, 80, 95, 100"); 196 | nodeBase.setting.setAttribute("SensitivityShowRuns", "false"); 197 | nodeBase.setting.setAttribute("StyleSheet", "{}"); 198 | -------------------------------------------------------------------------------- /src/DNA.js: -------------------------------------------------------------------------------- 1 | export class DNA { 2 | /** 3 | * @param {import("./api/Blocks").Primitive} primitive 4 | * @param {string=} id 5 | */ 6 | constructor(primitive, id) { 7 | this.primitive = primitive; 8 | 9 | /** @type {string} */ 10 | this.id = id || primitive.id; 11 | 12 | /** @type {string} */ 13 | this.name = primitive.name; 14 | 15 | /** @type {import('./formula/Units').UnitStore} */ 16 | this.units = null; 17 | 18 | /** @type {import('./Simulator').SolverType} */ 19 | this.solver = undefined; 20 | 21 | /** @type {boolean} */ 22 | this.frozen = undefined; 23 | 24 | /** @type {boolean} */ 25 | this.slider = undefined; 26 | 27 | /** @type {string} */ 28 | this.targetId = undefined; 29 | 30 | /** @type {string} */ 31 | this.sourceId = undefined; 32 | 33 | /** @type {any} */ 34 | this.value = undefined; 35 | 36 | /** @type {TriggerType} */ 37 | this.trigger = undefined; 38 | 39 | /** @type {boolean} */ 40 | this.repeat = undefined; 41 | 42 | /** @type {import("./formula/Material").Material} */ 43 | this.delay = undefined; 44 | 45 | /** @type {StockTypeType} */ 46 | this.stockType = undefined; 47 | 48 | /** @type {boolean} */ 49 | this.recalculate = undefined; 50 | 51 | /** @type {boolean} */ 52 | this.nonNegative = undefined; 53 | 54 | /** @type {import("./formula/Material").Material} */ 55 | this.residency = undefined; 56 | 57 | /** @type {string} */ 58 | this.source = undefined; 59 | 60 | /** @type {string} */ 61 | this.interpolation = undefined; 62 | 63 | /** @type {any} */ 64 | this.triggerValue = undefined; 65 | 66 | /** @type {import("./formula/Material").Material[]} */ 67 | this.inputs = undefined; 68 | 69 | /** @type {import("./formula/Material").Material[]} */ 70 | this.outputs = undefined; 71 | 72 | /** @type {boolean} */ 73 | this.flowUnitless = undefined; 74 | 75 | /** @type {boolean} */ 76 | this.useMaxConstraint = undefined; 77 | 78 | /** @type {boolean} */ 79 | this.useMinConstraint = undefined; 80 | 81 | /** @type {any} */ 82 | this.minConstraint = undefined; 83 | 84 | /** @type {number} */ 85 | this.maxConstraint = undefined; 86 | 87 | /** @type {number} */ 88 | this.toBase = undefined; 89 | 90 | /** @type {boolean} */ 91 | this.unitless = undefined; 92 | 93 | /** @type {any} */ 94 | this.equation = undefined; 95 | 96 | /** @type {import('./Primitives').SPopulation} */ 97 | this.agents = undefined; 98 | 99 | // Don't display in any outputs 100 | /** @type {boolean} */ 101 | this.noOutput = false; 102 | 103 | /** @type {DNA} */ 104 | this.neighborProxyDNA = null; 105 | 106 | /** @type {import("./api/Blocks").Primitive[]} */ 107 | this.extraLinksPrimitives = []; 108 | 109 | // Adopt material units on first calculation 110 | /** @type {boolean} */ 111 | this.adoptUnits = false; 112 | } 113 | } -------------------------------------------------------------------------------- /src/ModelNode.js: -------------------------------------------------------------------------------- 1 | import { DOMParser } from "../vendor/xmldom/dom-parser.js"; 2 | // eslint-disable-next-line 3 | import { Stock, Variable, State, Action, Population, Transition, Flow, Link, Folder, Agent, Converter, Primitive } from "./api/Blocks.js"; 4 | 5 | 6 | 7 | export class ModelNode { 8 | constructor() { 9 | this.attributes = new Map(); 10 | 11 | /** @type {ModelNode} */ 12 | this.parent = null; 13 | 14 | /** @type {ModelNode[]} */ 15 | this.children = []; 16 | 17 | /** @type {string} */ 18 | this.id = null; 19 | 20 | /** @type {{ nodeName: string }} */ 21 | this.value = { nodeName: null }; 22 | 23 | /** @type {Primitive} */ 24 | this._primitive = undefined; 25 | 26 | /** @type {ModelNode} */ 27 | this.source = null; 28 | 29 | /** @type {ModelNode} */ 30 | this.target = null; 31 | } 32 | 33 | /** 34 | * @param {import("./api/Model").Model} model 35 | * @param {*} config 36 | * @returns {Primitive} 37 | */ 38 | primitive(model, config = {}) { 39 | if (this._primitive === undefined) { 40 | if (this.value.nodeName === "Stock") { 41 | this._primitive = new Stock(this, config); 42 | } else if (this.value.nodeName === "Variable") { 43 | this._primitive = new Variable(this, config); 44 | } else if (this.value.nodeName === "Converter") { 45 | this._primitive = new Converter(this, config); 46 | } else if (this.value.nodeName === "State") { 47 | this._primitive = new State(this, config); 48 | } else if (this.value.nodeName === "Action") { 49 | this._primitive = new Action(this, config); 50 | } else if (this.value.nodeName === "Agents") { 51 | this._primitive = new Population(this, config); 52 | } else if (this.value.nodeName === "Flow") { 53 | this._primitive = new Flow(this, config); 54 | } else if (this.value.nodeName === "Transition") { 55 | this._primitive = new Transition(this, config); 56 | } else if (this.value.nodeName === "Link") { 57 | this._primitive = new Link(this, config); 58 | } else if (this.value.nodeName === "Folder") { 59 | if (this.getAttribute("Type") === "Agent") { 60 | this._primitive = new Agent(this, config); 61 | } else { 62 | this._primitive = new Folder(this, config); 63 | } 64 | } else { 65 | this._primitive = null; 66 | } 67 | if (this._primitive && !this._primitive.model) { 68 | this._primitive.model = model; 69 | } 70 | } 71 | 72 | return this._primitive; 73 | } 74 | 75 | 76 | /** 77 | * @param {ModelNode} newChild 78 | */ 79 | addChild(newChild) { 80 | if (newChild.parent) { 81 | // remove from prior parent if there is one 82 | let index = newChild.parent.children.indexOf(newChild); 83 | if (index > -1) { 84 | newChild.parent.children.splice(index, 1); 85 | } 86 | } 87 | 88 | this.children.push(newChild); 89 | newChild.parent = this; 90 | } 91 | 92 | /** 93 | * @param {string} x 94 | * @returns {string} 95 | */ 96 | getAttribute(x) { 97 | if (x === "id") { 98 | return this.id; 99 | } 100 | return this.attributes.get(x); 101 | } 102 | 103 | setAttribute(x, value) { 104 | if (x === "id") { 105 | this.id = "" + value; 106 | return; 107 | } 108 | this.attributes.set(x, "" + value); 109 | } 110 | 111 | getValue() { 112 | return { 113 | removeAttribute: (name) => this.attributes.delete(name) 114 | }; 115 | } 116 | 117 | toString(indent = 0) { 118 | let start = " ".repeat(indent) + `<${this.value.nodeName}${this.getAttribute("name") ? " [" + this.getAttribute("name") + "]" : ""}>`; 119 | let end = ``; 120 | 121 | return start + (this.children.length ? "\n" : "") + this.children.map(child => " ".repeat(indent) + child.toString(indent + 2)).join("\n") + (this.children.length ? ("\n" + " ".repeat(indent)) : "") + end; 122 | } 123 | } 124 | 125 | 126 | 127 | 128 | /** 129 | * @param {string} modelString 130 | * 131 | * @returns 132 | */ 133 | export function loadXML(modelString) { 134 | let oParser = new DOMParser(); 135 | let data = oParser.parseFromString(modelString, "text/xml"); 136 | let graph = graphXMLToNodes(data); 137 | 138 | 139 | graph.children[0].value = { nodeName: "root" }; 140 | graph.children[0].id = "1"; 141 | 142 | let connectors = primitives(/** @type {any} */(graph), ["Flow", "Link", "Transition"]); 143 | 144 | 145 | let items = primitives(graph); 146 | connectors.forEach((x) => { 147 | x.source = null; 148 | x.target = null; 149 | items.forEach((i) => { 150 | if (x.children[0].getAttribute("source") && x.children[0].getAttribute("source") === i.id) { 151 | x.source = i; 152 | } 153 | if (x.children[0].getAttribute("target") && x.children[0].getAttribute("target") === i.id) { 154 | x.target = i; 155 | } 156 | }); 157 | }); 158 | 159 | function cleanNode(x) { 160 | if (x.children) { 161 | let nodes = x.children.filter((c) => c.value.nodeName === "mxCell"); 162 | 163 | if (nodes.length > 0) { 164 | if (nodes[0].getAttribute("parent")) { 165 | let parent = items.find(item => item.id === nodes[0].getAttribute("parent")); 166 | if (parent && parent.value.nodeName === "Folder") { 167 | parent.addChild(x); 168 | } 169 | } 170 | } 171 | 172 | x.children = x.children.filter((c) => c.value.nodeName !== "mxCell"); 173 | 174 | for (let i = x.children.length - 1; i >= 0; i--) { 175 | cleanNode(x.children[i]); 176 | } 177 | } 178 | } 179 | 180 | cleanNode(graph); 181 | 182 | 183 | return graph; 184 | } 185 | 186 | 187 | function findRootParent(node) { 188 | if (node.parent) { 189 | return findRootParent(node.parent); 190 | } 191 | return node; 192 | } 193 | 194 | export function modelNodeClone(node, parent) { 195 | let obj = new ModelNode(); 196 | obj.value = node.cloneNode(true); 197 | obj.parent = parent; 198 | 199 | let currIds = ["1"].concat(primitives(findRootParent(parent)).map(x => x.id).filter(x => !!x)); 200 | 201 | if (node.attributes.length > 0) { 202 | for (let j = 0; j < node.attributes.length; j++) { 203 | let attribute = node.attributes.item(j); 204 | obj.setAttribute(attribute.nodeName, attribute.nodeValue); 205 | } 206 | } 207 | 208 | obj.setAttribute("id", "" + (Math.max.apply(null, currIds) + 1)); 209 | 210 | return obj; 211 | } 212 | 213 | 214 | function graphXMLToNodes(xml, parent) { 215 | // Create the return object 216 | let obj = new ModelNode(); 217 | obj.value = xml; 218 | obj.parent = parent; 219 | 220 | if (xml.nodeType === 1) { // element 221 | // do attributes 222 | if (xml.attributes.length > 0) { 223 | for (let j = 0; j < xml.attributes.length; j++) { 224 | let attribute = xml.attributes.item(j); 225 | obj.attributes.set(attribute.nodeName, attribute.nodeValue); 226 | } 227 | 228 | obj.id = obj.attributes.get("id"); 229 | } 230 | } else if (xml.nodeType === 3) { // text 231 | return null; 232 | } 233 | 234 | if (xml.hasChildNodes()) { 235 | obj.children = []; 236 | for (let i = 0; i < xml.childNodes.length; i++) { 237 | let item = xml.childNodes.item(i); 238 | let x = graphXMLToNodes(item, obj); 239 | if (x) { 240 | obj.addChild(x); 241 | } 242 | } 243 | } 244 | 245 | return obj; 246 | } 247 | 248 | 249 | /** 250 | * @param {ModelNode} root 251 | * @param {PrimitiveNameType|PrimitiveNameType[]=} type 252 | * 253 | * @returns {GraphNode[]} 254 | */ 255 | export function primitives(root, type) { 256 | let myNodes = nodeChildren(root.children[0]); 257 | 258 | if (!type) { 259 | return myNodes; 260 | } else { 261 | let targetNodes = []; 262 | 263 | for (let node of myNodes) { 264 | if (Array.isArray(type) ? type.includes(node.value.nodeName) : node.value.nodeName === type) { 265 | targetNodes.push(node); 266 | } 267 | } 268 | 269 | return targetNodes; 270 | } 271 | } 272 | 273 | 274 | function nodeChildren(node) { 275 | let children = node.children.slice(); 276 | 277 | let childrenLength = children.length; 278 | for (let i = 0; i < childrenLength; i++) { 279 | let child = children[i]; 280 | children = children.concat(nodeChildren(child)); 281 | } 282 | 283 | return children; 284 | } 285 | -------------------------------------------------------------------------------- /src/TaskScheduler.js: -------------------------------------------------------------------------------- 1 | import { mult, greaterThan, minus, eq, neq, lessThan } from "./formula/Formula.js"; 2 | import { Material } from "./formula/Material.js"; 3 | import Tree from "../vendor/avl/avl.js"; 4 | 5 | 6 | export class TaskQueue { 7 | constructor(config) { 8 | config = config || {}; 9 | this.tasks = new Tree(compare); 10 | /** @type {(function(Material, Material, Material): void)[]} */ 11 | this.onMoveEvents = []; 12 | this.setTime(config.start); 13 | this.debug = false; 14 | 15 | /** 16 | * @type {Material} 17 | */ 18 | this.end = config.end; 19 | 20 | /** 21 | * @type {Object} 22 | */ 23 | this.states = {}; 24 | 25 | /** @type {import("../vendor/avl/avl").Node} */ 26 | this.cursor = null; 27 | } 28 | 29 | print() { 30 | console.log("Current Time: " + this.time.value); 31 | let cursor = this.tasks.minNode(); 32 | while (cursor) { 33 | console.log(cursor.key.name); 34 | console.log(" Time: " + cursor.key.time.value); 35 | console.log(" Priority: " + cursor.key.priority); 36 | if (cursor.key.expires !== undefined) { 37 | console.log(" Expires: " + cursor.key.expires); 38 | } 39 | if (cursor.key.skip !== undefined) { 40 | console.log(" Skip: " + cursor.key.skip); 41 | } 42 | cursor = this.tasks.next(cursor); 43 | } 44 | } 45 | 46 | /** 47 | * @param {(function(Material, Material, Material): void)} event 48 | */ 49 | addEvent(event) { 50 | this.onMoveEvents.push(event); 51 | } 52 | 53 | /** 54 | * @param {Material} timeChange 55 | * @param {Material} oldTime 56 | * @param {Material} newTime 57 | */ 58 | fireEvents(timeChange, oldTime, newTime) { 59 | if (this.debug) { 60 | console.log("Firing Events"); 61 | } 62 | for (let i = 0; i < this.onMoveEvents.length; i++) { 63 | this.onMoveEvents[i](timeChange, oldTime, newTime); 64 | } 65 | } 66 | 67 | /** 68 | * @param {Material} t 69 | */ 70 | setTime(t) { 71 | if (this.time === undefined || neq(t, this.time)) { 72 | let oldTime = this.time; 73 | 74 | this.time = t; 75 | 76 | if (oldTime !== undefined) { 77 | this.fireEvents(minus(t, oldTime), oldTime, t); 78 | } 79 | 80 | } 81 | } 82 | 83 | /** 84 | * @param {Material} newTime 85 | */ 86 | moveTo(newTime) { 87 | if (eq(this.time, newTime)) { 88 | return; 89 | } else { 90 | if (this.debug) { 91 | console.log("Shifting time to: " + newTime.value); 92 | } 93 | 94 | if (this.cursor) { // we have something defined 95 | let maxTime = this.tasks.max().time; 96 | let minTime = this.tasks.min().time; 97 | 98 | while (lessThan(this.time, newTime) && (!greaterThan(this.time, maxTime))) { 99 | this.step(); 100 | } 101 | while (greaterThan(this.time, newTime) && greaterThan(this.time, minTime)) { 102 | this.stepBack(); 103 | } 104 | } 105 | 106 | this.setTime(newTime); 107 | 108 | if (this.debug) { 109 | console.log("Time shift to " + newTime.value + " completed."); 110 | } 111 | } 112 | } 113 | 114 | /** 115 | * @param {Task} task 116 | */ 117 | add(task) { 118 | task.queue = this; 119 | 120 | this.tasks.insert(task); 121 | } 122 | 123 | goNext() { 124 | this.cursor = this.tasks.next(this.cursor); 125 | } 126 | 127 | goPrev() { 128 | this.cursor = this.tasks.prev(this.cursor); 129 | } 130 | 131 | step() { 132 | if (this.time === undefined) { 133 | this.cursor = this.tasks.minNode(); 134 | this.setTime(this.cursor.key.time); 135 | } 136 | 137 | 138 | let current = this.cursor.key; 139 | if (current) { 140 | let dead = current.deadAction; 141 | current.execute(); 142 | 143 | if ((!dead) && current.timeShift) { 144 | current.timeShift(); 145 | return; 146 | } 147 | this.goNext(); 148 | } 149 | 150 | if (this.cursor) { 151 | this.setTime(this.cursor.key.time); 152 | } else { 153 | this.goNext(); 154 | this.setTime(mult(/** @type {Material} */(this.tasks.max().time), new Material(10))); 155 | } 156 | } 157 | 158 | stepBack() { 159 | if (this.time === undefined) { 160 | this.cursor = this.tasks.minNode(); 161 | this.setTime(this.cursor.key.time); 162 | return; 163 | } 164 | 165 | if (!this.cursor) { 166 | this.cursor = this.tasks.maxNode(); 167 | } else { 168 | this.goPrev(); 169 | } 170 | 171 | let t = this.cursor.key.time; 172 | while (this.cursor && eq(t, this.cursor.key.time)) { 173 | this.cursor.key.rollback(); 174 | this.goPrev(); 175 | } 176 | 177 | if (!this.cursor) { 178 | this.cursor = this.tasks.minNode(); 179 | } else { 180 | this.goNext(); 181 | } 182 | 183 | this.setTime(this.cursor.key.time); 184 | } 185 | 186 | /** 187 | * @returns {boolean} 188 | */ 189 | atStart() { 190 | return this.time === undefined || this.cursor.key === this.tasks.min(); 191 | } 192 | 193 | /** 194 | * @returns {boolean} 195 | */ 196 | completed() { 197 | return this.time !== undefined && (greaterThan(this.time, this.end) || (!this.cursor)); 198 | } 199 | 200 | /** 201 | * @param {Task} task 202 | */ 203 | remove(task) { 204 | if (task === this.cursor.key) { 205 | this.goNext(); 206 | } 207 | this.tasks.remove(task); 208 | } 209 | } 210 | 211 | 212 | let taskIdCounter = 0; 213 | 214 | // new Task({name: "solver", time: t, action: fn(), rollback: fn(), priority: -10, expires: 1}) 215 | export class Task { 216 | /** 217 | * @param {Object} config 218 | * @param {string} config.name 219 | * @param {Material} config.time 220 | * @param {(function(Task) : void) & {task?: Task, reverse?: Task}} config.action 221 | * @param {function=} config.rollback 222 | * @param {number=} config.priority 223 | * @param {number=} config.expires 224 | * @param {number=} config.skip 225 | * @param {function=} config.timeShift 226 | * @param {any=} config.data 227 | * @param {string=} config.blocker 228 | */ 229 | constructor(config) { 230 | /** @type {number} */ 231 | this.id = taskIdCounter++; // must be numeric as we use it in equality comparisons 232 | this.name = config.name; 233 | this.time = config.time; 234 | 235 | if (!this.time) { 236 | throw new TypeError("Task time is missing."); 237 | } 238 | 239 | if (isNaN(this.time.value)) { 240 | throw new TypeError("Task time is a NaN."); 241 | } 242 | 243 | this.action = config.action; 244 | this.reverse = config.rollback; 245 | this.priority = config.priority || 0; // Lower priorities will be run before higher priorities at the same time 246 | this.expires = config.expires; // if defined, the number of times this is called before it expires 247 | this.skip = config.skip; 248 | this.timeShift = config.timeShift; 249 | this.data = config.data; // optional data object to be carried along, the task scheduler makes no use of this 250 | this.blocker = config.blocker; 251 | this.queue = undefined; 252 | 253 | this.deadAction = false; // once dead no longer executes 254 | this.deadReverse = false; // once dead no longer executes 255 | 256 | if (this.action) { 257 | this.action.task = this; 258 | } 259 | if (this.reverse) { 260 | this.action.reverse = this; 261 | } 262 | 263 | } 264 | 265 | execute() { 266 | if (this.action && (!this.deadAction) && ((!this.blocker) || !this.queue.states[this.blocker])) { 267 | if (this.skip !== undefined && this.skip > 0) { 268 | this.skip--; 269 | if (this.queue.debug) { 270 | console.log("Skipping: " + this.name); 271 | } 272 | } else { 273 | if (this.queue.debug) { 274 | console.log("%c Executing: " + this.name + " (Time: " + this.time.value + ")", "color:blue"); 275 | } 276 | 277 | if (this.expires !== undefined) { 278 | this.expires--; 279 | if (this.queue.debug) { 280 | console.log(" Current count before expire: " + this.expires); 281 | } 282 | if (this.expires <= 0) { 283 | if (this.queue.debug) { 284 | console.log(" Task expired."); 285 | } 286 | this.deadAction = true; 287 | } 288 | } 289 | } 290 | 291 | 292 | this.action(this); 293 | } 294 | } 295 | 296 | rollback() { 297 | if (this.reverse && (!this.deadReverse) && ((!this.blocker) || !this.queue.states[this.blocker])) { 298 | if (this.queue.debug) { 299 | console.log("Rolling back: " + this.name + " (Time: " + this.time.value + ")"); 300 | } 301 | 302 | if (this.expires !== undefined) { 303 | if (this.expires <= 0) { 304 | if (this.queue.debug) { 305 | console.log(" Rollback expired."); 306 | } 307 | this.deadReverse = true; 308 | } 309 | } 310 | 311 | this.reverse(); 312 | } 313 | } 314 | 315 | /** 316 | * @param {Material} newTime 317 | */ 318 | reschedule(newTime) { 319 | this.queue.remove(this); 320 | this.time = newTime; 321 | this.queue.add(this); 322 | } 323 | 324 | remove() { 325 | this.queue.remove(this); 326 | } 327 | 328 | kill() { 329 | this.deadAction = true; 330 | this.deadReverse = true; 331 | } 332 | 333 | /** 334 | * @param {string=} id 335 | */ 336 | block(id) { 337 | id = id || this.blocker; 338 | this.queue.states[id] = true; 339 | } 340 | 341 | /** 342 | * @param {string=} id 343 | */ 344 | unblock(id) { 345 | id = id || this.blocker; 346 | this.queue.states[id] = false; 347 | } 348 | 349 | toString() { 350 | return this.name + " - " + this.id; 351 | } 352 | } 353 | 354 | 355 | 356 | /** 357 | * Sorts by: 358 | * - first by time 359 | * - then by priority 360 | * - then by creation order (id) 361 | * 362 | * @param {Task} a 363 | * @param {Task} b 364 | * 365 | * @returns {number} 366 | */ 367 | function compare(a, b) { 368 | if (eq(b.time, a.time)) { 369 | if (b.priority === a.priority) { 370 | if (b.id === a.id) { 371 | return 0; 372 | } else if (b.id < a.id) { 373 | return 1; 374 | } else { 375 | return -1; 376 | } 377 | } else if (b.priority < a.priority) { 378 | return 1; 379 | } else { 380 | return -1; 381 | } 382 | } else { 383 | if (lessThan(b.time, a.time)) { 384 | return 1; 385 | } else { 386 | return -1; 387 | } 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /src/Utilities.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export function isTrue(item) { 4 | return item === 1 || item === "true" || item === true; 5 | } 6 | 7 | 8 | /** 9 | * @param {string} str 10 | * @returns {string} 11 | */ 12 | export function toHTML(str) { 13 | return str.replace(/[&<>'"]/g, 14 | tag => ({ 15 | "&": "&", 16 | "<": "<", 17 | ">": ">", 18 | "'": "'", 19 | "\"": """, 20 | "`": "`" 21 | }[tag])); 22 | } 23 | 24 | 25 | /** 26 | * @param {any} nStr 27 | * @returns {string} 28 | */ 29 | export function commaStr(nStr) { 30 | if (typeof nStr === "string") { 31 | return escape(nStr); 32 | } 33 | 34 | if (typeof nStr === "boolean") { 35 | return nStr.toString(); 36 | } 37 | 38 | if (nStr === undefined || nStr === null) { 39 | return ""; 40 | } 41 | 42 | if (nStr >= 1e9 || (nStr <= 1e-9 && nStr !== 0)) { 43 | return nStr.toPrecision(3); 44 | } else { 45 | nStr = round(nStr, 9) + ""; 46 | let x = nStr.split("."); 47 | let x1 = x[0]; 48 | let x2 = x.length > 1 ? "." + x[1] : ""; 49 | let reg = /(\d+)(\d{3})/; 50 | while (reg.test(x1)) { 51 | x1 = x1.replace(reg, "$1,$2"); 52 | } 53 | return x1 + x2; 54 | } 55 | } 56 | 57 | 58 | /** 59 | * @param {number} value 60 | * @param {number=} precision 61 | */ 62 | export function round(value, precision = 15) { 63 | return +value.toPrecision(precision); 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/api/Results.js: -------------------------------------------------------------------------------- 1 | import { Vector } from "../formula/Vector.js"; 2 | 3 | 4 | export class Results { 5 | /** 6 | * @param {import("../Simulator").ResultsType} data 7 | * @param {Object} nameIdMapping 8 | */ 9 | constructor(data, nameIdMapping) { 10 | this._data = data; 11 | this._nameIdMapping = nameIdMapping; 12 | this.timeUnits = data.timeUnits; 13 | 14 | for (let i = 0; i < this._data.data.length; i++) { 15 | let current = this._data.data[i]; 16 | for (let key in current) { 17 | current[key] = simplifyResults(current[key]); 18 | } 19 | } 20 | } 21 | 22 | times() { 23 | return this._data.times.slice(); 24 | } 25 | 26 | /** 27 | * @param {import("./Blocks").Primitive[]=} primitives 28 | */ 29 | table(primitives) { 30 | if (primitives) { 31 | let series = primitives.map(x => ({ primitive: x, series: this.series(x) })); 32 | let times = this.times(); 33 | 34 | let res = []; 35 | for (let i = 0; i < times.length; i++) { 36 | let data = { 37 | _time: times[i], 38 | }; 39 | for (let item of series) { 40 | data[item.primitive.name] = item.series[i]; 41 | } 42 | res.push(data); 43 | } 44 | 45 | return res; 46 | } else { 47 | let res = []; 48 | for (let i = 0; i < this._data.data.length; i++) { 49 | let data = {}; 50 | data._time = this._data.times[i]; 51 | 52 | let current = this._data.data[i]; 53 | for (let id in current) { 54 | data[this._nameIdMapping[id]] = current[id]; 55 | } 56 | res.push(data); 57 | } 58 | return res; 59 | } 60 | } 61 | 62 | /** 63 | * @param {import("./Blocks").Primitive} primitive 64 | */ 65 | series(primitive) { 66 | return this._data.value(primitive._node).slice(); 67 | } 68 | 69 | /** 70 | * @param {import("./Blocks").Primitive} primitive 71 | * @param {number=} time - if omitted, the last available value 72 | */ 73 | value(primitive, time = null) { 74 | let series = this.series(primitive); 75 | if (time === null) { 76 | return series[series.length - 1]; 77 | } 78 | 79 | let times = this.times(); 80 | 81 | let index = times.indexOf(time); 82 | if (index === -1) { 83 | throw new Error("Could not find time: " + time + ". Available options are: " + JSON.stringify(times)); 84 | } 85 | 86 | return series[index]; 87 | } 88 | } 89 | 90 | 91 | function simplifyResults(x) { 92 | if (x instanceof Vector) { 93 | if (x.names) { 94 | let res = {}; 95 | for (let i = 0; i < x.names.length; i++) { 96 | res[x.names[i]] = simplifyResults(x.items[i]); 97 | } 98 | return res; 99 | } else { 100 | return x.items.map(simplifyResults); 101 | } 102 | } 103 | 104 | return x; 105 | } -------------------------------------------------------------------------------- /src/api/SimulationError.js: -------------------------------------------------------------------------------- 1 | export class SimulationError extends Error { 2 | /** 3 | * @param {string} message 4 | * @param {{ source?: string, primitive?: any, line?: number, code: number }} config 5 | */ 6 | constructor(message, config) { 7 | super(message); 8 | 9 | if (Error.captureStackTrace) { 10 | Error.captureStackTrace(this, SimulationError); 11 | } 12 | 13 | this.name = "SimulationError"; 14 | 15 | 16 | this.source = config.source; 17 | this.primitive = config.primitive; 18 | this.line = config.line; 19 | this.code = config.code; 20 | 21 | // Don't print these on the console 22 | Object.defineProperty(this, "code", { enumerable: false }); 23 | Object.defineProperty(this, "primitive", { enumerable: false }); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/formula/Material.js: -------------------------------------------------------------------------------- 1 | import { convertUnits, UnitStore } from "./Units.js"; 2 | import { fn } from "../CalcMap.js"; 3 | import { ModelError } from "./ModelError.js"; 4 | import { PrimitiveStore } from "./Formula.js"; 5 | 6 | 7 | export class Material { 8 | /** 9 | * @param {number} value 10 | * @param {import("./Units").UnitStore=} units 11 | * @param {boolean=} explicitUnits 12 | */ 13 | constructor(value, units, explicitUnits=true) { 14 | /** @type {number} */ 15 | this.value = value; 16 | /** @type {import("./Units").UnitStore} */ 17 | this.units = units; 18 | /** @type {boolean} */ 19 | this.explicitUnits = explicitUnits; 20 | } 21 | 22 | /** 23 | * @returns {Material} 24 | */ 25 | toNum() { 26 | return this; 27 | } 28 | 29 | toString() { 30 | if (this.units && !this.units.isUnitless()) { 31 | return "{" + this.value + " " + this.units.toStringShort() + "}"; 32 | } else { 33 | return this.value + ""; 34 | } 35 | } 36 | 37 | fullClone() { 38 | return new Material(this.value, this.units, this.explicitUnits); 39 | } 40 | 41 | /** 42 | * @param {import("./Units").UnitStore} newUnits 43 | * 44 | * @returns {Material} 45 | */ 46 | forceUnits(newUnits) { 47 | if (!this.units) { 48 | this.units = newUnits; 49 | } else { 50 | let scale = convertUnits(this.units, newUnits); 51 | if (scale === 0) { 52 | unitAlert(this.units, newUnits, "conversion of units"); 53 | } else { 54 | this.value = fn["*"](this.value, scale); 55 | this.units = newUnits; 56 | } 57 | } 58 | 59 | return this; 60 | } 61 | } 62 | 63 | 64 | /** @typedef {"MATERIAL"|"VECTOR"|"PRIMITIVE"|null} OperandType */ 65 | 66 | /** 67 | * @param {Material|UnitStore} lhs 68 | * @param {Material|UnitStore} rhs 69 | * @param {string} type 70 | * @param {string=} operator 71 | * @param {import("./Formula").TreeNode=} lhsNode 72 | * @param {import("./Formula").TreeNode=} rhsNode 73 | */ 74 | export function unitAlert(lhs, rhs, type, operator, lhsNode, rhsNode) { 75 | if (lhs instanceof UnitStore && rhs instanceof UnitStore) { 76 | throw new ModelError(`Incompatible units for the ${type} of ${((lhs && !lhs.isUnitless()) ? lhs.toString() : "unitless")} and ${((rhs && !rhs.isUnitless()) ? rhs.toString() : "unitless")}.`, { 77 | code: 5000 78 | }); 79 | } else if (lhs instanceof Material && rhs instanceof Material) { 80 | let lhsUnits = lhs.units; 81 | let rhsUnits = rhs.units; 82 | let msg = `Incompatible units for the ${type} of ${((lhsUnits && !lhsUnits.isUnitless()) ? lhsUnits.toString() : "unitless")} and ${((rhsUnits && !rhsUnits.isUnitless()) ? rhsUnits.toString() : "unitless")}.`; 83 | 84 | let formatMat = (mat) => { 85 | if (mat.units && !mat.units.isUnitless()) { 86 | return `{${mat.value} ${mat.units.toStringShort()}}`; 87 | } else { 88 | return mat.value; 89 | } 90 | }; 91 | 92 | msg += `

Attempted ${type}: ${formatMat(lhs)} ${operator} ${formatMat(rhs)}`; 93 | 94 | if (!lhsUnits) { 95 | if (lhsNode instanceof Material) { 96 | let lhsSuggested = lhs.fullClone(); 97 | lhsSuggested.units = rhsUnits; 98 | msg += `

Consider replacing ${formatMat(lhs)} with ${formatMat(lhsSuggested)}.`; 99 | } else if (lhsNode instanceof PrimitiveStore) { 100 | msg += "

Consider setting the units of [" + lhsNode.primitive.dna.name + "] to " + rhsUnits.toStringShort() + "."; 101 | } 102 | } 103 | 104 | if (!rhsUnits) { 105 | if (rhsNode instanceof Material) { 106 | let rhsSuggested = rhs.fullClone(); 107 | rhsSuggested.units = lhsUnits; 108 | msg += `

Consider replacing ${formatMat(rhs)} with ${formatMat(rhsSuggested)}.`; 109 | } else if (rhsNode instanceof PrimitiveStore) { 110 | msg += "

Consider setting the units of [" + rhsNode.primitive.dna.name + "] to " + lhsUnits.toStringShort() + "."; 111 | } 112 | } 113 | 114 | throw new ModelError(msg, { 115 | code: 5000 116 | }); 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /src/formula/ModelError.js: -------------------------------------------------------------------------------- 1 | export class ModelError extends Error { 2 | /** 3 | * @param {string} message 4 | * @param {{ source?: string, primitive?: import('../api/Blocks.js').Primitive|import('../Primitives').SPrimitive|GraphNode, showEditor?: boolean, line?: number, details?: string, code: number }} config 5 | */ 6 | constructor(message, config) { 7 | super(message); 8 | this.name = "ModelError"; 9 | this.primitive = config.primitive; 10 | this.showEditor = config.showEditor; 11 | this.source = config.source; 12 | this.line = config.line; 13 | this.details = config.details; 14 | this.code = config.code; 15 | } 16 | } -------------------------------------------------------------------------------- /src/formula/Rand.js: -------------------------------------------------------------------------------- 1 | import { ModelError } from "./ModelError.js"; 2 | import { jStat } from "../../vendor/jstat/jstat.js"; 3 | 4 | 5 | export class RandList { 6 | /** 7 | * @param {import("../Simulator").Simulator} simulate 8 | */ 9 | constructor(simulate) { 10 | this.simulate = simulate; 11 | 12 | /** @type {number[]} */ 13 | this.vals = []; 14 | } 15 | 16 | /** 17 | * @param {number} i 18 | * 19 | * @returns {number} 20 | */ 21 | get(i) { 22 | if (i > this.vals.length - 1) { 23 | for (let j = this.vals.length; j <= i; j++) { 24 | if (this.simulate.random) { 25 | this.vals.push(this.simulate.random()); 26 | } else { 27 | this.vals.push(Math.random()); 28 | } 29 | } 30 | } 31 | return this.vals[i]; 32 | } 33 | } 34 | 35 | 36 | /** 37 | * @param {import("../Simulator").Simulator} simulate 38 | * 39 | * @returns {number} 40 | */ 41 | function getRandPos(simulate) { 42 | return Math.floor((simulate.time().value - simulate.timeStart.value) / simulate.timeStep.value); 43 | } 44 | 45 | 46 | /** 47 | * @param {import("../Simulator").Simulator} simulate 48 | * @param {number=} minVal 49 | * @param {number=} maxVal 50 | * 51 | * @return {number} 52 | */ 53 | export function Rand(simulate, minVal = null, maxVal = null) { 54 | simulate.stochastic = true; 55 | 56 | if (minVal !== null && minVal !== undefined) { 57 | isNormalNumber(minVal, "Rand", "Minimum"); 58 | isNormalNumber(maxVal, "Rand", "Maximum"); 59 | 60 | return Rand(simulate) * (maxVal - minVal) + (0 + minVal); 61 | } 62 | if (simulate.RKOrder === 1) { 63 | if (simulate.random) { 64 | return simulate.random(); 65 | } else { 66 | return Math.random(); 67 | } 68 | } 69 | let RandPos = getRandPos(simulate); 70 | if (RandPos !== simulate.lastRandPos) { 71 | simulate.randLoc = -1; 72 | simulate.lastRandPos = RandPos; 73 | } 74 | while (simulate.previousRandLists.length <= RandPos) { 75 | simulate.previousRandLists.push(new RandList(simulate)); 76 | } 77 | simulate.randLoc = simulate.randLoc + 1; 78 | return simulate.previousRandLists[RandPos].get(simulate.randLoc); 79 | } 80 | 81 | 82 | /** 83 | * @param {import("../Simulator").Simulator} simulate 84 | * @param {number=} mu 85 | * @param {number=} sigma 86 | * 87 | * @returns {number} 88 | */ 89 | export function RandNormal(simulate, mu = null, sigma = null) { 90 | isNormalNumber(mu, "RandNormal", "mu"); 91 | isNormalNumber(sigma, "RandNormal", "sigma"); 92 | 93 | if (mu === null) { 94 | mu = 0; 95 | } 96 | if (sigma === null) { 97 | sigma = 1; 98 | } 99 | 100 | let z; 101 | z = Math.sqrt(-2 * Math.log(1 - Rand(simulate))) * Math.cos(Rand(simulate) * 2 * Math.PI); 102 | return z * sigma + (0 + mu); 103 | } 104 | 105 | 106 | /** 107 | * @param {import("../Simulator").Simulator} simulate 108 | * @param {number=} lambda 109 | * 110 | * @returns {number} 111 | */ 112 | export function RandExp(simulate, lambda = null) { 113 | if (lambda === null) { 114 | lambda = 1; 115 | } 116 | 117 | if (lambda < 0) { 118 | throw new ModelError(`Lambda for RandExp must be greater than or equal to 0; got ${lambda}.`, { 119 | code: 4000 120 | }); 121 | } 122 | 123 | isNormalNumber(lambda, "RandExp", "lambda"); 124 | 125 | return -Math.log(Rand(simulate)) / lambda; 126 | } 127 | 128 | 129 | /** 130 | * @param {import("../Simulator").Simulator} simulate 131 | * @param {number} mu 132 | * @param {number} sigma 133 | * 134 | * @returns {number} 135 | */ 136 | export function RandLognormal(simulate, mu, sigma) { 137 | isNormalNumber(mu, "RandLognormal", "mu"); 138 | isNormalNumber(sigma, "RandLognormal", "sigma"); 139 | 140 | 141 | if (mu <= 0) { 142 | throw new ModelError(`Mu for RandLognormal() must be greater than 0; got ${mu}.`, { 143 | code: 4001 144 | }); 145 | } 146 | if (sigma <= 0) { 147 | throw new ModelError(`Sigma for RandLognormal() must be greater than 0; got ${sigma}.`, { 148 | code: 4002 149 | }); 150 | } 151 | 152 | 153 | let lmu = Math.log(mu) - 0.5 * Math.log(1 + Math.pow(sigma / mu, 2)); 154 | let lsigma = Math.sqrt(Math.log(1 + Math.pow(sigma / mu, 2))); 155 | 156 | return Math.exp(RandNormal(simulate, lmu, lsigma)); 157 | } 158 | 159 | 160 | /** 161 | * Based on: 162 | * https://github.com/numpy/numpy/blob/623bc1fae1d47df24e7f1e29321d0c0ba2771ce0/numpy/random/src/distributions/distributions.c 163 | * 164 | * @param {import("../Simulator").Simulator} simulate 165 | * @param {number} count 166 | * @param {number} probability 167 | * 168 | * @returns {number} 169 | */ 170 | export function RandBinomial(simulate, count, probability) { 171 | isNormalNumber(count, "RandBinomial", "count"); 172 | isNormalNumber(probability, "RandBinomial", "probability"); 173 | if (count < 0) { 174 | throw new ModelError(`Count for RandBinomial() must be greater than or equal to 0; got ${count}.`, { 175 | code: 4003 176 | }); 177 | } 178 | if (probability < 0 || probability > 1) { 179 | throw new ModelError(`Probability for RandBinomial() must be between 0 and 1 (inclusive); got ${probability}.`, { 180 | code: 4004 181 | }); 182 | } 183 | 184 | if ((count === 0) || (probability === 0)) { 185 | return 0; 186 | } 187 | 188 | if (probability <= 0.5) { 189 | if (probability * count <= 30.0) { 190 | return randomBinomialInversion(simulate, count, probability); 191 | } else { 192 | return randomBinomialBtpe(simulate, count, probability); 193 | } 194 | } else { 195 | let q = 1.0 - probability; 196 | if (q * count <= 30.0) { 197 | return count - randomBinomialInversion(simulate, count, q); 198 | } else { 199 | return count - randomBinomialBtpe(simulate, count, q); 200 | } 201 | } 202 | } 203 | 204 | /** 205 | * @param {import("../Simulator").Simulator} simulate 206 | * @param {number} n 207 | * @param {number} p 208 | */ 209 | function randomBinomialBtpe(simulate, n, p) { 210 | let r, q, fm, p1, xm, xl, xr, c, laml, lamr, p2, p3, p4; 211 | let a, u, v, s, F, rho, t, A, nrq, x1, x2, f1, f2, z, z2, w, w2, x; 212 | let m, y, k, i; 213 | 214 | 215 | r = Math.min(p, 1.0 - p); 216 | q = 1.0 - r; 217 | fm = n * r + r; 218 | m = Math.floor(fm); 219 | p1 = Math.floor(2.195 * Math.sqrt(n * r * q) - 4.6 * q) + 0.5; 220 | xm = m + 0.5; 221 | xl = xm - p1; 222 | xr = xm + p1; 223 | c = 0.134 + 20.5 / (15.3 + m); 224 | a = (fm - xl) / (fm - xl * r); 225 | laml = a * (1.0 + a / 2.0); 226 | a = (xr - fm) / (xr * q); 227 | lamr = a * (1.0 + a / 2.0); 228 | p2 = p1 * (1.0 + 2.0 * c); 229 | p3 = p2 + c / laml; 230 | p4 = p3 + c / lamr; 231 | 232 | 233 | // eslint-disable-next-line 234 | while (true) { 235 | nrq = n * r * q; 236 | u = Rand(simulate) * p4; 237 | v = Rand(simulate); 238 | if (!(u > p1)) { 239 | y = Math.floor(xm - p1 * v + u); 240 | break; 241 | } 242 | 243 | 244 | if (!(u > p2)) { 245 | x = xl + (u - p1) / c; 246 | v = v * c + 1.0 - Math.abs(m - x + 0.5) / p1; 247 | if (v > 1.0) { 248 | continue; 249 | } 250 | y = Math.floor(x); 251 | } else if (!(u > p3)) { 252 | y = Math.floor(xl + Math.log(v) / laml); 253 | /* Reject if v==0.0 since previous cast is undefined */ 254 | if ((y < 0) || (v === 0.0)) { 255 | continue; 256 | } 257 | v = v * (u - p2) * laml; 258 | } else { 259 | y = Math.floor(xr - Math.log(v) / lamr); 260 | /* Reject if v==0.0 since previous cast is undefined */ 261 | if ((y > n) || (v === 0.0)) { 262 | continue; 263 | } 264 | v = v * (u - p3) * lamr; 265 | } 266 | 267 | k = Math.abs(y - m); 268 | if (!((k > 20) && (k < ((nrq) / 2.0 - 1)))) { 269 | s = r / q; 270 | a = s * (n + 1); 271 | F = 1.0; 272 | if (m < y) { 273 | for (i = m + 1; i <= y; i++) { 274 | F *= (a / i - s); 275 | } 276 | } else if (m > y) { 277 | for (i = y + 1; i <= m; i++) { 278 | F /= (a / i - s); 279 | } 280 | } 281 | if (v > F) { 282 | continue; 283 | } 284 | break; 285 | } else { 286 | rho = 287 | (k / (nrq)) * ((k * (k / 3.0 + 0.625) + 0.16666666666666666) / nrq + 0.5); 288 | t = -k * k / (2 * nrq); 289 | /* log(0.0) ok here */ 290 | A = Math.log(v); 291 | if (A < (t - rho)) { 292 | break; 293 | } 294 | if (A > (t + rho)) { 295 | continue; 296 | } 297 | 298 | x1 = y + 1; 299 | f1 = m + 1; 300 | z = n + 1 - m; 301 | w = n - y + 1; 302 | x2 = x1 * x1; 303 | f2 = f1 * f1; 304 | z2 = z * z; 305 | w2 = w * w; 306 | if (A > (xm * Math.log(f1 / x1) + (n - m + 0.5) * Math.log(z / w) + 307 | (y - m) * Math.log(w * r / (x1 * q)) + 308 | (13680. - (462. - (132. - (99. - 140. / f2) / f2) / f2) / f2) / f1 / 309 | 166320. + 310 | (13680. - (462. - (132. - (99. - 140. / z2) / z2) / z2) / z2) / z / 311 | 166320. + 312 | (13680. - (462. - (132. - (99. - 140. / x2) / x2) / x2) / x2) / x1 / 313 | 166320. + 314 | (13680. - (462. - (132. - (99. - 140. / w2) / w2) / w2) / w2) / w / 315 | 166320.)) { 316 | continue; 317 | } 318 | } 319 | } 320 | 321 | if (p > 0.5) { 322 | y = n - y; 323 | } 324 | 325 | return y; 326 | } 327 | 328 | /** 329 | * @param {import("../Simulator").Simulator} simulate 330 | * @param {number} n 331 | * @param {number} p 332 | */ 333 | function randomBinomialInversion(simulate, n, p) { 334 | let q, qn, np, px, U; 335 | let X, bound; 336 | 337 | q = 1.0 - p; 338 | qn = Math.exp(n * Math.log(q)); 339 | np = n * p; 340 | bound = Math.min(n, np + 10.0 * Math.sqrt(np * q + 1)); 341 | 342 | X = 0; 343 | px = qn; 344 | U = Rand(simulate); 345 | while (U > px) { 346 | X++; 347 | if (X > bound) { 348 | X = 0; 349 | px = qn; 350 | U = Rand(simulate); 351 | } else { 352 | U -= px; 353 | px = ((n - X + 1) * p * px) / (X * q); 354 | } 355 | } 356 | return X; 357 | } 358 | 359 | 360 | /** 361 | * @param {import("../Simulator").Simulator} simulate 362 | * @param {number} successes 363 | * @param {number} probability 364 | * 365 | * @returns {number} 366 | */ 367 | export function RandNegativeBinomial(simulate, successes, probability) { 368 | isNormalNumber(successes, "RandNegativeBinomial", "successes"); 369 | isNormalNumber(probability, "RandNegativeBinomial", "probability"); 370 | if (successes < 0) { 371 | throw new ModelError(`Successes for RandNegativeBinomial() must be greater than or equal to 0; got ${successes}.`, { 372 | code: 4005 373 | }); 374 | } 375 | if (probability < 0 || probability > 1) { 376 | throw new ModelError(`Probability for RandNegativeBinomial() must be between 0 and 1 (inclusive); got ${probability}.`, { 377 | code: 4006 378 | }); 379 | } 380 | 381 | let i = 0; 382 | let s = 0; 383 | while (s < successes) { 384 | if (Rand(simulate) <= probability) { 385 | s = s + 1; 386 | } 387 | i = i + 1; 388 | } 389 | return i; 390 | } 391 | 392 | 393 | /** 394 | * @param {import("../Simulator").Simulator} simulate 395 | * @param {number} lambda 396 | * 397 | * @returns {number} 398 | */ 399 | export function RandPoisson(simulate, lambda) { 400 | isNormalNumber(lambda, "RandPoisson", "lambda"); 401 | if (lambda < 0) { 402 | throw new ModelError(`Lambda for RandPoisson() must be greater than or equal to 0; got ${lambda}.`, { 403 | code: 4007 404 | }); 405 | } 406 | 407 | 408 | if (lambda < 50) { 409 | // Slow method suitable for small lambda's 410 | // https://en.wikipedia.org/wiki/Poisson_distribution#Generating_Poisson-distributed_random_variables 411 | let L = Math.exp(-lambda); 412 | let k = 0; 413 | let p = 1; 414 | // eslint-disable-next-line 415 | while (true) { 416 | k = k + 1; 417 | p = p * Rand(simulate); 418 | if (!(p > L)) { 419 | break; 420 | } 421 | } 422 | return k - 1; 423 | } else { 424 | // Approximation suitable for large lambda's 425 | // https://www.johndcook.com/blog/2010/06/14/generating-poisson-random-values/ 426 | let c = 0.767 - 3.36 / lambda; 427 | let beta = Math.PI / Math.sqrt(3.0 * lambda); 428 | let alpha = beta * lambda; 429 | let k = Math.log(c) - lambda - Math.log(beta); 430 | 431 | // eslint-disable-next-line 432 | while (true) { 433 | let u = Rand(simulate); 434 | let x = (alpha - Math.log((1.0 - u) / u)) / beta; 435 | let n = Math.floor(x + 0.5); 436 | if (n < 0) { 437 | continue; 438 | } 439 | let v = Rand(simulate); 440 | let y = alpha - beta * x; 441 | let lhs = y + Math.log(v / Math.pow(1.0 + Math.exp(y), 2)); 442 | let rhs = k + n * Math.log(lambda) - jStat.gammaln(n + 1); 443 | if (lhs <= rhs) { 444 | return n; 445 | } 446 | } 447 | } 448 | } 449 | 450 | /** 451 | * @param {import("../Simulator").Simulator} simulate 452 | * @param {number} alpha 453 | * @param {number} beta 454 | * 455 | * @returns {number} 456 | */ 457 | export function RandGamma(simulate, alpha, beta) { 458 | isNormalNumber(alpha, "RandGamma", "alpha"); 459 | isNormalNumber(beta, "RandGamma", "beta"); 460 | 461 | if (alpha <= 0) { 462 | throw new ModelError(`Alpha (shape parameter) for RandGamma() must be greater than 0; got ${alpha}.`, { 463 | code: 4008 464 | }); 465 | } 466 | if (beta <= 0) { 467 | throw new ModelError(`Beta (rate parameter) for RandGamma() must be greater than 0; got ${beta}.`, { 468 | code: 4009 469 | }); 470 | } 471 | 472 | 473 | let temp = 1; 474 | for (let i = 1; i <= alpha; i++) { 475 | temp = temp * Rand(simulate); 476 | } 477 | return -beta * Math.log(temp); 478 | } 479 | 480 | 481 | /** 482 | * @param {import("../Simulator").Simulator} simulate 483 | * @param {number} alpha 484 | * @param {number} beta 485 | * 486 | * @returns {number} 487 | */ 488 | export function RandBeta(simulate, alpha, beta) { 489 | isNormalNumber(alpha, "RandBeta", "alpha"); 490 | isNormalNumber(beta, "RandBeta", "beta"); 491 | 492 | if (alpha <= 0) { 493 | throw new ModelError(`Alpha for RandBeta() must be greater than 0; got ${alpha}.`, { 494 | code: 4010 495 | }); 496 | } 497 | if (beta <= 0) { 498 | throw new ModelError(`Beta for RandBeta() must be greater than 0; got ${beta}.`, { 499 | code: 4011 500 | }); 501 | } 502 | 503 | let x = RandGamma(simulate, alpha, 1); 504 | let y = RandGamma(simulate, beta, 1); 505 | return x / (x + y); 506 | } 507 | 508 | 509 | /** 510 | * @param {import("../Simulator").Simulator} simulate 511 | * @param {number} minimum 512 | * @param {number} maximum 513 | * @param {number} peak 514 | * 515 | * @returns {number} 516 | */ 517 | export function RandTriangular(simulate, minimum, maximum, peak) { 518 | isNormalNumber(minimum, "RandTriangular", "minimum"); 519 | isNormalNumber(maximum, "RandTriangular", "maximum"); 520 | isNormalNumber(peak, "RandTriangular", "peak"); 521 | 522 | let a = 0 + minimum; 523 | let b = 0 + maximum; 524 | let c = 0 + peak; 525 | 526 | if (a === b) { 527 | throw new ModelError("Maximum can't equal the minimum for the triangular distribution.", { 528 | code: 4012 529 | }); 530 | } 531 | 532 | if (c < a || c > b) { 533 | throw new ModelError("The peak must be within the maximum and minimum for the triangular distribution.", { 534 | code: 4013 535 | }); 536 | } 537 | 538 | let fc = (c - a) / (b - a); 539 | 540 | let u = Rand(simulate); 541 | 542 | if (u < fc) { 543 | return a + Math.sqrt(u * (b - a) * (c - a)); 544 | } else { 545 | return b - Math.sqrt((1 - u) * (b - a) * (b - c)); 546 | } 547 | } 548 | 549 | 550 | /** 551 | * @param {import("../Simulator").Simulator} simulate 552 | * @param {number[]} x 553 | * @param {number[]} y 554 | * 555 | * @returns {number} 556 | */ 557 | export function RandDist(simulate, x, y) { 558 | if (x.length !== y.length) { 559 | throw new ModelError("The lengths of the 'x' and 'y' vectors must be the same.", { 560 | code: 4014 561 | }); 562 | } 563 | if (x.length < 2) { 564 | throw new ModelError("There must be at least 2 points in a distribution to generate a random number.", { 565 | code: 4015 566 | }); 567 | } 568 | for (let i = 0; i < x.length; i++) { 569 | isNormalNumber(x[i], "RandDist", "x"); 570 | isNormalNumber(y[i], "RandDist", "y"); 571 | if (y[i] < 0) { 572 | throw new ModelError("The y values of RandDist cannot be negative.", { 573 | code: 4016 574 | }); 575 | } 576 | } 577 | 578 | let area = 0; 579 | for (let i = 0; i < x.length - 1; i++) { 580 | area += (x[i + 1] - x[i]) * (y[i + 1] + y[i]) / 2; 581 | } 582 | if (area === 0) { 583 | throw new ModelError("The area of the distribution in RandDist cannot be 0.", { 584 | code: 4017 585 | }); 586 | } 587 | 588 | let a = area * Rand(simulate); 589 | area = 0; 590 | for (let i = 0; i < x.length - 1; i++) { 591 | let nextArea = (x[i + 1] - x[i]) * (y[i + 1] + y[i]) / 2; 592 | if (a > area && a < area + nextArea) { 593 | let neededArea = a - area; 594 | let slope = (y[i + 1] - y[i]) / (x[i + 1] - x[i]); 595 | let dist; 596 | if (slope === 0) { 597 | dist = neededArea / y[i]; 598 | } else { 599 | dist = (-y[i] + Math.sqrt(Math.pow(y[i], 2) + 2 * slope * neededArea)) / slope; 600 | } 601 | 602 | return x[i] + dist; 603 | } 604 | area += nextArea; 605 | } 606 | 607 | } 608 | 609 | 610 | /** 611 | * @param {number} x 612 | * @param {string} name 613 | * @param {string} v 614 | */ 615 | function isNormalNumber(x, name, v) { 616 | if (isNaN(x)) { 617 | throw new ModelError(`The ${v} passed to ${name}() was not a number.`, { 618 | code: 4018 619 | }); 620 | } 621 | 622 | if (!isFinite(x)) { 623 | throw new ModelError(`The ${v} passed to ${name}() must not be infinite.`, { 624 | code: 4019 625 | }); 626 | } 627 | 628 | if (x > 1e+15) { 629 | // randBinomial fails at a count of around 1e26. Let's add a reasonable maximum to all these 630 | // functions 631 | throw new ModelError(`The ${v} passed to ${name}() must not be greater than 1e15 (got ${x}).`, { 632 | code: 4020 633 | }); 634 | } 635 | } 636 | -------------------------------------------------------------------------------- /src/formula/SyntaxCheck.js: -------------------------------------------------------------------------------- 1 | import antlr from "../../vendor/antlr4-all.js"; 2 | import FormulaLexer from "./grammar/FormulaLexer.js"; 3 | import FormulaParser from "./grammar/FormulaParser.js"; 4 | 5 | 6 | export function isValidSyntax(input) { 7 | let error = /** @type {any} */ (null); 8 | 9 | const chars = new antlr.InputStream(input); 10 | const lexer = new FormulaLexer(chars); 11 | 12 | lexer.removeErrorListeners(); 13 | lexer.addErrorListener({ 14 | syntaxError: (recognizer, offendingSymbol, line, column, msg, err) => { 15 | error = { 16 | line, 17 | column 18 | }; 19 | } 20 | }); 21 | const tokens = new antlr.CommonTokenStream(lexer); 22 | const parser = new FormulaParser(tokens); 23 | parser.removeErrorListeners(); 24 | parser.addErrorListener({ 25 | syntaxError: (recognizer, offendingSymbol, line, column, msg, err) => { 26 | error = { 27 | line, 28 | column 29 | }; 30 | }, 31 | reportAttemptingFullContext: (...args) => { 32 | // console.log("reportAttemptingFullContext", args); 33 | }, 34 | reportAmbiguity: (...args) => { 35 | // console.log("reportAmbiguity", args); 36 | }, 37 | reportContextSensitivity: (...args) => { 38 | // console.log("reportContextSensitivity", args); 39 | } 40 | }); 41 | 42 | try { 43 | if (!error) { 44 | parser.lines(); 45 | } 46 | 47 | if (error) { 48 | return { 49 | error: true, 50 | line: error.line, 51 | char: error.column, 52 | token: error.token 53 | }; 54 | } 55 | } catch (_err) { 56 | // Can throw an exception with: "Invalid equation syntax" 57 | console.warn(_err); 58 | return { 59 | error: true, 60 | token: null 61 | }; 62 | } 63 | 64 | return { 65 | error: false 66 | }; 67 | } -------------------------------------------------------------------------------- /src/formula/Utilities.js: -------------------------------------------------------------------------------- 1 | import { SAgent } from "../Primitives.js"; 2 | import { eq, toNum } from "./Formula.js"; 3 | import { Material } from "./Material.js"; 4 | import { ModelError } from "./ModelError.js"; 5 | import { Vector } from "./Vector.js"; 6 | 7 | 8 | /** 9 | * @param {*} x 10 | * @param {import("../Simulator").Simulator} simulate 11 | */ 12 | export function stringify(x, simulate) { 13 | if (x instanceof Vector) { 14 | return x.recurseApply((x) => stringify(x, simulate)); 15 | } 16 | // eslint-disable-next-line 17 | let res = new String(x); 18 | // @ts-ignore 19 | res.parent = simulate.varBank.get("stringbase"); 20 | return res; 21 | } 22 | 23 | 24 | 25 | /** 26 | * @param {*} mat 27 | * @param {import("../Simulator").Simulator} simulate 28 | * @param {*} items 29 | * @param {*} fill 30 | */ 31 | export function selectFromMatrix(mat, simulate, items, fill) { 32 | 33 | let selectorCount = items.length; 34 | 35 | function vectorize(m) { 36 | if (!(m instanceof Vector)) { 37 | if (items[0] === "*") { 38 | if (m instanceof Material) { 39 | throw new ModelError("Can't use * selector on a number.", { 40 | code: 3000 41 | }); 42 | } else if (m instanceof Boolean || typeof m === "boolean") { 43 | throw new ModelError("Can't use * selector on an boolean.", { 44 | code: 3001 45 | }); 46 | } else if (m instanceof String || typeof m === "string") { 47 | throw new ModelError("Can't use * selector on an string.", { 48 | code: 3002 49 | }); 50 | } else { 51 | throw new ModelError("Can't use * selector on an object.", { 52 | code: 3003 53 | }); 54 | } 55 | } 56 | if (m.vector) { 57 | m = m.vector; 58 | } else if (m.parent) { 59 | m = new Vector([], simulate, [], m.parent); 60 | } 61 | } 62 | if (fill === undefined && m.fullClone) { 63 | m = m.fullClone(); 64 | } 65 | return m; 66 | } 67 | 68 | 69 | let m = vectorize(mat); 70 | 71 | let root = selectFromVector(m, simulate, items.shift(), !items.length ? fill : undefined, fill !== undefined); 72 | let children = []; 73 | if (root.collapsed) { 74 | children = [root.data]; 75 | } else { 76 | if (!root.data.items) { 77 | throw new ModelError(`No element available for selector ${selectorCount - items.length + 1}`, { 78 | code: 3004 79 | }); 80 | } 81 | children = root.data.items; 82 | } 83 | 84 | while (items.length) { 85 | let newChildren = []; 86 | let selector = items.shift(); 87 | for (let i = 0; i < children.length; i++) { 88 | if (!(children[i] instanceof Vector)) { 89 | throw new ModelError(`No element available for selector ${selectorCount - items.length}`, { 90 | code: 3005 91 | }); 92 | } 93 | 94 | let vec = selectFromVector(children[i], simulate, selector, !items.length ? fill : undefined); 95 | 96 | if (vec.collapsed) { 97 | 98 | if (!fill) { 99 | children[i].items = [vec.data]; 100 | children[i].names = ["!!BREAKOUT DATA"]; 101 | } 102 | 103 | newChildren = newChildren.concat(vec.data); 104 | } else { 105 | newChildren = newChildren.concat(vec.data.items); 106 | 107 | if (!fill) { 108 | children[i].items = vec.data.items; 109 | children[i].names = vec.data.names; 110 | } 111 | } 112 | } 113 | children = newChildren; 114 | } 115 | 116 | return doBreakouts(root.data); 117 | } 118 | 119 | 120 | function doBreakouts(vec) { 121 | if (!(vec instanceof Vector)) { 122 | return vec; 123 | } 124 | if (vec.items.length === 1 && vec.names && vec.names[0] === "!!BREAKOUT DATA") { 125 | return doBreakouts(vec.items[0]); 126 | } 127 | for (let i = 0; i < vec.items.length; i++) { 128 | vec.items[i] = doBreakouts(vec.items[i]); 129 | } 130 | return vec; 131 | } 132 | 133 | 134 | /** 135 | * @param {*} vec 136 | * @param {import("../Simulator").Simulator} simulate 137 | * @param {*} items 138 | * @param {*} fill 139 | * @param {*=} doNotClone 140 | */ 141 | export function selectFromVector(vec, simulate, items, fill, doNotClone) { 142 | if (items === "*") { 143 | return { data: vec }; 144 | } else if (typeof items === "function") { 145 | return { data: items([vec]), collapsed: true }; 146 | } else if (items === "parent") { 147 | if (vec.parent) { 148 | return { data: doNotClone ? vec.parent : vec.parent.fullClone(), collapsed: true }; 149 | } else { 150 | throw new ModelError("Vector does not have a parent.", { 151 | code: 3006 152 | }); 153 | } 154 | } 155 | 156 | if (items instanceof Vector) { 157 | let res = []; 158 | let names = vec.names ? [] : undefined; 159 | for (let i = 0; i < items.items.length; i++) { 160 | let v = items.items[i]; 161 | let shouldSelect = true; 162 | if (typeof v === "boolean") { 163 | if (v) { 164 | v = new Material(i + 1); 165 | } else { 166 | shouldSelect = false; 167 | } 168 | } 169 | if (shouldSelect) { 170 | let r = selectElementFromVector(vec, v, fill); 171 | res.push(r.value); 172 | if (names) { 173 | names.push(r.name); 174 | } 175 | } 176 | } 177 | return { collapsed: false, parent: vec, data: new Vector(res, simulate, names, vec.parent) }; 178 | } else { 179 | return { collapsed: true, parent: vec, data: selectElementFromVector(vec, items, fill).value }; 180 | } 181 | } 182 | 183 | 184 | /** 185 | * @param {Vector} vec 186 | * @param {any} item 187 | * @param {any} fill 188 | * 189 | * @returns {any} 190 | */ 191 | function selectElementFromVector(vec, item, fill) { 192 | let name = undefined; 193 | let value = undefined; 194 | 195 | let index; 196 | 197 | 198 | if (typeof item === "string" || item instanceof String) { 199 | try { 200 | if (fill === undefined) { 201 | if (!vec.names) { 202 | if (vec.parent) { 203 | return selectElementFromVector(vec.parent, item, fill); 204 | } else { 205 | throw new ModelError(`Key '${item}' not in vector.`, { 206 | code: 3007 207 | }); 208 | } 209 | } 210 | } 211 | if (vec.names) { 212 | index = -1; 213 | let lc = item.toLowerCase(); 214 | for (let i = 0; i < vec.names.length; i++) { 215 | if (vec.names[i] && vec.names[i].toLowerCase() === lc) { 216 | index = i; 217 | break; 218 | } 219 | } 220 | if (index < 0) { 221 | index = vec.names.indexOf("*"); 222 | } 223 | } 224 | if (index < 0 || index === undefined) { 225 | if (fill === undefined) { 226 | if (vec.parent) { 227 | return selectElementFromVector(vec.parent, item, fill); 228 | } else { 229 | throw new ModelError(`Key '${item}' not in vector.`, { 230 | code: 3008 231 | }); 232 | } 233 | } else { 234 | index = item; 235 | } 236 | 237 | } 238 | } catch (err) { 239 | if (vec.parent) { 240 | return selectElementFromVector(vec.parent, item, fill); 241 | } else { 242 | throw err; 243 | } 244 | } 245 | 246 | } else { 247 | index = parseFloat(toNum(item)) - 1; 248 | } 249 | 250 | if (index instanceof String || typeof index === "string") { 251 | if (!vec.names) { 252 | vec.names = []; 253 | for (let i = 0; i < vec.items.length; i++) { 254 | vec.names.push(undefined); 255 | } 256 | } 257 | vec.items.push(fill); 258 | vec.names.push(index.valueOf()); 259 | value = fill; 260 | name = index; 261 | 262 | } else { 263 | if (index < 0 || !vec.items || index >= vec.items.length || index % 1 !== 0) { 264 | throw new ModelError("Index " + (1 + index) + " is not in the vector.", { 265 | code: 3009 266 | }); 267 | } 268 | if (fill !== undefined) { 269 | vec.items[index] = fill; 270 | } 271 | value = vec.items[index]; 272 | if (vec.names) { 273 | name = vec.names[index]; 274 | } 275 | } 276 | 277 | return { name: name, value: value }; 278 | } 279 | 280 | 281 | export function strictEquals(a, b) { 282 | if (a instanceof SAgent || b instanceof SAgent) { 283 | if (a instanceof SAgent && b instanceof SAgent) { 284 | if (a.instanceId === b.instanceId) { 285 | return true; 286 | } 287 | } 288 | } else if (a instanceof Vector || b instanceof Vector) { 289 | if (a instanceof Vector && b instanceof Vector) { 290 | if (a.equals(b)) { 291 | return true; 292 | } 293 | } 294 | } else if (eq(a, b)) { 295 | return true; 296 | } 297 | return false; 298 | } 299 | 300 | -------------------------------------------------------------------------------- /src/formula/Vector.js: -------------------------------------------------------------------------------- 1 | import { toNum } from "./Formula.js"; 2 | import { Material } from "./Material.js"; 3 | import { ModelError } from "./ModelError.js"; 4 | import { selectFromMatrix, strictEquals } from "./Utilities.js"; 5 | 6 | /** 7 | * Truncates to max length adding ellipsis if truncated. If text is only 2 or fewer characters longer than maxLength, it will not be truncated. 8 | * 9 | * @param {string} string 10 | * @param {number} maxLength 11 | */ 12 | function truncate(string, maxLength) { 13 | if (string.length > maxLength + 2) { 14 | return string.substring(0, maxLength - 3) + "..."; 15 | } 16 | return string; 17 | } 18 | 19 | /** 20 | * @param {string[]} thisNames 21 | * @param {string[]} keys 22 | * 23 | * @returns {boolean} 24 | */ 25 | function keysMatch(thisNames, keys) { 26 | if (!keys.includes("*")) { 27 | for (let i = 0; i < thisNames.length; i++) { 28 | if (thisNames[i] !== "*") { 29 | if (thisNames[i] === undefined) { 30 | return false; 31 | } 32 | if (!keys.includes(thisNames[i])) { 33 | return false; 34 | } 35 | } 36 | } 37 | } 38 | 39 | if (!thisNames.includes("*")) { 40 | for (let i = 0; i < keys.length; i++) { 41 | if (keys[i] !== "*") { 42 | if (keys[i] === undefined) { 43 | return false; 44 | } 45 | if (!thisNames.includes(keys[i])) { 46 | return false; 47 | } 48 | } 49 | } 50 | } 51 | 52 | return true; 53 | } 54 | 55 | 56 | /** @template {any} T */ 57 | export class Vector { 58 | /** 59 | * @param {T[]} items 60 | * @param {import("../Simulator").Simulator} simulate 61 | * @param {string[]=} names 62 | * @param {Vector=} parent 63 | */ 64 | constructor(items, simulate, names, parent) { 65 | this.simulate = simulate; 66 | 67 | this.parent = parent ? parent : simulate.varBank.get("vectorbase"); 68 | this.items = items; 69 | /** @type {string[]} */ 70 | this.names = names; 71 | /** @type {string[]} */ 72 | this.namesLC = undefined; 73 | /** @type {boolean} */ 74 | this.isNum = undefined; 75 | /** @type {boolean} */ 76 | this.terminateApply = undefined; 77 | 78 | if (names) { 79 | this.namesLC = []; 80 | for (let name of names) { 81 | if (name) { 82 | this.namesLC.push(name.toLowerCase()); 83 | } else { 84 | this.namesLC.push(undefined); 85 | } 86 | } 87 | } 88 | 89 | } 90 | 91 | toNum() { 92 | if (this.isNum) { 93 | return this; 94 | } 95 | 96 | let v = this.fullClone(); 97 | for (let i = 0; i < v.items.length; i++) { 98 | v.items[i] = toNum(v.items[i]); 99 | } 100 | v.isNum = true; 101 | return v; 102 | } 103 | 104 | /** 105 | * @returns {string} 106 | */ 107 | toString() { 108 | let items = []; 109 | for (let i = 0; i < this.items.length; i++) { 110 | try { 111 | let str = toNum(this.items[i]).toString(); 112 | if (this.names && this.names[i]) { 113 | str = this.names[i] + ": " + str; 114 | } 115 | items.push(str); 116 | } catch (err) { 117 | items.push("{Cannot convert value to string}"); 118 | } 119 | } 120 | return "{" + items.join(", ") + "}"; 121 | } 122 | 123 | /** 124 | * @returns {number} 125 | */ 126 | length() { 127 | return this.items.length; 128 | } 129 | 130 | cloneCombine(other, operation, rhs, noSwitch) { 131 | return this.fullClone().combine(other, operation, rhs, noSwitch); 132 | } 133 | 134 | combine(other, operation, rhs, noSwitch) { 135 | if (other instanceof Vector) { 136 | if (this.length() !== other.length() && (!this.names || !other.names)) { 137 | throw new ModelError(`Vectors must have equal length when combined. Got lengths of ${this.length()} and ${other.length()}.`, { 138 | code: 2001 139 | }); 140 | } 141 | } 142 | 143 | if (other instanceof Vector && (this.names && other.names)) { 144 | if (!noSwitch && other.depth() > this.depth()) { 145 | return other.combine(this, operation, !rhs); 146 | } 147 | if (!this.keysMatch(other.namesLC)) { 148 | if (this.items[0] instanceof Vector) { 149 | for (let i = 0; i < this.items.length; i++) { 150 | /** @type {Vector} */ (this.items[i]).combine(other, operation, rhs, true); 151 | } 152 | return this; 153 | } else { 154 | throw new ModelError(`Keys do not match for vector operation. Got keys {${truncate(this.names.map(x => "\"" + x +"\"").join(", "), 36)}} and {${truncate(other.names.map(x => "\"" + x +"\"").join(", "), 36)}}.`, { 155 | code: 2000 156 | }); 157 | } 158 | } 159 | } 160 | 161 | for (let i = 0; i < this.length(); i++) { 162 | let x; 163 | if (other instanceof Vector) { 164 | if (this.names && other.names) { 165 | let index = other.namesLC.indexOf(this.namesLC[i]); 166 | if (this.names.length === 1 && this.names[0] === "*") { 167 | index = -2; 168 | } 169 | if (index === -1) { 170 | index = other.names.indexOf("*"); 171 | } 172 | if (index === -1) { 173 | throw new ModelError("Mismatched keys for vector operation.", { 174 | code: 2002 175 | }); 176 | } 177 | if (index === -2) { 178 | x = undefined; 179 | } else { 180 | x = other.items[index]; 181 | } 182 | } else { 183 | x = other.items[i]; 184 | } 185 | } else { 186 | x = other; 187 | } 188 | if (x !== undefined) { 189 | if (rhs) { 190 | this.items[i] = operation(x, this.items[i]); 191 | } else { 192 | this.items[i] = operation(this.items[i], x); 193 | } 194 | } 195 | } 196 | 197 | if (this.names && this.names.includes("*") && other instanceof Vector && other.names) { 198 | let starred = this.items[this.names.indexOf("*")]; 199 | for (let i = 0; i < other.names.length; i++) { 200 | if (other.names[i] && (!this.namesLC.includes(other.namesLC[i]))) { 201 | 202 | if (rhs) { 203 | this.items.push(operation(other.items[i], starred)); 204 | } else { 205 | this.items.push(operation(starred, other.items[i])); 206 | } 207 | this.names.push(other.names[i]); 208 | this.namesLC.push(other.namesLC[i]); 209 | } 210 | } 211 | } 212 | 213 | return this; 214 | } 215 | 216 | collapseDimensions(target) { 217 | if (target instanceof Vector) { 218 | if (this.depth() === target.depth()) { 219 | return this; 220 | } else { 221 | let selector = []; 222 | /** @type {Vector} */ 223 | let base = this; 224 | let targetLevel = target; 225 | for (let i = 0; i < this.depth(); i++) { 226 | if (!(targetLevel instanceof Vector)) { 227 | selector.push((x) => { 228 | return this.simulate.varBank.get("sum")(x[0].items); 229 | }); 230 | base = /** @type {Vector} */ (base.items[0]); 231 | } else if ((base.namesLC === undefined && targetLevel.namesLC === undefined) || (base.namesLC !== undefined && targetLevel.namesLC !== undefined && keysMatch(base.namesLC, targetLevel.namesLC))) { 232 | selector.push("*"); 233 | base = /** @type {Vector} */ (base.items[0]); 234 | targetLevel = /** @type {Vector} */ (targetLevel.items[0]); 235 | } else { 236 | selector.push((x) => { 237 | return this.simulate.varBank.get("sum")(x[0].items); 238 | }); 239 | 240 | base = /** @type {Vector} */ (base.items[0]); 241 | } 242 | } 243 | if (targetLevel.items) { 244 | throw new ModelError("Keys do not match for vector collapsing.", { 245 | code: 2003 246 | }); 247 | } 248 | return selectFromMatrix(this, this.simulate, selector); 249 | } 250 | } else { 251 | return this.simulate.varBank.get("sum")([this.simulate.varBank.get("flatten")([this])]); 252 | } 253 | } 254 | 255 | /** 256 | * @returns {number} 257 | */ 258 | depth() { 259 | if (!this.items.length) { 260 | return 1; 261 | } 262 | let firstItem = this.items[0]; 263 | if (firstItem instanceof Vector) { 264 | return firstItem.depth() + 1; 265 | } 266 | return 1; 267 | } 268 | 269 | /** 270 | * @param {string[]} keys 271 | */ 272 | keysMatch(keys) { 273 | if (this.names && keys) { 274 | return keysMatch(this.namesLC, keys); 275 | } 276 | return false; 277 | } 278 | 279 | /** 280 | * @param {function} operation 281 | * @returns 282 | */ 283 | cloneApply(operation) { 284 | return this.fullClone().apply(operation); 285 | } 286 | 287 | /** 288 | * @param {function} operation 289 | * @returns 290 | */ 291 | apply(operation) { 292 | for (let i = 0; i < this.items.length; i++) { 293 | this.items[i] = operation(this.items[i], this.names ? this.names[i] : undefined); 294 | } 295 | return this; 296 | } 297 | 298 | /** 299 | * @param {function(Vector): any} operation 300 | * 301 | * @returns {Vector} 302 | */ 303 | stackApply(operation) { 304 | if (this.depth() === 1) { 305 | return operation(this); 306 | } 307 | let s = this.stack(); 308 | 309 | return s.recurseApply(operation); 310 | } 311 | 312 | /** 313 | * @param {any[]=} selector 314 | * 315 | * @returns 316 | */ 317 | stack(selector) { 318 | let res = []; 319 | 320 | selector = selector || [0]; 321 | 322 | let base = /** @type {Vector} */ (this.select(selector)); 323 | 324 | 325 | for (let i = 1; i < this.items.length; i++) { 326 | selector[0] = i; 327 | let alt = this.select(selector); 328 | if (base instanceof Vector && alt instanceof Vector) { 329 | if ((base.names && !alt.names) || (alt.names && !base.names)) { 330 | throw new ModelError("Mismatched keys for vector collapsing.", { 331 | code: 2004 332 | }); 333 | } else if (base.items.length !== alt.items.length) { 334 | throw new ModelError("Vectors of unequal size.", { 335 | code: 2005 336 | }); 337 | } 338 | } else if (!(base instanceof Vector || alt instanceof Vector)) { 339 | throw new ModelError("Mismatched keys for vector collapsing.", { 340 | code: 2006 341 | }); 342 | } 343 | } 344 | selector[0] = 0; 345 | 346 | selector.push(0); 347 | 348 | for (let i = 0; i < base.items.length; i++) { 349 | selector[selector.length - 1] = base.names ? base.namesLC[i] : i; 350 | 351 | if (base.items[i] instanceof Vector) { 352 | res.push(this.stack(selector.slice())); 353 | } else { 354 | let vecs = []; 355 | for (let j = 0; j < this.items.length; j++) { 356 | let newSelector = selector.slice(); 357 | newSelector[0] = j; 358 | 359 | let item = this.select(newSelector); 360 | if (item instanceof Vector) { 361 | throw new ModelError("Number where vector expected in vector collapsing.", { 362 | code: 2007 363 | }); 364 | } 365 | vecs.push(item); 366 | } 367 | let v = new Vector(vecs, this.simulate); 368 | v.terminateApply = true; 369 | res.push(v); 370 | } 371 | } 372 | 373 | return new Vector(res, this.simulate, base.names ? base.names.slice() : undefined); 374 | } 375 | 376 | 377 | select(selector) { 378 | /** @type {VectorElementType} */ 379 | let b = this; 380 | 381 | for (let s = 0; s < selector.length; s++) { 382 | if (!(b instanceof Vector)) { 383 | throw new ModelError("Number where vector expected in vector collapsing.", { 384 | code: 2008 385 | }); 386 | } 387 | 388 | if (selector[s] instanceof String || typeof selector[s] === "string") { 389 | let ind = b.namesLC.indexOf(selector[s].valueOf()); 390 | if (ind === -1) { 391 | throw new ModelError("Mismatched keys for vector collapsing.", { 392 | code: 2009 393 | }); 394 | } 395 | b = b.items[ind]; 396 | } else { 397 | b = b.items[selector[s]]; 398 | } 399 | } 400 | return b; 401 | } 402 | 403 | /** 404 | * @param {function} operation 405 | * 406 | * @returns {Vector} 407 | */ 408 | recurseApply(operation) { 409 | for (let i = 0; i < this.items.length; i++) { 410 | let item = this.items[i]; 411 | if (item instanceof Vector && !item.terminateApply) { 412 | this.items[i] = /** @type {any} */ (item.recurseApply(operation)); 413 | } else { 414 | this.items[i] = operation(this.items[i]); 415 | } 416 | } 417 | return this; 418 | } 419 | 420 | 421 | fullNames() { 422 | let firstElement = this.items[0]; 423 | 424 | if (firstElement instanceof Vector && firstElement.names) { 425 | let subn = firstElement.fullNames(); 426 | let n = []; 427 | for (let i = 0; i < this.names.length; i++) { 428 | for (let j = 0; j < subn.length; j++) { 429 | n.push([this.names[i]].concat(subn[j])); 430 | } 431 | } 432 | 433 | return n; 434 | } else { 435 | return this.names.map(name => [name]); 436 | } 437 | } 438 | 439 | 440 | /** 441 | * @returns {Vector} 442 | */ 443 | clone() { 444 | let newItems = []; 445 | for (let item of this.items) { 446 | if (item instanceof Vector) { 447 | newItems.push(item.clone()); 448 | } else { 449 | newItems.push(item); 450 | } 451 | } 452 | return new Vector(newItems, this.simulate, this.names ? this.names.slice() : undefined, this.parent); 453 | } 454 | 455 | /** 456 | * @returns {Vector} 457 | */ 458 | fullClone() { 459 | let newItems = []; 460 | for (let item of this.items) { 461 | if (item instanceof Vector || item instanceof Material) { 462 | newItems.push(item.fullClone()); 463 | } else { 464 | newItems.push(item); 465 | } 466 | } 467 | return new Vector(newItems, this.simulate, this.names ? this.names.slice() : undefined, this.parent); 468 | } 469 | 470 | /** 471 | * @param {Vector} vec 472 | * 473 | * @returns {boolean} 474 | */ 475 | equals(vec) { 476 | if (this.length() !== vec.length()) { 477 | return false; 478 | } 479 | 480 | for (let i = 0; i < this.items.length; i++) { 481 | if (!strictEquals(this.items[i], vec.items[i])) { 482 | return false; 483 | } 484 | } 485 | return true; 486 | } 487 | } 488 | 489 | -------------------------------------------------------------------------------- /src/formula/grammar/FormulaLexer.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | /* eslint-disable */ 3 | 4 | 5 | import antlr4 from "../../../vendor/antlr4-all.js"; 6 | 7 | 8 | const serializedATN = [4,0,61,486,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2, 9 | 4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7, 10 | 12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,19, 11 | 2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,2,26,7,26,2, 12 | 27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2,31,7,31,2,32,7,32,2,33,7,33,2,34, 13 | 7,34,2,35,7,35,2,36,7,36,2,37,7,37,2,38,7,38,2,39,7,39,2,40,7,40,2,41,7, 14 | 41,2,42,7,42,2,43,7,43,2,44,7,44,2,45,7,45,2,46,7,46,2,47,7,47,2,48,7,48, 15 | 2,49,7,49,2,50,7,50,2,51,7,51,2,52,7,52,2,53,7,53,2,54,7,54,2,55,7,55,2, 16 | 56,7,56,2,57,7,57,2,58,7,58,2,59,7,59,2,60,7,60,1,0,1,0,1,1,1,1,1,2,1,2, 17 | 1,3,1,3,1,3,1,4,1,4,1,5,1,5,1,6,1,6,1,6,1,6,1,7,1,7,1,8,3,8,144,8,8,1,8, 18 | 1,8,1,8,1,8,5,8,150,8,8,10,8,12,8,153,9,8,1,8,1,8,1,8,1,8,3,8,159,8,8,1, 19 | 8,1,8,1,9,3,9,164,8,9,1,9,1,9,1,9,3,9,169,8,9,1,9,5,9,172,8,9,10,9,12,9, 20 | 175,9,9,1,9,3,9,178,8,9,1,9,1,9,1,10,5,10,183,8,10,10,10,12,10,186,9,10, 21 | 1,10,1,10,5,10,190,8,10,10,10,12,10,193,9,10,4,10,195,8,10,11,10,12,10,196, 22 | 1,11,4,11,200,8,11,11,11,12,11,201,1,12,1,12,1,13,1,13,1,13,1,13,1,13,1, 23 | 13,1,14,1,14,1,14,1,14,1,15,1,15,1,15,1,15,1,15,1,16,1,16,1,16,1,17,1,17, 24 | 1,17,1,18,1,18,1,18,1,19,1,19,1,19,1,19,1,19,1,20,1,20,1,20,1,21,1,21,1, 25 | 21,1,21,1,21,1,22,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1,23,1,23,1,23,1,23, 26 | 1,23,1,23,1,24,1,24,1,24,1,24,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,26,1, 27 | 26,1,26,1,26,1,27,1,27,1,27,1,27,1,28,1,28,1,28,1,28,1,28,1,28,1,29,1,29, 28 | 1,29,1,29,1,29,1,29,1,30,1,30,1,30,1,30,3,30,292,8,30,1,31,1,31,1,31,1,31, 29 | 1,32,1,32,1,32,1,32,1,32,3,32,303,8,32,1,33,1,33,1,33,3,33,308,8,33,1,34, 30 | 1,34,1,34,1,34,3,34,314,8,34,1,35,1,35,1,36,1,36,1,36,1,37,1,37,1,38,1,38, 31 | 1,38,1,39,1,39,1,40,1,40,1,41,1,41,1,42,1,42,1,43,1,43,1,43,1,43,3,43,338, 32 | 8,43,1,44,1,44,1,45,1,45,1,45,3,45,345,8,45,1,46,1,46,1,46,3,46,350,8,46, 33 | 1,47,1,47,1,48,1,48,1,49,4,49,357,8,49,11,49,12,49,358,1,49,1,49,3,49,363, 34 | 8,49,1,49,5,49,366,8,49,10,49,12,49,369,9,49,3,49,371,8,49,1,50,5,50,374, 35 | 8,50,10,50,12,50,377,9,50,1,50,1,50,4,50,381,8,50,11,50,12,50,382,1,50,4, 36 | 50,386,8,50,11,50,12,50,387,1,50,3,50,391,8,50,1,50,1,50,3,50,395,8,50,1, 37 | 50,5,50,398,8,50,10,50,12,50,401,9,50,3,50,403,8,50,1,51,1,51,1,51,1,51, 38 | 1,51,1,51,1,51,1,51,1,51,3,51,414,8,51,1,52,1,52,1,52,1,52,1,53,1,53,1,53, 39 | 1,53,1,53,1,53,1,53,1,53,1,54,1,54,1,54,1,54,1,54,1,54,1,55,1,55,5,55,436, 40 | 8,55,10,55,12,55,439,9,55,1,56,1,56,4,56,443,8,56,11,56,12,56,444,1,56,1, 41 | 56,1,56,1,56,1,56,4,56,452,8,56,11,56,12,56,453,1,56,1,56,1,56,3,56,459, 42 | 8,56,1,57,1,57,1,58,1,58,1,59,1,59,1,60,1,60,5,60,469,8,60,10,60,12,60,472, 43 | 9,60,1,60,1,60,1,60,1,60,1,60,5,60,479,8,60,10,60,12,60,482,9,60,1,60,3, 44 | 60,485,8,60,4,151,444,453,470,0,61,1,1,3,2,5,3,7,4,9,5,11,6,13,7,15,8,17, 45 | 9,19,10,21,11,23,12,25,13,27,14,29,15,31,16,33,17,35,18,37,19,39,20,41,21, 46 | 43,22,45,23,47,24,49,25,51,26,53,27,55,28,57,29,59,30,61,31,63,32,65,33, 47 | 67,34,69,35,71,36,73,37,75,38,77,39,79,40,81,41,83,42,85,43,87,44,89,45, 48 | 91,46,93,47,95,48,97,49,99,50,101,51,103,52,105,53,107,54,109,55,111,56, 49 | 113,57,115,58,117,59,119,60,121,61,1,0,28,2,0,78,78,110,110,2,0,79,79,111, 50 | 111,2,0,84,84,116,116,2,0,10,10,13,13,2,0,87,87,119,119,2,0,72,72,104,104, 51 | 2,0,73,73,105,105,2,0,76,76,108,108,2,0,69,69,101,101,2,0,70,70,102,102, 52 | 2,0,82,82,114,114,2,0,77,77,109,109,2,0,66,66,98,98,2,0,89,89,121,121,2, 53 | 0,80,80,112,112,2,0,83,83,115,115,2,0,85,85,117,117,2,0,67,67,99,99,2,0, 54 | 68,68,100,100,2,0,65,65,97,97,2,0,88,88,120,120,2,0,43,43,45,45,2,0,81,81, 55 | 113,113,2,0,65,90,97,122,4,0,48,57,65,90,95,95,97,122,2,0,91,91,93,93,3, 56 | 0,9,9,12,12,32,32,1,0,34,34,523,0,1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7, 57 | 1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0, 58 | 0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1, 59 | 0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0, 60 | 41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,0, 61 | 0,0,0,53,1,0,0,0,0,55,1,0,0,0,0,57,1,0,0,0,0,59,1,0,0,0,0,61,1,0,0,0,0,63, 62 | 1,0,0,0,0,65,1,0,0,0,0,67,1,0,0,0,0,69,1,0,0,0,0,71,1,0,0,0,0,73,1,0,0,0, 63 | 0,75,1,0,0,0,0,77,1,0,0,0,0,79,1,0,0,0,0,81,1,0,0,0,0,83,1,0,0,0,0,85,1, 64 | 0,0,0,0,87,1,0,0,0,0,89,1,0,0,0,0,91,1,0,0,0,0,93,1,0,0,0,0,95,1,0,0,0,0, 65 | 97,1,0,0,0,0,99,1,0,0,0,0,101,1,0,0,0,0,103,1,0,0,0,0,105,1,0,0,0,0,107, 66 | 1,0,0,0,0,109,1,0,0,0,0,111,1,0,0,0,0,113,1,0,0,0,0,115,1,0,0,0,0,117,1, 67 | 0,0,0,0,119,1,0,0,0,0,121,1,0,0,0,1,123,1,0,0,0,3,125,1,0,0,0,5,127,1,0, 68 | 0,0,7,129,1,0,0,0,9,132,1,0,0,0,11,134,1,0,0,0,13,136,1,0,0,0,15,140,1,0, 69 | 0,0,17,143,1,0,0,0,19,163,1,0,0,0,21,184,1,0,0,0,23,199,1,0,0,0,25,203,1, 70 | 0,0,0,27,205,1,0,0,0,29,211,1,0,0,0,31,215,1,0,0,0,33,220,1,0,0,0,35,223, 71 | 1,0,0,0,37,226,1,0,0,0,39,229,1,0,0,0,41,234,1,0,0,0,43,237,1,0,0,0,45,242, 72 | 1,0,0,0,47,247,1,0,0,0,49,256,1,0,0,0,51,260,1,0,0,0,53,267,1,0,0,0,55,271, 73 | 1,0,0,0,57,275,1,0,0,0,59,281,1,0,0,0,61,291,1,0,0,0,63,293,1,0,0,0,65,302, 74 | 1,0,0,0,67,307,1,0,0,0,69,313,1,0,0,0,71,315,1,0,0,0,73,317,1,0,0,0,75,320, 75 | 1,0,0,0,77,322,1,0,0,0,79,325,1,0,0,0,81,327,1,0,0,0,83,329,1,0,0,0,85,331, 76 | 1,0,0,0,87,337,1,0,0,0,89,339,1,0,0,0,91,344,1,0,0,0,93,349,1,0,0,0,95,351, 77 | 1,0,0,0,97,353,1,0,0,0,99,356,1,0,0,0,101,390,1,0,0,0,103,413,1,0,0,0,105, 78 | 415,1,0,0,0,107,419,1,0,0,0,109,427,1,0,0,0,111,433,1,0,0,0,113,458,1,0, 79 | 0,0,115,460,1,0,0,0,117,462,1,0,0,0,119,464,1,0,0,0,121,484,1,0,0,0,123, 80 | 124,5,40,0,0,124,2,1,0,0,0,125,126,5,44,0,0,126,4,1,0,0,0,127,128,5,41,0, 81 | 0,128,6,1,0,0,0,129,130,5,60,0,0,130,131,5,45,0,0,131,8,1,0,0,0,132,133, 82 | 5,58,0,0,133,10,1,0,0,0,134,135,5,33,0,0,135,12,1,0,0,0,136,137,7,0,0,0, 83 | 137,138,7,1,0,0,138,139,7,2,0,0,139,14,1,0,0,0,140,141,5,46,0,0,141,16,1, 84 | 0,0,0,142,144,3,23,11,0,143,142,1,0,0,0,143,144,1,0,0,0,144,145,1,0,0,0, 85 | 145,146,5,47,0,0,146,147,5,42,0,0,147,151,1,0,0,0,148,150,9,0,0,0,149,148, 86 | 1,0,0,0,150,153,1,0,0,0,151,152,1,0,0,0,151,149,1,0,0,0,152,154,1,0,0,0, 87 | 153,151,1,0,0,0,154,155,5,42,0,0,155,156,5,47,0,0,156,158,1,0,0,0,157,159, 88 | 3,23,11,0,158,157,1,0,0,0,158,159,1,0,0,0,159,160,1,0,0,0,160,161,6,8,0, 89 | 0,161,18,1,0,0,0,162,164,3,23,11,0,163,162,1,0,0,0,163,164,1,0,0,0,164,168, 90 | 1,0,0,0,165,166,5,47,0,0,166,169,5,47,0,0,167,169,5,35,0,0,168,165,1,0,0, 91 | 0,168,167,1,0,0,0,169,173,1,0,0,0,170,172,8,3,0,0,171,170,1,0,0,0,172,175, 92 | 1,0,0,0,173,171,1,0,0,0,173,174,1,0,0,0,174,177,1,0,0,0,175,173,1,0,0,0, 93 | 176,178,3,23,11,0,177,176,1,0,0,0,177,178,1,0,0,0,178,179,1,0,0,0,179,180, 94 | 6,9,0,0,180,20,1,0,0,0,181,183,3,119,59,0,182,181,1,0,0,0,183,186,1,0,0, 95 | 0,184,182,1,0,0,0,184,185,1,0,0,0,185,194,1,0,0,0,186,184,1,0,0,0,187,191, 96 | 3,25,12,0,188,190,3,119,59,0,189,188,1,0,0,0,190,193,1,0,0,0,191,189,1,0, 97 | 0,0,191,192,1,0,0,0,192,195,1,0,0,0,193,191,1,0,0,0,194,187,1,0,0,0,195, 98 | 196,1,0,0,0,196,194,1,0,0,0,196,197,1,0,0,0,197,22,1,0,0,0,198,200,3,119, 99 | 59,0,199,198,1,0,0,0,200,201,1,0,0,0,201,199,1,0,0,0,201,202,1,0,0,0,202, 100 | 24,1,0,0,0,203,204,7,3,0,0,204,26,1,0,0,0,205,206,7,4,0,0,206,207,7,5,0, 101 | 0,207,208,7,6,0,0,208,209,7,7,0,0,209,210,7,8,0,0,210,28,1,0,0,0,211,212, 102 | 7,9,0,0,212,213,7,1,0,0,213,214,7,10,0,0,214,30,1,0,0,0,215,216,7,9,0,0, 103 | 216,217,7,10,0,0,217,218,7,1,0,0,218,219,7,11,0,0,219,32,1,0,0,0,220,221, 104 | 7,6,0,0,221,222,7,0,0,0,222,34,1,0,0,0,223,224,7,2,0,0,224,225,7,1,0,0,225, 105 | 36,1,0,0,0,226,227,7,12,0,0,227,228,7,13,0,0,228,38,1,0,0,0,229,230,7,7, 106 | 0,0,230,231,7,1,0,0,231,232,7,1,0,0,232,233,7,14,0,0,233,40,1,0,0,0,234, 107 | 235,7,6,0,0,235,236,7,9,0,0,236,42,1,0,0,0,237,238,7,2,0,0,238,239,7,5,0, 108 | 0,239,240,7,8,0,0,240,241,7,0,0,0,241,44,1,0,0,0,242,243,7,8,0,0,243,244, 109 | 7,7,0,0,244,245,7,15,0,0,245,246,7,8,0,0,246,46,1,0,0,0,247,248,7,9,0,0, 110 | 248,249,7,16,0,0,249,250,7,0,0,0,250,251,7,17,0,0,251,252,7,2,0,0,252,253, 111 | 7,6,0,0,253,254,7,1,0,0,254,255,7,0,0,0,255,48,1,0,0,0,256,257,7,8,0,0,257, 112 | 258,7,0,0,0,258,259,7,18,0,0,259,50,1,0,0,0,260,261,7,10,0,0,261,262,7,8, 113 | 0,0,262,263,7,2,0,0,263,264,7,16,0,0,264,265,7,10,0,0,265,266,7,0,0,0,266, 114 | 52,1,0,0,0,267,268,7,0,0,0,268,269,7,8,0,0,269,270,7,4,0,0,270,54,1,0,0, 115 | 0,271,272,7,2,0,0,272,273,7,10,0,0,273,274,7,13,0,0,274,56,1,0,0,0,275,276, 116 | 7,17,0,0,276,277,7,19,0,0,277,278,7,2,0,0,278,279,7,17,0,0,279,280,7,5,0, 117 | 0,280,58,1,0,0,0,281,282,7,2,0,0,282,283,7,5,0,0,283,284,7,10,0,0,284,285, 118 | 7,1,0,0,285,286,7,4,0,0,286,60,1,0,0,0,287,288,5,124,0,0,288,292,5,124,0, 119 | 0,289,290,7,1,0,0,290,292,7,10,0,0,291,287,1,0,0,0,291,289,1,0,0,0,292,62, 120 | 1,0,0,0,293,294,7,20,0,0,294,295,7,1,0,0,295,296,7,10,0,0,296,64,1,0,0,0, 121 | 297,298,5,38,0,0,298,303,5,38,0,0,299,300,7,19,0,0,300,301,7,0,0,0,301,303, 122 | 7,18,0,0,302,297,1,0,0,0,302,299,1,0,0,0,303,66,1,0,0,0,304,308,5,61,0,0, 123 | 305,306,5,61,0,0,306,308,5,61,0,0,307,304,1,0,0,0,307,305,1,0,0,0,308,68, 124 | 1,0,0,0,309,310,5,33,0,0,310,314,5,61,0,0,311,312,5,60,0,0,312,314,5,62, 125 | 0,0,313,309,1,0,0,0,313,311,1,0,0,0,314,70,1,0,0,0,315,316,5,60,0,0,316, 126 | 72,1,0,0,0,317,318,5,60,0,0,318,319,5,61,0,0,319,74,1,0,0,0,320,321,5,62, 127 | 0,0,321,76,1,0,0,0,322,323,5,62,0,0,323,324,5,61,0,0,324,78,1,0,0,0,325, 128 | 326,5,43,0,0,326,80,1,0,0,0,327,328,5,45,0,0,328,82,1,0,0,0,329,330,5,42, 129 | 0,0,330,84,1,0,0,0,331,332,5,47,0,0,332,86,1,0,0,0,333,338,5,37,0,0,334, 130 | 335,7,11,0,0,335,336,7,1,0,0,336,338,7,18,0,0,337,333,1,0,0,0,337,334,1, 131 | 0,0,0,338,88,1,0,0,0,339,340,5,94,0,0,340,90,1,0,0,0,341,345,5,171,0,0,342, 132 | 343,5,60,0,0,343,345,5,60,0,0,344,341,1,0,0,0,344,342,1,0,0,0,345,92,1,0, 133 | 0,0,346,350,5,187,0,0,347,348,5,62,0,0,348,350,5,62,0,0,349,346,1,0,0,0, 134 | 349,347,1,0,0,0,350,94,1,0,0,0,351,352,5,123,0,0,352,96,1,0,0,0,353,354, 135 | 5,125,0,0,354,98,1,0,0,0,355,357,2,48,57,0,356,355,1,0,0,0,357,358,1,0,0, 136 | 0,358,356,1,0,0,0,358,359,1,0,0,0,359,370,1,0,0,0,360,362,7,8,0,0,361,363, 137 | 7,21,0,0,362,361,1,0,0,0,362,363,1,0,0,0,363,367,1,0,0,0,364,366,2,48,57, 138 | 0,365,364,1,0,0,0,366,369,1,0,0,0,367,365,1,0,0,0,367,368,1,0,0,0,368,371, 139 | 1,0,0,0,369,367,1,0,0,0,370,360,1,0,0,0,370,371,1,0,0,0,371,100,1,0,0,0, 140 | 372,374,2,48,57,0,373,372,1,0,0,0,374,377,1,0,0,0,375,373,1,0,0,0,375,376, 141 | 1,0,0,0,376,378,1,0,0,0,377,375,1,0,0,0,378,380,5,46,0,0,379,381,2,48,57, 142 | 0,380,379,1,0,0,0,381,382,1,0,0,0,382,380,1,0,0,0,382,383,1,0,0,0,383,391, 143 | 1,0,0,0,384,386,2,48,57,0,385,384,1,0,0,0,386,387,1,0,0,0,387,385,1,0,0, 144 | 0,387,388,1,0,0,0,388,389,1,0,0,0,389,391,5,46,0,0,390,375,1,0,0,0,390,385, 145 | 1,0,0,0,391,402,1,0,0,0,392,394,7,8,0,0,393,395,7,21,0,0,394,393,1,0,0,0, 146 | 394,395,1,0,0,0,395,399,1,0,0,0,396,398,2,48,57,0,397,396,1,0,0,0,398,401, 147 | 1,0,0,0,399,397,1,0,0,0,399,400,1,0,0,0,400,403,1,0,0,0,401,399,1,0,0,0, 148 | 402,392,1,0,0,0,402,403,1,0,0,0,403,102,1,0,0,0,404,405,7,2,0,0,405,406, 149 | 7,10,0,0,406,407,7,16,0,0,407,414,7,8,0,0,408,409,7,9,0,0,409,410,7,19,0, 150 | 0,410,411,7,7,0,0,411,412,7,15,0,0,412,414,7,8,0,0,413,404,1,0,0,0,413,408, 151 | 1,0,0,0,414,104,1,0,0,0,415,416,7,14,0,0,416,417,7,8,0,0,417,418,7,10,0, 152 | 0,418,106,1,0,0,0,419,420,7,15,0,0,420,421,7,22,0,0,421,422,7,16,0,0,422, 153 | 423,7,19,0,0,423,424,7,10,0,0,424,425,7,8,0,0,425,426,7,18,0,0,426,108,1, 154 | 0,0,0,427,428,7,17,0,0,428,429,7,16,0,0,429,430,7,12,0,0,430,431,7,8,0,0, 155 | 431,432,7,18,0,0,432,110,1,0,0,0,433,437,7,23,0,0,434,436,7,24,0,0,435,434, 156 | 1,0,0,0,436,439,1,0,0,0,437,435,1,0,0,0,437,438,1,0,0,0,438,112,1,0,0,0, 157 | 439,437,1,0,0,0,440,442,3,115,57,0,441,443,8,25,0,0,442,441,1,0,0,0,443, 158 | 444,1,0,0,0,444,445,1,0,0,0,444,442,1,0,0,0,445,446,1,0,0,0,446,447,3,117, 159 | 58,0,447,459,1,0,0,0,448,449,3,115,57,0,449,451,3,115,57,0,450,452,8,25, 160 | 0,0,451,450,1,0,0,0,452,453,1,0,0,0,453,454,1,0,0,0,453,451,1,0,0,0,454, 161 | 455,1,0,0,0,455,456,3,117,58,0,456,457,3,117,58,0,457,459,1,0,0,0,458,440, 162 | 1,0,0,0,458,448,1,0,0,0,459,114,1,0,0,0,460,461,5,91,0,0,461,116,1,0,0,0, 163 | 462,463,5,93,0,0,463,118,1,0,0,0,464,465,7,26,0,0,465,120,1,0,0,0,466,470, 164 | 5,39,0,0,467,469,9,0,0,0,468,467,1,0,0,0,469,472,1,0,0,0,470,471,1,0,0,0, 165 | 470,468,1,0,0,0,471,473,1,0,0,0,472,470,1,0,0,0,473,485,5,39,0,0,474,480, 166 | 5,34,0,0,475,476,5,92,0,0,476,479,5,34,0,0,477,479,8,27,0,0,478,475,1,0, 167 | 0,0,478,477,1,0,0,0,479,482,1,0,0,0,480,478,1,0,0,0,480,481,1,0,0,0,481, 168 | 483,1,0,0,0,482,480,1,0,0,0,483,485,5,34,0,0,484,466,1,0,0,0,484,474,1,0, 169 | 0,0,485,122,1,0,0,0,39,0,143,151,158,163,168,173,177,184,191,196,201,291, 170 | 302,307,313,337,344,349,358,362,367,370,375,382,387,390,394,399,402,413, 171 | 437,444,453,458,470,478,480,484,1,6,0,0]; 172 | 173 | 174 | const atn = new antlr4.atn.ATNDeserializer().deserialize(serializedATN); 175 | 176 | const decisionsToDFA = atn.decisionToState.map( (ds, index) => new antlr4.dfa.DFA(ds, index) ); 177 | 178 | export default class FormulaLexer extends antlr4.Lexer { 179 | 180 | static grammarFileName = "Formula.g"; 181 | static channelNames = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN" ]; 182 | static modeNames = [ "DEFAULT_MODE" ]; 183 | static literalNames = [ null, "'('", "','", "')'", "'<-'", "':'", "'!'", 184 | "'not'", "'.'", null, null, null, null, null, "'while'", 185 | "'for'", "'from'", "'in'", "'to'", "'by'", "'loop'", 186 | "'if'", "'then'", "'else'", "'function'", "'end'", 187 | "'return'", "'new'", "'try'", "'catch'", "'throw'", 188 | null, "'xor'", null, null, null, "'<'", "'<='", 189 | "'>'", "'>='", "'+'", "'-'", "'*'", "'/'", null, 190 | "'^'", null, null, "'{'", "'}'", null, null, null, 191 | "'per'", "'squared'", "'cubed'", null, null, "'['", 192 | "']'" ]; 193 | static symbolicNames = [ null, null, null, null, null, null, null, null, 194 | null, "COMMENT", "LINE_COMMENT", "R__", "R_", 195 | "NEWLINES", "WHILESTATEMENT", "FORSTATEMENT", 196 | "FROMSTATEMENT", "INSTATEMENT", "TOSTATEMENT", 197 | "BYSTATEMENT", "LOOPSTATEMENT", "IFSTATEMENT", 198 | "THENSTATEMENT", "ELSESTATEMENT", "FUNCTIONSTATEMENT", 199 | "ENDBLOCK", "RETURNSTATEMENT", "NEWSTATEMENT", 200 | "TRYSTATEMENT", "CATCHSTATEMENT", "THROWSTATEMENT", 201 | "OR", "XOR", "AND", "EQUALS", "NOTEQUALS", "LT", 202 | "LTEQ", "GT", "GTEQ", "PLUS", "MINUS", "MULT", 203 | "DIV", "MOD", "POW", "LARR", "RARR", "LCURL", 204 | "RCURL", "INTEGER", "FLOAT", "BOOL", "PER", "SQUARED", 205 | "CUBED", "IDENT", "PRIMITIVE", "LBRACKET", "RBRACKET", 206 | "SPACE", "STRING" ]; 207 | static ruleNames = [ "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", 208 | "T__7", "COMMENT", "LINE_COMMENT", "R__", "R_", "NEWLINES", 209 | "WHILESTATEMENT", "FORSTATEMENT", "FROMSTATEMENT", 210 | "INSTATEMENT", "TOSTATEMENT", "BYSTATEMENT", "LOOPSTATEMENT", 211 | "IFSTATEMENT", "THENSTATEMENT", "ELSESTATEMENT", "FUNCTIONSTATEMENT", 212 | "ENDBLOCK", "RETURNSTATEMENT", "NEWSTATEMENT", "TRYSTATEMENT", 213 | "CATCHSTATEMENT", "THROWSTATEMENT", "OR", "XOR", "AND", 214 | "EQUALS", "NOTEQUALS", "LT", "LTEQ", "GT", "GTEQ", 215 | "PLUS", "MINUS", "MULT", "DIV", "MOD", "POW", "LARR", 216 | "RARR", "LCURL", "RCURL", "INTEGER", "FLOAT", "BOOL", 217 | "PER", "SQUARED", "CUBED", "IDENT", "PRIMITIVE", "LBRACKET", 218 | "RBRACKET", "SPACE", "STRING" ]; 219 | 220 | constructor(input) { 221 | super(input) 222 | this._interp = new antlr4.atn.LexerATNSimulator(this, atn, decisionsToDFA, new antlr4.atn.PredictionContextCache()); 223 | } 224 | } 225 | 226 | FormulaLexer.EOF = antlr4.Token.EOF; 227 | FormulaLexer.T__0 = 1; 228 | FormulaLexer.T__1 = 2; 229 | FormulaLexer.T__2 = 3; 230 | FormulaLexer.T__3 = 4; 231 | FormulaLexer.T__4 = 5; 232 | FormulaLexer.T__5 = 6; 233 | FormulaLexer.T__6 = 7; 234 | FormulaLexer.T__7 = 8; 235 | FormulaLexer.COMMENT = 9; 236 | FormulaLexer.LINE_COMMENT = 10; 237 | FormulaLexer.R__ = 11; 238 | FormulaLexer.R_ = 12; 239 | FormulaLexer.NEWLINES = 13; 240 | FormulaLexer.WHILESTATEMENT = 14; 241 | FormulaLexer.FORSTATEMENT = 15; 242 | FormulaLexer.FROMSTATEMENT = 16; 243 | FormulaLexer.INSTATEMENT = 17; 244 | FormulaLexer.TOSTATEMENT = 18; 245 | FormulaLexer.BYSTATEMENT = 19; 246 | FormulaLexer.LOOPSTATEMENT = 20; 247 | FormulaLexer.IFSTATEMENT = 21; 248 | FormulaLexer.THENSTATEMENT = 22; 249 | FormulaLexer.ELSESTATEMENT = 23; 250 | FormulaLexer.FUNCTIONSTATEMENT = 24; 251 | FormulaLexer.ENDBLOCK = 25; 252 | FormulaLexer.RETURNSTATEMENT = 26; 253 | FormulaLexer.NEWSTATEMENT = 27; 254 | FormulaLexer.TRYSTATEMENT = 28; 255 | FormulaLexer.CATCHSTATEMENT = 29; 256 | FormulaLexer.THROWSTATEMENT = 30; 257 | FormulaLexer.OR = 31; 258 | FormulaLexer.XOR = 32; 259 | FormulaLexer.AND = 33; 260 | FormulaLexer.EQUALS = 34; 261 | FormulaLexer.NOTEQUALS = 35; 262 | FormulaLexer.LT = 36; 263 | FormulaLexer.LTEQ = 37; 264 | FormulaLexer.GT = 38; 265 | FormulaLexer.GTEQ = 39; 266 | FormulaLexer.PLUS = 40; 267 | FormulaLexer.MINUS = 41; 268 | FormulaLexer.MULT = 42; 269 | FormulaLexer.DIV = 43; 270 | FormulaLexer.MOD = 44; 271 | FormulaLexer.POW = 45; 272 | FormulaLexer.LARR = 46; 273 | FormulaLexer.RARR = 47; 274 | FormulaLexer.LCURL = 48; 275 | FormulaLexer.RCURL = 49; 276 | FormulaLexer.INTEGER = 50; 277 | FormulaLexer.FLOAT = 51; 278 | FormulaLexer.BOOL = 52; 279 | FormulaLexer.PER = 53; 280 | FormulaLexer.SQUARED = 54; 281 | FormulaLexer.CUBED = 55; 282 | FormulaLexer.IDENT = 56; 283 | FormulaLexer.PRIMITIVE = 57; 284 | FormulaLexer.LBRACKET = 58; 285 | FormulaLexer.RBRACKET = 59; 286 | FormulaLexer.SPACE = 60; 287 | FormulaLexer.STRING = 61; 288 | 289 | 290 | 291 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {MaterialType|VectorType|import("./Primitives").SAgent|String|Boolean} VectorElementType 3 | */ 4 | 5 | 6 | /** 7 | * @typedef {MaterialType|VectorType} ValueType 8 | */ 9 | 10 | 11 | /** 12 | * @typedef {import("./formula/Vector").Vector} VectorType 13 | */ 14 | 15 | 16 | /** 17 | * @typedef {import("./formula/Material").Material} MaterialType 18 | */ 19 | 20 | 21 | /** 22 | * @typedef {InstanceType} GraphNode 23 | */ 24 | 25 | 26 | /** 27 | * @typedef {"Timeout"|"Probability"|"Condition"} TriggerType 28 | */ 29 | 30 | 31 | /** 32 | * @typedef {"Store"|"Conveyor"} StockTypeType 33 | */ 34 | 35 | 36 | /** 37 | * @typedef {"Flow"|"Link"|"Transition"} PrimitiveConnectorType 38 | */ 39 | 40 | 41 | /** 42 | * @typedef {"Stock"|"Text"|"Button"|"Picture"|"Converter"|"Variable"|"State"|"Action"|"Folder"|"Display"|"Agents"|"Ghost"} PrimitiveNodeType 43 | */ 44 | 45 | 46 | /** 47 | * @typedef {PrimitiveNodeType|PrimitiveConnectorType|"Setting"} PrimitiveNameType 48 | */ 49 | 50 | 51 | /** 52 | * @typedef {"None"|"Agent"} FolderTypeType 53 | */ 54 | 55 | 56 | /** 57 | * @typedef {"None"|"Custom Function"} NetworkType 58 | */ 59 | 60 | 61 | /** 62 | * @typedef {"Random"|"Network"|"Grid"|"Ellipse"|"Custom Function"} PlacementType 63 | */ -------------------------------------------------------------------------------- /test/TestUtilities.js: -------------------------------------------------------------------------------- 1 | import { Model } from "../src/api/Model.js"; 2 | 3 | 4 | export let testConfig = { 5 | globals: "" 6 | }; 7 | 8 | 9 | export function setupComplexExample() { 10 | let m = new Model(); 11 | 12 | let c = m.Converter({ 13 | name: "My Converter" 14 | }); 15 | 16 | let x = m.Stock({ 17 | name: "x" 18 | }); 19 | 20 | m.Stock({ 21 | name: "y" 22 | }); 23 | 24 | m.Stock({ 25 | name: "y" 26 | }); 27 | 28 | let ms = m.Stock({ 29 | name: "My Stock" 30 | }); 31 | 32 | m.Variable({ 33 | name: "a" 34 | }); 35 | 36 | let b = m.Variable({ 37 | name: "b" 38 | }); 39 | 40 | m.Link(x, b); 41 | 42 | m.Link(x, c); 43 | 44 | m.Flow(x, ms, { 45 | name: "My Flow" 46 | }); 47 | 48 | return m; 49 | } 50 | 51 | 52 | /** 53 | * @param {string} eqn 54 | * @param {any} out 55 | * @param {number=} decimals 56 | */ 57 | export function check(eqn, out, decimals = null) { 58 | let m = new Model(); 59 | 60 | m.timeLength = 2; 61 | 62 | m.globals = testConfig.globals; 63 | 64 | let variable = m.Variable(); 65 | 66 | variable.value = eqn; 67 | 68 | let res = m.simulate(); 69 | let x = res.value(variable); 70 | if (typeof x === "number") { 71 | if (decimals !== null) { 72 | x = +x.toFixed(decimals); 73 | } 74 | } 75 | expect(x).toStrictEqual(out); 76 | } 77 | 78 | 79 | /** 80 | * @param {string} eqn 81 | * @param {string=} errorMsg 82 | */ 83 | export function failure(eqn, errorMsg = null) { 84 | let m = new Model(); 85 | 86 | m.timeLength = 2; 87 | 88 | m.globals = testConfig.globals; 89 | 90 | 91 | let variable = m.Variable(); 92 | 93 | variable.value = eqn; 94 | 95 | if (errorMsg) { 96 | expect(() => m.simulate()).toThrow(errorMsg); 97 | } else { 98 | expect(() => m.simulate()).toThrow(); 99 | } 100 | } 101 | 102 | 103 | 104 | /** 105 | * Checks simulation equality including agent locations and states for equality. 106 | * 107 | * @param {import("../src/Simulator").ResultsType} a 108 | * @param {import("../src/Simulator").ResultsType} b 109 | */ 110 | export function areResultsDifferent(a, b) { 111 | if (a === null && b === null) { 112 | return false; 113 | } 114 | 115 | if (a.errorCode !== b.errorCode) { 116 | return `Different error code: ${a.errorCode} "${a.error ? a.error.slice(0, 60) : a.error}..." ${b.errorCode} "${b.error ? b.error.slice(0, 60) : b.error}..."`; 117 | } 118 | 119 | if (a.error !== b.error) { 120 | // generally skip this test 121 | // return `Different error text: "${a.error ? a.error.slice(0, 60) : a.error}..." "${b.error ? b.error.slice(0, 60) : b.error}..."`; 122 | } 123 | 124 | if (a.periods !== b.periods) { 125 | return `Different periods: "${a.periods}" "${b.periods}"`; 126 | } 127 | 128 | for (let i = 0; i < a.periods; i++) { 129 | let dA = a.data[i]; 130 | let dB = b.data[i]; 131 | for (let key in dA) { 132 | let oA = dA[key]; 133 | let oB = dB[key]; 134 | if (!oA.current) { 135 | if (("" + oA) !== ("" + oB)) { 136 | return `Different value at "${key}": "${oA}" "${oB}"`; 137 | } 138 | } else { 139 | for (let j = 0; j < oA.current.length; j++) { 140 | let pA = oA.current[j]; 141 | let pB = oB.current[j]; 142 | 143 | if (pA.instanceId !== pB.instanceId) { 144 | return "Different instance id"; 145 | } 146 | if ("" + pA.location.items[0] !== "" + pB.location.items[0]) { 147 | return `Different location x: "${pA.location.items[0]}" "${pB.location.items[0]}"`; 148 | } 149 | if ("" + pA.location.items[1] !== "" + pB.location.items[1]) { 150 | return `Different location y: "${pA.location.items[1]}" "${pB.location.items[1]}"`; 151 | } 152 | if (!(pA.state === null && pB.state === null)) { 153 | for (let k = 0; k < pA.state.length; k++) { 154 | let sA = pA.state[k]; 155 | let sB = pB.state[k]; 156 | if (sA.active !== sB.active) { 157 | return `Different active state: ${sA.dna.name} - ${sA.active}; ${sB.dna.name} - ${sB.active}`; 158 | } 159 | } 160 | } 161 | } 162 | } 163 | } 164 | } 165 | 166 | return false; 167 | } 168 | -------------------------------------------------------------------------------- /test/config.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/suites/distributions.test.js: -------------------------------------------------------------------------------- 1 | import { check, failure, testConfig } from "../TestUtilities.js"; 2 | 3 | 4 | test("Statistics", () => { 5 | check("expit(0)", 0.5); 6 | check("expit(100000000)", 1); 7 | check("expit(-100000000)", 0); 8 | check("logit(.5)", 0); 9 | 10 | check("Round(pdfNormal(1)*10000)", 2420); 11 | check("Round(pdfNormal(1, 3, 2)*10000)", 1210); 12 | check("Round(cdfNormal(1)*10000)", 8413); 13 | check("Round(cdfNormal(1, 3, 2)*10000)", 1587); 14 | check("Round(invNormal(.6)*10000)", 2533); 15 | check("Round(invNormal(.7, 3, 2)*10000)", 40488); 16 | failure("invNormal(2)"); 17 | failure("invNormal(-1)"); 18 | 19 | check("Round(pdfLogNormal(1)*10000)", 3989); 20 | check("Round(pdfLogNormal(1, 3, 2)*10000)", 648); 21 | check("Round(cdfLogNormal(1)*10000)", 5000); 22 | check("Round(cdfLogNormal(1, 3, 2)*10000)", 668); 23 | check("Round(invLogNormal(.6)*10000)", 12883); 24 | check("Round(invLogNormal(.7, 3, 2)*10000)", 573287); 25 | failure("invLogNormal(2)"); 26 | failure("invLogNormal(-1)"); 27 | 28 | check("Round(pdfT(1, 10)*10000)", 2304); 29 | check("Round(pdfT(2,3)*10000)", 675); 30 | check("Round(cdfT(1, 10)*10000)", 8296); 31 | check("Round(cdfT(2, 3)*10000)", 9303); 32 | check("Round(invT(.1, 10)*10000)", -13722); 33 | check("Round(invT(.9, 2)*10000)", 18856); 34 | failure("invT(2, 3)"); 35 | failure("invT(.9, -1)"); 36 | 37 | check("Round(pdfF(1, 10, 7)*10000)", 5552); 38 | check("Round(pdfF(2, 3, 8)*10000)", 1472); 39 | check("Round(cdfF(1, 10, 7)*10000)", 4834); 40 | check("Round(cdfF(2, 3, 8)*10000)", 8073); 41 | check("Round(invF(.1, 10, 7)*10000)", 4143); 42 | check("Round(invF(.9, 2, 8)*10000)", 31131); 43 | failure("invF(2, 3, 4)"); 44 | failure("invF(.9, -1, 5)"); 45 | failure("invF(.9, 1, -5)"); 46 | 47 | 48 | check("Round(pdfChiSquared(1, 10)*10000)", 8); 49 | check("Round(pdfChiSquared(2,3)*10000)", 2076); 50 | check("Round(cdfChiSquared(1, 10)*10000)", 2); 51 | check("Round(cdfChiSquared(2, 3)*10000)", 4276); 52 | check("Round(invChiSquared(.1, 10)*10000)", 48652); 53 | check("Round(invChiSquared(.9, 2)*10000)", 46052); 54 | failure("invChiSquared(2, 3)"); 55 | failure("invChiSquared(.9, -1)"); 56 | 57 | check("Round(pdfExponential(1, 10)*10000)", 5); 58 | check("Round(pdfExponential(2,3)*10000)", 74); 59 | check("Round(cdfExponential(1, 10)*10000)", 10000); 60 | check("Round(cdfExponential(2, 3)*10000)", 9975); 61 | check("Round(invExponential(.1, 10)*10000)", 105); 62 | check("Round(invExponential(.9, 2)*10000)", 11513); 63 | failure("invExponential(2, 3)"); 64 | failure("invExponential(.9, -1)"); 65 | 66 | check("Round(pmfPoisson(1, 10)*10000)", 5); 67 | check("Round(pmfPoisson(2, 3)*10000)", 2240); 68 | check("Round(cdfPoisson(1, 10)*10000)", 5); 69 | check("Round(cdfPoisson(2, 3)*10000)", 4232); 70 | failure("pmfPoisson(1, -10)"); 71 | 72 | 73 | failure("randTriangular(1,1,1)"); 74 | failure("randTriangular(1,2,-1)"); 75 | failure("randTriangular(1,2,3)"); 76 | 77 | failure("randBeta()"); 78 | failure("randBeta(1,2,3)"); 79 | 80 | failure("RandDist(1,1,1)"); 81 | failure("RandDist()"); 82 | failure("RandDist({1,2})"); 83 | failure("RandDist({ {1,2}, {1,2} })"); 84 | }); 85 | 86 | 87 | test("Random number generation", () => { 88 | testConfig.globals = "setRandSeed(12)"; 89 | 90 | check("round(mean(repeat(randNormal(), 2000))*10)", 0); 91 | check("round(mean(repeat(randNormal(0.8, .01), 1000))*100)", 80); 92 | 93 | check("round(mean(repeat(randLogNormal(0.71, .01), 1000))*100)", 71); 94 | 95 | check("round(mean(repeat(rand(), 50000))*100)", 50); 96 | check("round(mean(repeat(rand(.5, 1), 5000))*100)", 75); 97 | 98 | check("round(randBinomial(100000, .8)/100000*100)", 80); 99 | 100 | check("round(sum(repeat(ifThenElse(randBoolean(.3),1,0),2000))/200)", 3); 101 | 102 | check("round(mean(repeat(randPoisson(0.12), 10000))*100)", 12); 103 | 104 | check("round(mean(repeat(randExp(2), 10000))*100)", 50); 105 | 106 | check("round(mean(repeat(randTriangular(0, 1, .7), 20000))*10)", Math.round(1.7/3*10)); 107 | 108 | check("round(mean(repeat(randDist({{1, 2},{2, 2}}), 20000))*10)", 15); 109 | check("round(mean(repeat(randDist({ {1, 2}, {2, 2}, {2, 0}, {3, 0}, {3, 2}, {4, 2} }), 20000))*10)", 25); 110 | check("round(mean(repeat(randDist({1, 2}, {2, 2}), 20000))*10)", 15); 111 | 112 | check("round(mean(repeat(randDist({ 1, 2, 2, 3, 3, 4 }, {2, 2, 0, 0, 2, 2}), 20000))*10)", 25); 113 | check("round(mean(repeat(randDist({0, 1}, {1, 4}), 20000))*10)", 6); 114 | check("round(mean(repeat(randDist({0, 1, 2}, {1, 4, 1}), 20000))*10)", 10); 115 | 116 | check("round(mean(repeat(randBeta(1, 2), 20000))*10)", Math.round((1/(1+2/1))*10)); 117 | check("round(mean(repeat(randBeta(2, 1), 20000))*10)", Math.round((1/(1+1/2))*10)); 118 | 119 | 120 | check("round(mean(repeat(randnormal({a: 0, b:10}, {a: .1, b: .2}){\"a\"}, 10000))*10)", 0); 121 | check("round(mean(repeat(randnormal({a: 0, b:10}, .1){\"b\"}, 10000))*10)", 100); 122 | check("round(mean(repeat(randnormal(10, {a: .1, b: .2}){\"b\"}, 10000))*10)", 100); 123 | check("round(mean(repeat(randnormal({a: 0, b:10}, {a: .1, b: .2}){\"b\"}, 10000))*10)", 100); 124 | // mismatched keys 125 | failure("randnormal({a: 0, b:10}, {x: .1, y: .2})", "Vector keys do not match between parameters"); 126 | // non-named vectors 127 | failure("randnormal(1, {1, 2, 3})", "does not accepted non-named vectors"); 128 | 129 | testConfig.globals = ""; 130 | }); 131 | 132 | -------------------------------------------------------------------------------- /test/suites/folders.test.js: -------------------------------------------------------------------------------- 1 | import { Agent, Folder } from "../../src/api/Blocks.js"; 2 | import { Model } from "../../src/api/Model.js"; 3 | 4 | 5 | test("Folders", () => { 6 | let m = new Model(); 7 | 8 | let s = m.Stock(); 9 | let f = m.Folder(); 10 | 11 | expect(s.parent).toBeNull(); 12 | s.parent = f; 13 | expect(s.parent.id).toBe(f.id); 14 | 15 | s.parent = null; 16 | expect(s.parent).toBeNull(); 17 | 18 | expect(f instanceof Folder).toBe(true); 19 | 20 | 21 | let a = m.Agent(); 22 | expect(a instanceof Agent).toBe(true); 23 | 24 | a.agentParent = "abc"; 25 | expect(a.agentParent).toBe("abc"); 26 | }); -------------------------------------------------------------------------------- /test/suites/import.test.js: -------------------------------------------------------------------------------- 1 | import { loadInsightMaker } from "../../src/api/Model.js"; 2 | 3 | 4 | test("Extra node with id", () => { 5 | let m = loadInsightMaker(` 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | `); 19 | 20 | m.simulate(); // no error 21 | }); -------------------------------------------------------------------------------- /test/suites/macros.test.js: -------------------------------------------------------------------------------- 1 | import { Model } from "../../src/api/Model.js"; 2 | 3 | 4 | test("Macros", () => { 5 | let m = new Model(); 6 | 7 | let mString = `a <- 52 8 | f(x)<-x^2`; 9 | 10 | m.globals = mString; 11 | expect(m.globals).toBe(mString); 12 | 13 | 14 | let p1 = m.Variable({ 15 | name: "p1" 16 | }); 17 | let p2 = m.Variable({ 18 | name: "p2" 19 | }); 20 | p1.value = "a"; 21 | p2.value = "f(2)"; 22 | let res = m.simulate(); 23 | expect(res.series(p1)[3]).toBe(52); 24 | expect(res.series(p2)[3]).toBe(4); 25 | }); 26 | 27 | 28 | test("Macros time settings", () => { 29 | let m = new Model(); 30 | 31 | let mString = "x <- removeUnits(timeStep, \"years\")"; 32 | 33 | m.globals = mString; 34 | expect(m.globals).toBe(mString); 35 | 36 | 37 | let p1 = m.Variable({ 38 | name: "p1" 39 | }); 40 | p1.value = "x"; 41 | let res = m.simulate(); 42 | expect(res.series(p1)[3]).toBe(1); 43 | }); 44 | 45 | 46 | test("Macros error", () => { 47 | let m = new Model(); 48 | 49 | m.globals = "x <- 1/"; 50 | 51 | expect(() => m.simulate()).toThrow("error with the macros"); 52 | }); 53 | -------------------------------------------------------------------------------- /test/suites/misc.test.js: -------------------------------------------------------------------------------- 1 | import { Stock, Variable } from "../../src/api/Blocks.js"; 2 | import { Model } from "../../src/api/Model.js"; 3 | import { areResultsDifferent, setupComplexExample } from "../TestUtilities.js"; 4 | 5 | 6 | test("find()", () => { 7 | let m = setupComplexExample(); 8 | 9 | expect(m.findStocks()).toHaveLength(4); 10 | expect(m.find(item => item.note === "foo")).toHaveLength(0); 11 | m.findStocks().forEach(s => s.note = "foo"); 12 | expect(m.find(item => item.note === "foo")).toHaveLength(4); 13 | 14 | expect(m.find(item => item instanceof Stock || item instanceof Variable)).toHaveLength(6); 15 | 16 | expect(m.find()).toHaveLength(10); 17 | 18 | expect(m.find(s => s.name === "fdfsdgfg")).toHaveLength(0); 19 | expect(m.find(s => s.name === "y")).toHaveLength(2); 20 | expect(m.find(s => ["x", "y", "fvdf"].includes(s.name))).toHaveLength(3); 21 | 22 | 23 | let id = m.get(x => x.name === "x").id; 24 | expect(m.getId(id)).not.toBeNull(); 25 | expect(m.getId("gfdgdfg")).toBeNull(); 26 | }); 27 | 28 | 29 | test("Simulation equality", () => { 30 | let m = new Model(); 31 | 32 | let s = m.Stock({ 33 | name: "x" 34 | }); 35 | let f = m.Flow(null, s, { 36 | name: "f" 37 | }); 38 | f.rate = "[x]*.01"; 39 | s.initial = 100; 40 | 41 | let res1 = m.simulate(); 42 | let res2 = m.simulate(); 43 | 44 | s.initial = 120; 45 | let res3 = m.simulate(); 46 | 47 | expect(areResultsDifferent(res1._data, res2._data)).toBe(false); 48 | expect(areResultsDifferent(res1._data, res3._data)).toBeTruthy(); 49 | }); 50 | 51 | 52 | test("Simulation equality - vectors", () => { 53 | let m = new Model(); 54 | 55 | let s = m.Stock({ 56 | name: "x" 57 | }); 58 | let f = m.Flow(null, s, { 59 | name: "f" 60 | }); 61 | f.rate = "{1, 1}"; 62 | s.initial = "{1, 2}"; 63 | 64 | let res1 = m.simulate(); 65 | let res2 = m.simulate(); 66 | 67 | s.initial = "{2,2}"; 68 | let res3 = m.simulate(); 69 | 70 | expect(areResultsDifferent(res1._data, res2._data)).toBe(false); 71 | expect(areResultsDifferent(res1._data, res3._data)).toBeTruthy(); 72 | }); 73 | 74 | 75 | it("ConverterTable", () => { 76 | let m = new Model(); 77 | 78 | let c = m.Converter({ 79 | name: "Conv", 80 | values: [{x: 1, y: 2}, {x: 2, y: 3}, {x: 3, y: 9}] 81 | }); 82 | 83 | let v = m.Variable({ 84 | name: "v", 85 | value: "ConverterTable([Conv])" 86 | }); 87 | 88 | m.Link(c, v); 89 | 90 | let res = m.simulate(); 91 | 92 | expect(res.value(v)).toEqual([{x: 1, y: 2}, {x: 2, y: 3}, {x: 3, y: 9}]); 93 | 94 | 95 | v.value = "ConverterTable(1)"; 96 | 97 | expect(() => m.simulate()).toThrow("requires a primitive for the parameter"); 98 | 99 | let v2 = m.Variable({ 100 | name: "v2", 101 | value: "1" 102 | }); 103 | 104 | m.Link(v2, v); 105 | 106 | v.value = "ConverterTable([v2])"; 107 | 108 | expect(() => m.simulate()).toThrow("requires a Converter primitive as its parameter"); 109 | }); -------------------------------------------------------------------------------- /test/suites/primitive_get_set.test.js: -------------------------------------------------------------------------------- 1 | import { setupComplexExample } from "../TestUtilities.js"; 2 | 3 | 4 | test("Primitive Get and Set", () => { 5 | let m = setupComplexExample(); 6 | 7 | let x = m.getStock(x => x.name === "x"); 8 | x.name = "xyz"; 9 | expect(x.name).toBe("xyz"); 10 | 11 | x.note = "abc"; 12 | expect(x.note).toBe("abc"); 13 | 14 | x.initial = "123"; 15 | expect(x.initial).toBe("123"); 16 | 17 | let fl = m.getFlow(x => x.name === "My Flow"); 18 | fl.rate = "098"; 19 | expect(fl.rate).toBe("098"); 20 | 21 | fl.units = "Qubits"; 22 | expect(fl.units).toBe("Qubits"); 23 | 24 | fl.constraints = { max: 10, min: -10 }; 25 | expect(fl.constraints).toEqual({ max: 10, min: -10 }); 26 | 27 | fl.nonNegative = true; 28 | expect(fl.nonNegative).toBe(true); 29 | fl.nonNegative = false; 30 | expect(fl.nonNegative).toBe(false); 31 | 32 | let s = m.getStock(x => x.name === "My Stock"); 33 | 34 | s.nonNegative = true; 35 | expect(s.nonNegative).toBe(true); 36 | s.nonNegative = false; 37 | expect(s.nonNegative).toBe(false); 38 | 39 | s.delay = 5; 40 | expect(s.delay).toBe("5"); 41 | 42 | s.type = "Conveyor"; 43 | expect(s.type).toBe("Conveyor"); 44 | s.type = "Store"; 45 | expect(s.type).toBe("Store"); 46 | 47 | 48 | let c = m.getConverter(x => x.name === "My Converter"); 49 | c.values = [{x: 1, y: 2}]; 50 | expect(c.values).toEqual([{x: 1, y: 2}]); 51 | c.input = s; 52 | expect(c.input).toBe(s); 53 | c.input = "Time"; 54 | expect(c.input).toBe("Time"); 55 | c.interpolation = "Linear"; 56 | expect(c.interpolation).toBe("Linear"); 57 | 58 | let state = m.State({ 59 | name: "My State" 60 | }); 61 | state.startActive = "abc"; 62 | expect(state.startActive).toBe("abc"); 63 | 64 | expect(state.residency).toBe("0"); 65 | state.residency = "xyz"; 66 | expect(state.residency).toBe("xyz"); 67 | 68 | let transition = m.Transition(state, null, { 69 | name: "My Transition" 70 | }); 71 | transition.value = "abc1"; 72 | expect(transition.value).toBe("abc1"); 73 | 74 | expect(transition.trigger).toBe("Timeout"); 75 | transition.trigger = "Condition"; 76 | expect(transition.trigger).toBe("Condition"); 77 | expect(transition.repeat).toBe(false); 78 | transition.repeat = true; 79 | expect(transition.repeat).toBe(true); 80 | expect(transition.recalculate).toBe(false); 81 | transition.recalculate = true; 82 | expect(transition.recalculate).toBe(true); 83 | 84 | let action = m.Action({ 85 | name: "My Action" 86 | }); 87 | 88 | action.action = "abc1"; 89 | expect(action.action).toBe("abc1"); 90 | 91 | action.value = "abc123"; 92 | expect(action.value).toBe("abc123"); 93 | 94 | expect(action.trigger).toBe("Probability"); 95 | action.trigger = "Timeout"; 96 | expect(action.trigger).toBe("Timeout"); 97 | 98 | expect(action.repeat).toBe(true); 99 | action.repeat = false; 100 | expect(action.repeat).toBe(false); 101 | 102 | expect(action.recalculate).toBe(false); 103 | action.recalculate = true; 104 | expect(action.recalculate).toBe(true); 105 | 106 | 107 | let agents = m.Population({ 108 | name: "My Agents" 109 | }); 110 | agents.populationSize = 123; 111 | expect(agents.populationSize).toBe(123); 112 | 113 | agents.geoUnits = "foo bar"; 114 | expect(agents.geoUnits).toBe("foo bar"); 115 | 116 | agents.geoHeight = "foo bar 1"; 117 | expect(agents.geoHeight).toBe("foo bar 1"); 118 | 119 | agents.geoWidth = "foo bar 2"; 120 | expect(agents.geoWidth).toBe("foo bar 2"); 121 | 122 | // @ts-ignore 123 | agents.geoPlacementType = "foo bar 3"; 124 | expect(agents.geoPlacementType).toBe("foo bar 3"); 125 | 126 | agents.geoPlacementFunction = "foo bar 4"; 127 | expect(agents.geoPlacementFunction).toBe("foo bar 4"); 128 | 129 | // @ts-ignore 130 | agents.networkType = "foo bar 5"; 131 | expect(agents.networkType).toBe("foo bar 5"); 132 | 133 | agents.networkFunction = "foo bar 6"; 134 | expect(agents.networkFunction).toBe("foo bar 6"); 135 | 136 | agents.geoWrapAround = true; 137 | expect(agents.geoWrapAround).toBe(true); 138 | agents.geoWrapAround = false; 139 | expect(agents.geoWrapAround).toBe(false); 140 | 141 | let f = m.Agent({ 142 | name: "f" 143 | }); 144 | m.Link(f, agents, { 145 | name: "Link" 146 | }); 147 | agents.agentBase = f; 148 | expect(agents.agentBase).toBe(f); 149 | }); 150 | 151 | -------------------------------------------------------------------------------- /test/suites/results_formatting.test.js: -------------------------------------------------------------------------------- 1 | import { Model } from "../../src/api/Model.js"; 2 | 3 | 4 | test("Results formatting", () => { 5 | let m = new Model(); 6 | let x = m.Variable({ 7 | name: "Test Variable" 8 | }); 9 | 10 | // string value 11 | x.value = "'abc'"; 12 | let res = m.simulate(); 13 | expect(res.series(x)[0]).toBe("abc"); 14 | 15 | // boolean value 16 | x.value = "true"; 17 | res = m.simulate(); 18 | expect(res.series(x)[0]).toBe(1); 19 | 20 | x.value = "false"; 21 | res = m.simulate(); 22 | expect(res.series(x)[0]).toBe(0); 23 | 24 | // vector boolean 25 | x.value = "{true, false}"; 26 | res = m.simulate(); 27 | expect(JSON.stringify(res.series(x)[0])).toBe("[true,false]"); 28 | 29 | // vector num 30 | x.value = "{1, 2}"; 31 | res = m.simulate(); 32 | expect(JSON.stringify(res.series(x)[0])).toBe("[1,2]"); 33 | 34 | // named vector boolean 35 | x.value = "{a: true, b: false}"; 36 | res = m.simulate(); 37 | expect(JSON.stringify(res.series(x)[0])).toBe("{\"a\":true,\"b\":false}"); 38 | 39 | // named vector num 40 | x.value = "{a: 1, b: 0}"; 41 | res = m.simulate(); 42 | expect(JSON.stringify(res.series(x)[0])).toBe("{\"a\":1,\"b\":0}"); 43 | 44 | // named vector string 45 | x.value = "{a: 'a', b: 'b'}"; 46 | res = m.simulate(); 47 | expect(JSON.stringify(res.series(x)[0])).toBe("{\"a\":\"a\",\"b\":\"b\"}"); 48 | }); -------------------------------------------------------------------------------- /test/suites/simulate_async.test.js: -------------------------------------------------------------------------------- 1 | import { Model } from "../../src/api/Model.js"; 2 | import { ModelError } from "../../src/formula/ModelError.js"; 3 | 4 | 5 | 6 | describe.each([ 7 | ["Euler"], ["RK4"] 8 | ])("Simulation async %s", 9 | 10 | /** 11 | * @param {"Euler"|"RK4"} algorithm 12 | */ 13 | (algorithm) => { 14 | 15 | test("General", async () => { 16 | let m = new Model({ algorithm }); 17 | 18 | m.timeUnits = "Years"; 19 | m.timeStep = 1; 20 | m.timeStart = 0; 21 | m.timeLength = 10; 22 | 23 | let s = m.Stock({ 24 | name: "My Stock" 25 | }); 26 | let f = m.Flow(s, null, { 27 | name: "My Flow" 28 | }); 29 | s.initial = "100"; 30 | f.rate = "0.1*[Alpha]"; 31 | 32 | let res = await m.simulateAsync(); 33 | let times = res.times(); 34 | expect(times.length).toBe(11); 35 | expect(times[0]).toBe(0); 36 | expect(times[10]).toBe(10); 37 | expect(res.series(s)[0]).toBe(100); 38 | 39 | expect(res.timeUnits).toBe("years"); 40 | 41 | }); 42 | 43 | 44 | test("Error", () => { 45 | let m = new Model({ algorithm }); 46 | 47 | m.timeUnits = "Years"; 48 | m.timeStep = 1; 49 | m.timeStart = 0; 50 | m.timeLength = 10; 51 | 52 | let s = m.Stock({ 53 | name: "My Stock", 54 | initial: "1 + true" 55 | }); 56 | 57 | expect(m.simulateAsync()).rejects.toEqual({ 58 | error: "Cannot convert Booleans to Numbers.", 59 | errorCode: 1089, 60 | errorPrimitive: s 61 | }); 62 | 63 | }); 64 | 65 | test("Pause with setValue", async () => { 66 | let m = new Model({ algorithm }); 67 | 68 | m.timeUnits = "Years"; 69 | m.timeStep = 1; 70 | m.timeStart = 0; 71 | m.timeLength = 10; 72 | m.timePause = 1; 73 | 74 | let v = m.Variable({ 75 | name: "My var", 76 | value: "100" 77 | }); 78 | 79 | let val = 100; 80 | 81 | let res = await m.simulateAsync({ 82 | onPause: (items) => { 83 | val = val + 1; 84 | items.setValue(v, val); 85 | } 86 | }); 87 | 88 | let times = res.times(); 89 | expect(times.length).toBe(11); 90 | expect(times[0]).toBe(0); 91 | expect(times[10]).toBe(10); 92 | expect(res.series(v)[0]).toBe(100); 93 | expect(res.series(v).at(-1)).toBe(109); 94 | 95 | expect(res.timeUnits).toBe("years"); 96 | }); 97 | 98 | 99 | test("Pause with setValue 2", async () => { 100 | let m = new Model({ algorithm }); 101 | 102 | m.timeUnits = "Years"; 103 | m.timeStep = 1; 104 | m.timeStart = 0; 105 | m.timeLength = 10; 106 | m.timePause = 1; 107 | 108 | let v = m.Variable({ 109 | name: "Val", 110 | value: "0" 111 | }); 112 | 113 | let a = m.Action({ 114 | name: "Increaser", 115 | trigger: "Condition", 116 | action: "[Stock] <- [Stock] + [Val]", 117 | value: "[Val]" 118 | }); 119 | 120 | m.Link(v, a); 121 | 122 | let s = m.Stock({ 123 | name: "Stock", 124 | initial: "0" 125 | }); 126 | 127 | m.Link(s, a); 128 | 129 | 130 | let res = await m.simulateAsync({ 131 | onPause: (items) => { 132 | if (items.time === 3) { 133 | items.setValue(v, 1); 134 | } else if (items.time === 6) { 135 | items.setValue(v, 0); 136 | } 137 | } 138 | }); 139 | 140 | expect(res.series(s)[0]).toBe(0); 141 | expect(res.series(s)[1]).toBe(0); 142 | 143 | expect(res.series(s).at(-2)).toBe(3); 144 | expect(res.series(s).at(-1)).toBe(3); 145 | 146 | }); 147 | 148 | test("Pause with invalid setValue rejects", async () => { 149 | let m = new Model({ algorithm }); 150 | 151 | m.timeUnits = "Years"; 152 | m.timeStep = 1; 153 | m.timeStart = 0; 154 | m.timeLength = 10; 155 | m.timePause = 1; 156 | 157 | let v = m.Variable({ 158 | name: "My var", 159 | value: "100" 160 | }); 161 | 162 | // Delete the variable so it's not in the model 163 | v.delete(); 164 | 165 | let val = 100; 166 | 167 | await expect(m.simulateAsync({ 168 | onPause: (items) => { 169 | val = val + 1; 170 | items.setValue(v, val); 171 | } 172 | })).rejects.toEqual(new ModelError("Could not find the primitive to update with setValue().", { code: 8002 })); 173 | }); 174 | 175 | }); -------------------------------------------------------------------------------- /test/suites/simulation_errors.test.js: -------------------------------------------------------------------------------- 1 | import { Model } from "../../src/api/Model.js"; 2 | 3 | 4 | test("Connector ends are correct", () => { 5 | let m = new Model(); 6 | 7 | // Transition must connect to a state 8 | 9 | let s = m.Stock({ 10 | name: "Stock" 11 | }); 12 | // @ts-ignore 13 | m.Transition(null, s, { 14 | name: "Transition" 15 | }); 16 | 17 | expect(() => m.simulate()).toThrow(/must be a state/); 18 | 19 | m = new Model(); 20 | 21 | let v = m.Variable({ 22 | name: "Variable" 23 | }); 24 | // @ts-ignore 25 | m.Transition(v, null, { 26 | name: "Transition" 27 | }); 28 | 29 | expect(() => m.simulate()).toThrow(/must be a state/); 30 | 31 | m = new Model(); 32 | 33 | // Flow must connect to a stock 34 | 35 | let st = m.State({ 36 | name: "State" 37 | }); 38 | // @ts-ignore 39 | m.Flow(null, st, { 40 | name: "Flow" 41 | }); 42 | 43 | expect(() => m.simulate()).toThrow(/must be a stock/); 44 | 45 | m = new Model(); 46 | 47 | v = m.Variable({ 48 | name: "Variable" 49 | }); 50 | // @ts-ignore 51 | m.Flow(v, null, { 52 | name: "Flow" 53 | }); 54 | 55 | expect(() => m.simulate()).toThrow(/must be a stock/); 56 | }); 57 | 58 | 59 | test("Transition timeout cannot be negative", () => { 60 | let m = new Model(); 61 | 62 | let s = m.State({ 63 | name: "State" 64 | }); 65 | let t = m.Transition(null, s, { 66 | name: "Transition" 67 | }); 68 | 69 | t.trigger = "Timeout"; 70 | 71 | 72 | t.value = "1"; 73 | m.simulate(); // no error 74 | 75 | 76 | t.value = "-1"; 77 | expect(() => m.simulate()).toThrow(/cannot be less/); 78 | }); 79 | 80 | 81 | test("Invalid simulation time step", () => { 82 | let m = new Model(); 83 | 84 | m.State({ 85 | name: "State" 86 | }); 87 | 88 | m.simulate(); // no error 89 | 90 | 91 | // @ts-ignore 92 | m.timeStep = "-1"; 93 | expect(() => m.simulate()).toThrow(/time step must/); 94 | 95 | 96 | // @ts-ignore 97 | m.timeStep = "abc"; 98 | expect(() => m.simulate()).toThrow(/time step must/); 99 | }); 100 | 101 | 102 | test("Invalid simulation time length", () => { 103 | let m = new Model(); 104 | 105 | m.State({ 106 | name: "State" 107 | }); 108 | 109 | m.simulate(); // mo error 110 | 111 | // @ts-ignore 112 | m.timeLength = "-1"; 113 | expect(() => m.simulate()).toThrow(/time length must/); 114 | 115 | // @ts-ignore 116 | m.timeLength = "abc"; 117 | expect(() => m.simulate()).toThrow(/time length must/); 118 | }); 119 | 120 | 121 | test("Time pause can't be smaller than time step", () => { 122 | let m = new Model({ 123 | timeStep: 10, 124 | timePause: 1 125 | }); 126 | 127 | m.Stock(); 128 | 129 | expect(() => m.simulate()).toThrow(/Time pause cannot be smaller/); 130 | }); 131 | 132 | 133 | test("Agent population cannot be within an agent folder", () => { 134 | let m = new Model(); 135 | 136 | let pop = m.Population({ 137 | name: "Population", 138 | populationSize: 10 139 | }); 140 | 141 | let f = m.Agent({ 142 | name: "My Agent" 143 | }); 144 | pop.parent = f; 145 | 146 | pop.agentBase = f; 147 | 148 | expect(() => m.simulate()).toThrow(/placed within/); 149 | }); 150 | 151 | 152 | test("Agent location initialization", () => { 153 | let m = new Model(); 154 | 155 | let pop = m.Population({ 156 | name: "Population", 157 | populationSize: 10 158 | }); 159 | 160 | let f = m.Agent({ 161 | name: "My Agent" 162 | }); 163 | let s = m.State({ 164 | name: "State" 165 | }); 166 | s.parent = f; 167 | 168 | m.Link(pop, s, { 169 | name: "Link" 170 | }); 171 | 172 | s.startActive = "[Population].findNearest(self)"; 173 | 174 | pop.agentBase = f; 175 | 176 | expect(() => m.simulate()).toThrow(/Location not initialized/); 177 | }); 178 | 179 | 180 | test("Flow units must include time", () => { 181 | let m = new Model(); 182 | 183 | let s = m.Stock({ 184 | name: "Stock" 185 | }); 186 | let f = m.Flow(s, null, { 187 | name: "Flow" 188 | }); 189 | 190 | f.units = "widgets"; 191 | expect(() => m.simulate()).toThrow(/Incompatible units for flow/); 192 | 193 | s.units = "widgets"; 194 | expect(() => m.simulate()).toThrow(/Incompatible units for flow/); 195 | }); 196 | 197 | 198 | test("Blank values are evaluated as 0", () => { 199 | let m = new Model(); 200 | 201 | let v = m.Variable({ 202 | name: "Variable" 203 | }); 204 | v.value = ""; 205 | 206 | let res = m.simulate(); 207 | expect(res.value(v)).toBe(0); 208 | 209 | 210 | let s = m.Stock({ 211 | name: "Stock" 212 | }); 213 | s.initial = ""; 214 | 215 | res = m.simulate(); 216 | expect(res.value(s)).toBe(0); 217 | }); 218 | 219 | 220 | test("Function calling method and errors", () => { 221 | let m = new Model(); 222 | let v = m.Variable({ 223 | name: "x", 224 | value: "years()" 225 | }); 226 | 227 | let vO = m.Variable({ 228 | name: "object", 229 | value: "[x].pastMean()" 230 | }); 231 | 232 | 233 | let vP = m.Variable({ 234 | name: "parameter", 235 | value: "pastMean([x])" 236 | }); 237 | 238 | m.Link(v, vO); 239 | m.Link(v, vP); 240 | 241 | m.simulate(); // no error 242 | 243 | // error with object version 244 | vO.value = "[x].pastMean(1, 2)"; 245 | expect(() => m.simulate()).toThrow(/PastMean\(Past/); 246 | 247 | 248 | // error with parameterized version 249 | vO.value = "[x].pastMean()"; 250 | vP.value = "pastMean([x], 1, 2)"; 251 | expect(() => m.simulate()).toThrow(/PastMean\(\[Primitive/); 252 | }); 253 | 254 | 255 | 256 | test("Vector type matching", () => { 257 | let m = new Model(); 258 | let x = m.Variable({ 259 | name: "x", 260 | value: "{1, 2}" 261 | }); 262 | 263 | let y = m.Variable({ 264 | name: "y", 265 | value: "Delay([x], {a: 2, b: 3})" 266 | }); 267 | 268 | m.Link(x, y); 269 | 270 | expect(() => m.simulate()).toThrow(/Vector keys do not match between/); 271 | 272 | y.value = "Delay([x], {a:1, b:2}, {1, 1})"; 273 | expect(() => m.simulate()).toThrow(/Vector keys do not match between/); 274 | }); 275 | 276 | 277 | test("Function calling evaluated paramters and checks types", () => { 278 | let m = new Model(); 279 | let v = m.Variable({ 280 | name: "x", 281 | value: "years()" 282 | }); 283 | 284 | let vD = m.Variable({ 285 | name: "delay", 286 | value: "10" 287 | }); 288 | 289 | 290 | let vP = m.Variable({ 291 | name: "parameter", 292 | value: "pastMean([x], [delay])" 293 | }); 294 | 295 | m.Link(vD, vP); 296 | m.Link(v, vP); 297 | 298 | m.simulate(); // no error 299 | 300 | // error with string 301 | vD.value = "\"foo\""; 302 | expect(() => m.simulate()).toThrow(/does not accept string values/); 303 | 304 | 305 | // success with function 306 | vP.value = ` 307 | function df() 308 | return 10 309 | end function 310 | pastMean([x], df) 311 | `; 312 | m.simulate(); // no error 313 | 314 | 315 | // error with function 316 | vP.value = ` 317 | function df() 318 | return "foo" 319 | end function 320 | pastMean([x], df) 321 | `; 322 | expect(() => m.simulate()).toThrow(/does not accept string values/); 323 | }); 324 | 325 | 326 | test("Cannot use state functions outside primitive", () => { 327 | let m = new Model(); 328 | m.Variable({ 329 | value: "x()" 330 | }); 331 | 332 | 333 | m.globals = ` 334 | Function x() 335 | smooth(1, 10) 336 | End Function 337 | `; 338 | expect(() => m.simulate()).toThrow(/Smooth\(\) may only/); 339 | 340 | 341 | m.globals = ` 342 | Function x() 343 | delay1(1, 10) 344 | End Function 345 | `; 346 | expect(() => m.simulate()).toThrow(/Delay1\(\) may only/); 347 | 348 | 349 | m.globals = ` 350 | Function x() 351 | delay3(1, 10) 352 | End Function 353 | `; 354 | expect(() => m.simulate()).toThrow(/Delay3\(\) may only/); 355 | }); 356 | 357 | 358 | test("Smooth errors attributed correctly", () => { 359 | let m = new Model(); 360 | let v = m.Variable({ 361 | value: `a<-1 362 | b<-2 363 | smooth(max("abc"), 10) 364 | c<-3` 365 | }); 366 | 367 | try { 368 | m.simulate(); 369 | } catch (err) { 370 | expect(err.primitive).toBe(v); 371 | expect(err.line).toBe(3); 372 | } 373 | 374 | v.value = `a<-1 375 | b<-2 376 | 377 | smooth(max("abc"), 10) 378 | c<-3`; 379 | try { 380 | m.simulate(); 381 | // should never happen 382 | expect(false).toBe(true); 383 | } catch (err) { 384 | expect(err.primitive).toBe(v); 385 | expect(err.source).toBe("PRIMITIVE:VALUE"); 386 | expect(err.line).toBe(4); 387 | 388 | 389 | expect(err.message).toContain("This value may only be numbers or vectors, found a string"); 390 | } 391 | }); 392 | 393 | 394 | test("Macro errors attributed correctly", () => { 395 | let m = new Model(); 396 | m.globals = `function foo() 397 | xxx() 398 | end function 399 | `; 400 | m.Variable({ 401 | value: "foo()" 402 | }); 403 | 404 | try { 405 | m.simulate(); 406 | // should never happen 407 | expect(false).toBe(true); 408 | } catch (err) { 409 | expect(err.primitive).toBe(undefined); 410 | 411 | expect(err.source).toBe("GLOBALS"); 412 | 413 | // undefined as the error is in the macro 414 | expect(err.line).toBe(2); 415 | 416 | expect(err.message).toContain("The variable or function \"xxx\" does not exist"); 417 | } 418 | }); 419 | 420 | 421 | it("Invalid flow vectors", () => { 422 | let m = new Model(); 423 | m.Variable({ 424 | name: "v1", 425 | value: "1" 426 | }); 427 | 428 | let s = m.Stock({ 429 | name: "Stock", 430 | initial: "{1,2}" 431 | }); 432 | let f = m.Flow(s, null, { 433 | name: "Flow", 434 | rate: "{1, 2, 3}" 435 | }); 436 | 437 | m.Variable({ 438 | name: "v2", 439 | value: "2" 440 | }); 441 | 442 | 443 | try { 444 | m.simulate(); 445 | // should never happen 446 | expect(false).toBe(true); 447 | } catch (err) { 448 | expect(err.primitive).toBe(f); 449 | 450 | expect(err.source).toBe("PRIMITIVE"); 451 | 452 | expect(err.line).toBe(undefined); 453 | 454 | expect(err.message).toContain("Incompatible vector keys"); 455 | } 456 | }); 457 | 458 | 459 | it("Invalid converter source", () => { 460 | let m = new Model(); 461 | let v = m.Variable({ 462 | name: "v1", 463 | value: "\"abc\"" 464 | }); 465 | 466 | let c = m.Converter({ 467 | name: "Converter", 468 | values: [{ x: 1, y: 1 }], 469 | input: v 470 | }); 471 | 472 | m.Link(v, c); 473 | 474 | 475 | 476 | m.Variable({ 477 | name: "v2", 478 | value: "2" 479 | }); 480 | 481 | try { 482 | m.simulate(); 483 | // should never happen 484 | expect(false).toBe(true); 485 | } catch (err) { 486 | expect(err.primitive).toBe(c); 487 | 488 | expect(err.source).toBe("PRIMITIVE"); 489 | 490 | expect(err.line).toBe(undefined); 491 | 492 | expect(err.message).toBe("Converter inputs must be numbers or vectors."); 493 | } 494 | }); 495 | 496 | 497 | test("Invalid name", () => { 498 | let m = new Model(); 499 | let x = m.Variable({ 500 | name: "[[f] [z]]", 501 | value: "1" 502 | }); 503 | 504 | let y = m.Variable({ 505 | name: "y", 506 | value: "[[f] [z]]" 507 | }); 508 | 509 | m.Link(x, y); 510 | 511 | expect(() => m.simulate()).toThrow(/Invalid equation syntax/); 512 | 513 | y.value = "[]"; 514 | expect(() => m.simulate()).toThrow(/Invalid equation syntax/); 515 | }); 516 | 517 | 518 | test("Smooth errors due to wrong number of parameters", () => { 519 | let m = new Model(); 520 | let v = m.Variable({ 521 | value: "smooth(10)" 522 | }); 523 | expect(() => m.simulate()).toThrow(/Wrong number/); 524 | 525 | 526 | v.value = "delay1(10, 11, 12, 14, 15)"; 527 | expect(() => m.simulate()).toThrow(/Wrong number/); 528 | expect(() => m.simulate()).toThrow(/delay1/); 529 | }); 530 | 531 | 532 | test("Smooth errors due to non-positive period", () => { 533 | let m = new Model(); 534 | let v = m.Variable({ 535 | value: "smooth(10, -1)" 536 | }); 537 | expect(() => m.simulate()).toThrow(/must be greater than/); 538 | 539 | 540 | v.value = "delay1(10, 0)"; 541 | expect(() => m.simulate()).toThrow(/must be greater than/); 542 | 543 | 544 | v.value = "delay3(10, -9)"; 545 | expect(() => m.simulate()).toThrow(/must be greater than/); 546 | }); 547 | 548 | 549 | test("Invalid simulation units", () => { 550 | let m = new Model(); 551 | 552 | m.State({ 553 | name: "State" 554 | }); 555 | 556 | m.simulate(); // mo error 557 | 558 | // @ts-ignore 559 | m.timeUnits = ""; 560 | expect(() => m.simulate()).toThrow(/must set the time units/); 561 | }); 562 | 563 | 564 | test("Invalid ifThenElse", () => { 565 | let m = new Model(); 566 | m.Variable({ 567 | value: `IfThenElse({ 568 | x: 4 569 | }, { 570 | x: {1} 571 | }, { 572 | x: {2} 573 | })` 574 | }); 575 | 576 | expect(() => m.simulate()).toThrow(/Keys do not match/); 577 | }); 578 | 579 | 580 | test("Ambiguous primitive names", () => { 581 | let m = new Model(); 582 | 583 | let x1 = m.Variable({ 584 | name: "x", 585 | value: "1" 586 | }); 587 | let x2= m.Variable({ 588 | name: "x", 589 | value: "2" 590 | }); 591 | let y = m.Variable({ 592 | name: "y", 593 | value: "[x]" 594 | }); 595 | 596 | m.Link(x1, y); 597 | 598 | let res = m.simulate(); 599 | expect(res.value(y)).toBe(1); 600 | 601 | m.Link(x2, y); 602 | expect(() => m.simulate()).toThrow(/\[x\] is ambiguous/); 603 | }); 604 | 605 | 606 | test("Units mismatch", () => { 607 | let m = new Model(); 608 | let x = m.Variable({ 609 | name: "x", 610 | value: "1" 611 | }); 612 | let y = m.Variable({ 613 | name: "y", 614 | value: "1" 615 | }); 616 | 617 | m.Link(x, y); 618 | 619 | // sugestion is shown when it's a matieral 620 | 621 | y.value = "1 + {1 meter}"; 622 | expect(() => m.simulate()).toThrow("Consider replacing 1 with {1 meter}"); 623 | 624 | y.value = "{1 meter} + 2"; 625 | expect(() => m.simulate()).toThrow("Consider replacing 2 with {2 meter}"); 626 | 627 | 628 | // suggestion is shown when it's a primitive 629 | 630 | y.value = "[x] + {1 meter}"; 631 | expect(() => m.simulate()).toThrow("Consider setting the units of [x] to meter."); 632 | 633 | y.value = "{1 meter} + [x]"; 634 | expect(() => m.simulate()).toThrow("Consider setting the units of [x] to meter."); 635 | 636 | 637 | // suggestion is not shown when it is a function 638 | 639 | y.value = "seconds() + {1 meter}"; 640 | try { 641 | m.simulate(); 642 | // should never reach this point 643 | expect(true).toBe(false); 644 | } catch (err) { 645 | expect(err.message).not.toContain("Consider"); 646 | } 647 | }); -------------------------------------------------------------------------------- /test/suites/simulation_get_set.test.js: -------------------------------------------------------------------------------- 1 | import { Model } from "../../src/api/Model.js"; 2 | 3 | 4 | test("Simulation get and set", () => { 5 | let m = new Model(); 6 | 7 | m.algorithm = "RK4"; 8 | expect(m.algorithm).toBe("RK4"); 9 | 10 | m.timeStep = 72; 11 | expect(m.timeStep).toBe(72); 12 | 13 | m.timeLength = 43; 14 | expect(m.timeLength).toBe(43); 15 | 16 | m.timeStart = 32; 17 | expect(m.timeStart).toBe(32); 18 | 19 | m.timePause = 2; 20 | expect(m.timePause).toBe(2); 21 | 22 | m.timeUnits = "Weeks"; 23 | expect(m.timeUnits).toBe("Weeks"); 24 | }); -------------------------------------------------------------------------------- /test/suites/subscripting.test.js: -------------------------------------------------------------------------------- 1 | import { Model } from "../../src/api/Model.js"; 2 | 3 | 4 | describe.each([ 5 | ["Euler"], ["RK4"] 6 | ])("Subscripting %s", 7 | /** 8 | * @param {"Euler"|"RK4"} algorithm 9 | */ 10 | (algorithm) => { 11 | test("Countries", () => { 12 | let m = new Model({ algorithm }); 13 | 14 | 15 | m.timeUnits = "Years"; 16 | m.timeLength = 20; 17 | 18 | 19 | expect(() => m.simulate()).not.toThrow(); 20 | 21 | 22 | let p = m.Stock({ 23 | name: "Population" 24 | }); 25 | let r = m.Variable({ 26 | name: "Rate" 27 | }); 28 | let f = m.Flow(null, p, { 29 | name: "Growth" 30 | }); 31 | m.Link(r, f, { 32 | name: "Link" 33 | }); 34 | 35 | let a = m.Variable({ 36 | name: "Aggregate" 37 | }); 38 | let b = m.Variable({ 39 | name: "Aggregate 2" 40 | }); 41 | m.Link(p, a, { 42 | name: "Link" 43 | }); 44 | m.Link(p, b, { 45 | name: "Link" 46 | }); 47 | 48 | p.initial = "{'a': 10, 'b': 5}"; 49 | f.rate = "[Rate]"; 50 | r.value = "{a: 2, b: 1}"; 51 | a.value = "[Population]{\"a\"}"; 52 | b.value = "[Population]{\"b\"}"; 53 | 54 | let res = m.simulate(); 55 | expect(res.series(a)[10]).toBe(30); 56 | expect(res.series(b)[10]).toBe(15); 57 | 58 | 59 | a.value = "[Population]{mean}"; 60 | b.value = "[Population]{max}"; 61 | res = m.simulate(); 62 | expect(res.series(a)[10]).toBe(45 / 2); 63 | expect(res.series(b)[10]).toBe(30); 64 | 65 | r.value = "2"; 66 | expect(() => m.simulate()).toThrow(); 67 | 68 | r.value = "repeat(2, {'a', 'b'})"; 69 | res = m.simulate(); 70 | expect(res.series(a)[10]).toBe(55 / 2); 71 | expect(res.series(b)[10]).toBe(30); 72 | 73 | p.initial = "{'males': {canada:1, usa:2,'mexico':3}, 'females': {'usa':20, 'canada':10, 'mexico': 30} }"; 74 | 75 | a.value = "([Population]{\"males\", *}).usa"; 76 | b.value = "[Population]{\"females\", \"mexico\"}"; 77 | expect(() => m.simulate()).toThrow(); 78 | 79 | r.value = "repeat(repeat(2, {'canada', 'usa', 'mexico'}), {'males', 'females'})"; 80 | res = m.simulate(); 81 | expect(res.series(a)[10]).toBe(2 + 20); 82 | expect(res.series(b)[10]).toBe(30 + 20); 83 | 84 | a.value = "[Population]{\"males\", max}"; 85 | b.value = "[Population]{min, \"canada\"}"; 86 | res = m.simulate(); 87 | expect(res.series(a)[10]).toBe(3 + 20); 88 | expect(res.series(b)[10]).toBe(1 + 20); 89 | 90 | a.value = "([Population]{\"males\", *}){\"USA\"}"; 91 | b.value = "([Population]{* , \"mexico\"}){\"Females\"}"; 92 | res = m.simulate(); 93 | expect(res.series(a)[10]).toBe(2 + 20); 94 | expect(res.series(b)[10]).toBe(30 + 20); 95 | 96 | a.value = "([Population]{\"males\", *}){\"USA\"}"; 97 | b.value = "([Population]{* , \"mexico\"}).Females"; 98 | res = m.simulate(); 99 | expect(res.series(a)[10]).toBe(2 + 20); 100 | expect(res.series(b)[10]).toBe(30 + 20); 101 | 102 | 103 | r.value = "{males: repeat(3, {'canada', 'usa', 'mexico'}), females: repeat(1, {'canada', 'usa', 'mexico'})}"; 104 | res = m.simulate(); 105 | expect(res.series(a)[10]).toBe(2 + 30); 106 | expect(res.series(b)[10]).toBe(30 + 10); 107 | 108 | p.nonNegative = false; 109 | f.nonNegative = true; 110 | r.value = "{'males': repeat(3, {'canada', 'usa', 'mexico'}), 'females': repeat(-4, {'canada', 'usa', 'mexico'})}"; 111 | res = m.simulate(); 112 | expect(res.series(a)[10]).toBe(2 + 30); 113 | expect(res.series(b)[10]).toBe(30 + 0); 114 | 115 | f.nonNegative = false; 116 | res = m.simulate(); 117 | expect(res.series(a)[10]).toBe(2 + 30); 118 | expect(res.series(b)[10]).toBe(30 - 40); 119 | 120 | p.nonNegative = true; 121 | res = m.simulate(); 122 | expect(res.series(a)[10]).toBe(2 + 30); 123 | expect(res.series(b)[10]).toBe(0); 124 | 125 | p.nonNegative = false; 126 | p.constraints = { 127 | min: -100, 128 | max: 100 129 | }; 130 | expect(() => m.simulate()).not.toThrow(); 131 | 132 | p.constraints = { 133 | min: -5, 134 | max: 100 135 | }; 136 | expect(() => m.simulate()).toThrow(); 137 | 138 | p.constraints = { 139 | min: -100, 140 | max: 31 141 | }; 142 | expect(() => m.simulate()).toThrow(); 143 | 144 | 145 | p.constraints = {}; 146 | expect(() => m.simulate()).not.toThrow(); 147 | 148 | p.units = "Widgets"; 149 | a.units = "Widgets"; 150 | b.units = "Widgets"; 151 | 152 | f.units = "Widgets/Year"; 153 | r.units = "Widgets/Year"; 154 | 155 | expect(() => m.simulate()).not.toThrow(); 156 | 157 | 158 | p.initial = "{'males': {'canada':1,'usa':{2 cats},'mexico':3}, 'females': {'usa':20, 'canada':10, 'mexico': 30} }"; 159 | expect(() => m.simulate()).toThrow(); 160 | 161 | p.initial = "{'males': {'canada':1,'usa':{2 widgets},'mexico':3}, 'females': {'usa':20, 'canada':10, 'mexico': 30} }"; 162 | 163 | expect(() => m.simulate()).not.toThrow(); 164 | 165 | 166 | p.type = "Conveyor"; 167 | p.delay = 5; 168 | res = m.simulate(); 169 | expect(res.series(a)[14]).toBe(2 + 30); 170 | expect(Math.round(res.series(b)[14] * 100000)).toBe((30 - 40) * 100000); 171 | 172 | 173 | a.value = "([Population]{\"males\", *}){\"India\"}"; 174 | expect(() => m.simulate()).toThrow(); 175 | 176 | a.value = "([Population]{\"foobar\", *}){\"USA\"}"; 177 | expect(() => m.simulate()).toThrow(); 178 | }); 179 | 180 | 181 | test("Animals", () => { 182 | let m = new Model({ algorithm }); 183 | 184 | 185 | let p = m.Stock({ 186 | name: "Population" 187 | }); 188 | let p2 = m.Stock({ 189 | name: "Population 2" 190 | }); 191 | let r = m.Variable({ 192 | name: "Rate" 193 | }); 194 | let f = m.Flow(p, p2, { 195 | name: "Growth" 196 | }); 197 | m.Link(r, f, { 198 | name: "Link" 199 | }); 200 | 201 | let a = m.Variable({ 202 | name: "Aggregate" 203 | }); 204 | m.Link(p2, a, { 205 | name: "Link" 206 | }); 207 | let b = m.Variable({ 208 | name: "Aggregate 2" 209 | }); 210 | m.Link(p, b, { 211 | name: "Link" 212 | }); 213 | 214 | p.initial = 100; 215 | p2.initial = 0; 216 | f.rate = "[Rate]"; 217 | r.value = "{'dogs':1, 'cats':2}"; 218 | let res = m.simulate(); 219 | expect(res.series(p)[10]).toBe(100 - 10 * 3); 220 | expect(res.series(p2)[10]).toBe(0 + 10 * 3); 221 | 222 | p2.initial = "{'dogs':5, 'cats':4}"; 223 | a.value = "[population 2]{'dogs'}"; 224 | res = m.simulate(); 225 | expect(res.series(p)[10]).toBe(100 - 10 * 3); 226 | expect(res.series(a)[10]).toBe(5 + 10 * 1); 227 | 228 | r.value = "{'dogs': {'x': 1, 'y':2}, 'cats': {'x':3, 'y':4} }"; 229 | res = m.simulate(); 230 | expect(res.series(p)[10]).toBe(100 - 10 * 10); 231 | expect(res.series(a)[10]).toBe(5 + 10 * 3); 232 | 233 | p.initial = "{x:40, y:60}"; 234 | b.value = "[Population]{'x'}"; 235 | res = m.simulate(); 236 | expect(res.series(b)[10]).toBe(40 - 4 * 10); 237 | expect(res.series(a)[10]).toBe(5 + 10 * 3); 238 | 239 | p2.initial = "{'dogs':5, 'cats':4, rats:6}"; 240 | r.value = "{'dogs': {'x': 1, 'y':2}, 'cats': {'x':3, 'y':4}, 'rats': {'x':9, 'y':10} }"; 241 | res = m.simulate(); 242 | expect(res.series(b)[10]).toBe(40 - 13 * 10); 243 | expect(res.series(a)[10]).toBe(5 + 10 * 3); 244 | 245 | f.start = p2; 246 | f.end = p; 247 | res = m.simulate(); 248 | expect(res.series(b)[10]).toBe(40 + 13 * 10); 249 | expect(res.series(a)[10]).toBe(5 - 10 * 3); 250 | }); 251 | 252 | 253 | test("Converted to object in results", () => { 254 | let m = new Model({ algorithm }); 255 | 256 | let v = m.Variable({ 257 | value: "{x: 1, y: 2*3}" 258 | }); 259 | 260 | expect(m.simulate().value(v)).toEqual({ x: 1, y: 6 }); 261 | }); 262 | }); 263 | -------------------------------------------------------------------------------- /test/suites/time_shift.test.js: -------------------------------------------------------------------------------- 1 | import { Model } from "../../src/api/Model.js"; 2 | 3 | 4 | test("Time Shift", () => { 5 | let m = new Model(); 6 | 7 | let A = m.Stock({ 8 | name: "A" 9 | }); 10 | let B = m.Stock({ 11 | name: "B" 12 | }); 13 | let flowA = m.Flow(null, A, { 14 | name: "Flow A" 15 | }); 16 | let flowB = m.Flow(null, B, { 17 | name: "Flow B" 18 | }); 19 | 20 | A.initial = 10; 21 | B.initial = 10; 22 | flowA.rate = "0.1*[A]"; 23 | flowB.rate = "0.1*[B]"; 24 | 25 | 26 | let fA = m.Folder({ 27 | name: "f A" 28 | }); 29 | let fB = m.Folder({ 30 | name: "f B" 31 | }); 32 | 33 | A.parent = fA; 34 | flowA.parent = fA; 35 | 36 | B.parent = fB; 37 | flowB.parent = fB; 38 | 39 | m.timeLength = 10; 40 | m.timeStep = 1; 41 | m.algorithm = "Euler"; 42 | 43 | let res = m.simulate(); 44 | expect(res.series(A)[1]).toBe(11); 45 | expect(res.series(B)[1]).toBe(11); 46 | 47 | fA.customTimeSettings = { enabled: true, algorithm: "RK4", timeStep: 1 }; 48 | res = m.simulate(); 49 | expect(Math.round(res.series(A)[1] * 1000)).toBe(Math.round(11.051708 * 1000)); 50 | expect(res.series(B)[1]).toBe(11); 51 | 52 | fB.customTimeSettings = { enabled: true, algorithm: "Euler", timeStep: 2.5 }; 53 | res = m.simulate(); 54 | expect(Math.round(res.series(A)[1] * 1000)).toBe(Math.round(11.051708 * 1000)); 55 | expect(res.series(B)[1] === undefined).toBe(true); 56 | expect(res.series(B)[3]).toBe(12.5); 57 | expect(res.series(A)[3] === undefined).toBe(true); 58 | 59 | fA.customTimeSettings = { enabled: true, algorithm: "RK4", timeStep: 2.5 }; 60 | fB.customTimeSettings = { enabled: true, algorithm: "Euler", timeStep: 1 }; 61 | res = m.simulate(); 62 | expect(res.series(A)[1] === undefined).toBe(true); 63 | expect(res.series(B)[1]).toBe(11); 64 | expect(Math.round(res.series(A)[3] * 1000)).toBe(Math.round(12.8401699 * 1000)); 65 | expect(res.series(B)[3] === undefined).toBe(true); 66 | }); -------------------------------------------------------------------------------- /test/suites/types.test.js: -------------------------------------------------------------------------------- 1 | import { Model } from "../../src/api/Model.js"; 2 | import { check, failure } from "../TestUtilities.js"; 3 | 4 | 5 | test("Number", () => { 6 | let m = new Model(); 7 | 8 | let x = m.Variable({ 9 | name: "tester" 10 | }); 11 | x.value = "1 > 2"; 12 | 13 | let res = m.simulate(); 14 | 15 | expect(res.series(x)[0]).toBe(0); 16 | }); 17 | 18 | 19 | test("Booleans", () => { 20 | let m = new Model(); 21 | 22 | let x = m.Variable({ 23 | name: "tester" 24 | }); 25 | // can do strict equality with booleans 26 | x.value = "true = true"; 27 | 28 | let res = m.simulate(); 29 | expect(res.series(x)[0]).toBe(1); 30 | 31 | // can't do inequality with booleans 32 | x.value = "true > true"; 33 | 34 | expect(() => m.simulate()).toThrow(/Cannot /); 35 | }); 36 | 37 | 38 | test("Strings", () => { 39 | let m = new Model(); 40 | 41 | let x = m.Variable({ 42 | name: "tester" 43 | }); 44 | // can do strict equality with string 45 | x.value = "\"a\" = \"a\""; 46 | 47 | let res = m.simulate(); 48 | expect(res.series(x)[0]).toBe(1); 49 | 50 | // can't do inequality with strings 51 | x.value = "\"a\" > \"a\""; 52 | 53 | expect(() => m.simulate()).toThrow(/Cannot /); 54 | }); 55 | 56 | 57 | test("Functions", () => { 58 | let m = new Model(); 59 | 60 | let x = m.Variable({ 61 | name: "tester" 62 | }); 63 | 64 | // functions are evaluated 65 | x.value = "z <- Function()\n 10\n end function\n\n z > 1"; 66 | let res = m.simulate(); 67 | expect(res.value(x)).toBe(1); 68 | 69 | 70 | // functions are evaluated 71 | x.value = "z <- Function()\n 1\n end function\n\n z > 1"; 72 | res = m.simulate(); 73 | expect(res.value(x)).toBe(0); 74 | }); 75 | 76 | 77 | test("Stock values must be vector/material", () => { 78 | let m = new Model(); 79 | 80 | let s = m.Stock({ 81 | name: "tester" 82 | }); 83 | s.initial = "\"abc\""; 84 | 85 | expect(() => m.simulate()).toThrow(/only be numbers or vectors/); 86 | 87 | s.initial = "{\"abc\"}"; 88 | expect(() => m.simulate()).toThrow(/only be numbers or vectors/); 89 | 90 | s.initial = "{true, false}"; 91 | expect(() => m.simulate()).toThrow(/only be numbers or vectors/); 92 | 93 | // note a single boolean is allowed and is converted to 1/0 94 | }); 95 | 96 | 97 | 98 | test("Flow values must be vector/material", () => { 99 | let m = new Model(); 100 | 101 | let s = m.Flow(null, null, { 102 | name: "tester" 103 | }); 104 | s.rate = "\"abc\""; 105 | 106 | expect(() => m.simulate()).toThrow(/only be numbers or vectors/); 107 | 108 | s.rate = "{\"abc\"}"; 109 | expect(() => m.simulate()).toThrow(/only be numbers or vectors/); 110 | 111 | s.rate = "{true, false}"; 112 | expect(() => m.simulate()).toThrow(/only be numbers or vectors/); 113 | 114 | s.rate = "true"; 115 | expect(() => m.simulate()).toThrow(/only be numbers or vectors/); 116 | }); 117 | 118 | 119 | 120 | test("Variable values must be vector/material when it has units", () => { 121 | let m = new Model(); 122 | 123 | let s = m.Variable({ 124 | name: "tester" 125 | }); 126 | s.units = "foo"; 127 | 128 | s.value = "\"abc\""; 129 | expect(() => m.simulate()).toThrow(/Cannot add units/); 130 | 131 | s.value = "{\"abc\"}"; 132 | expect(() => m.simulate()).toThrow(/Cannot add units/); 133 | 134 | s.value = "{true, false}"; 135 | expect(() => m.simulate()).toThrow(/Cannot add units/); 136 | 137 | // note a single boolean is allowed and is converted to 1/0 138 | 139 | // when we don't have units, any type is allowed 140 | s.units = ""; 141 | 142 | s.value = "\"abc\""; 143 | m.simulate(); // no error 144 | 145 | s.value = "{\"abc\"}"; 146 | m.simulate(); // no error 147 | 148 | s.value = "{true, false}"; 149 | m.simulate(); // no error 150 | }); 151 | 152 | 153 | test("Failed type conversions", () => { 154 | check("3 * 2", 6); 155 | failure("2 * 'abc'", "Cannot convert"); 156 | failure("2 * true", "Cannot convert"); 157 | failure("'abc' * 2", "Cannot convert"); 158 | failure("true * 2", "Cannot convert"); 159 | 160 | 161 | check("6 / 2", 3); 162 | failure("6 / 'abc'", "Cannot convert"); 163 | failure("6 / true", "Cannot convert"); 164 | 165 | 166 | check("6 ^ 2", 36); 167 | failure("6 ^ 'abc'", "Cannot convert"); 168 | failure("6 ^ true", "Cannot convert"); 169 | 170 | 171 | check("6 + 2", 8); 172 | check("6 + 'abc'", "6abc"); 173 | failure("6 + true", "Cannot convert"); 174 | check("'abc' + 6", "abc6"); 175 | failure("false + 6", "Cannot convert"); 176 | 177 | 178 | check("6 - 2", 4); 179 | failure("6 - 'abc'", "Cannot convert"); 180 | failure("6 - true", "Cannot convert"); 181 | 182 | 183 | check("6 % 2", 0); 184 | failure("6 % 'abc'", "Cannot convert"); 185 | failure("6 % true", "Cannot convert"); 186 | }); 187 | 188 | 189 | test("Invalid use of value()", () => { 190 | let m = new Model(); 191 | 192 | let x = m.Variable({ 193 | name: "x", 194 | value: "{1, 2, 3}" 195 | }); 196 | 197 | let y = m.Variable({ 198 | name: "y", 199 | value: "[x].value([x])" 200 | }); 201 | 202 | m.Link(x, y); 203 | expect(() => m.simulate()).toThrow(/does not contain agents/); 204 | }); -------------------------------------------------------------------------------- /test/suites/unit_constraints.test.js: -------------------------------------------------------------------------------- 1 | import { Model } from "../../src/api/Model.js"; 2 | 3 | 4 | test("units and constraints", () => { 5 | let m = new Model(); 6 | 7 | let x = m.Variable({ 8 | name: "tester" 9 | }); 10 | x.value = "time"; 11 | x.units = "Seconds"; 12 | 13 | m.simulate(); // no error; 14 | 15 | x.units = "Qubits"; 16 | expect(() => m.simulate()).toThrow(); 17 | 18 | x.units = "Minutes"; 19 | m.simulate(); // no error; 20 | 21 | x.constraints = { 22 | max: 10 23 | }; 24 | expect(() => m.simulate()).toThrow(); 25 | 26 | x.constraints = {}; 27 | m.simulate(); // no error 28 | 29 | x.constraints = { 30 | min: 5 31 | }; 32 | expect(() => m.simulate()).toThrow(); 33 | 34 | x.constraints = {}; 35 | m.simulate(); // no error 36 | }); 37 | 38 | 39 | test("square root and units", () => { 40 | let m = new Model(); 41 | 42 | let x = m.Variable({ 43 | name: "tester" 44 | }); 45 | x.value = "sqrt({4 square meters}) * {3 square meters}"; 46 | x.units = "meters^3"; 47 | 48 | 49 | let res = m.simulate(); 50 | expect(res.series(x)[10]).toBe(6); 51 | }); -------------------------------------------------------------------------------- /test/suites/unit_functions.test.js: -------------------------------------------------------------------------------- 1 | import { Model } from "../../src/api/Model.js"; 2 | import { UnitManager } from "../../src/formula/Units.js"; 3 | 4 | 5 | test("unit functions", () => { 6 | let m = new Model(); 7 | let p1 = m.Variable({ 8 | name: "p1" 9 | }); 10 | let p2 = m.Variable({ 11 | name: "p2" 12 | }); 13 | let p3 = m.Variable({ 14 | name: "p3" 15 | }); 16 | let p4 = m.Variable({ 17 | name: "p4" 18 | }); 19 | let p5 = m.Variable({ 20 | name: "p5" 21 | }); 22 | let p6 = m.Variable({ 23 | name: "p6" 24 | }); 25 | let p7 = m.Variable({ 26 | name: "p7" 27 | }); 28 | p1.value = "Years"; 29 | p2.value = "Months"; 30 | p3.value = "Weeks"; 31 | p4.value = "Days"; 32 | p5.value = "Hours"; 33 | p6.value = "Minutes"; 34 | p7.value = "Seconds"; 35 | 36 | let res = m.simulate(); 37 | expect(res.series(p1)[3]).toBe(3); 38 | expect(res.series(p2)[3]).toBe(3 * 12); 39 | expect(Math.floor(res.series(p3)[3])).toBe(3 * 52); 40 | expect(res.series(p4)[3]).toBe(3 * 365); 41 | expect(res.series(p5)[3]).toBe(3 * 365 * 24); 42 | expect(res.series(p6)[3]).toBe(3 * 365 * 24 * 60); 43 | expect(res.series(p7)[3]).toBe(3 * 365 * 24 * 60 * 60); 44 | 45 | p1.value = "Years(time*2)"; 46 | p2.value = "Months(time*2)"; 47 | p3.value = "Weeks(time*2)"; 48 | p4.value = "Days(time*2)"; 49 | p5.value = "Hours(time*2)"; 50 | p6.value = "Minutes(time*2)"; 51 | p7.value = "Seconds(time*2)"; 52 | res = m.simulate(); 53 | expect(res.series(p1)[3]).toBe(2 * 3); 54 | expect(res.series(p2)[3]).toBe(2 * 3 * 12); 55 | expect(Math.floor(res.series(p3)[3])).toBe(2 * 3 * 52); 56 | expect(res.series(p4)[3]).toBe(2 * 3 * 365); 57 | expect(res.series(p5)[3]).toBe(2 * 3 * 365 * 24); 58 | expect(res.series(p6)[3]).toBe(2 * 3 * 365 * 24 * 60); 59 | expect(res.series(p7)[3]).toBe(2 * 3 * 365 * 24 * 60 * 60); 60 | 61 | p1.value = "unitless(TimeStart)"; 62 | p2.value = "unitless(TimeLength)"; 63 | p3.value = "unitless(TimeStep)"; 64 | p4.value = "unitless(TimeEnd)"; 65 | 66 | res = m.simulate(); 67 | expect(res.series(p1)[3]).toBe(m.timeStart); 68 | expect(res.series(p2)[3]).toBe(m.timeLength); 69 | expect(res.series(p3)[3]).toBe(m.timeStep); 70 | expect(res.series(p4)[3]).toBe(m.timeStart + m.timeLength); 71 | }); 72 | 73 | 74 | test("Unit manager", () => { 75 | let manager = new UnitManager(); 76 | 77 | let mol = manager.unitsFromString("Molecules"); 78 | mol.addBase(); 79 | expect(mol.toBase).toBe(1/6.02214076e23); 80 | 81 | manager = new UnitManager(); 82 | mol = manager.unitsFromString("Molecule"); 83 | mol.addBase(); 84 | expect(mol.toBase).toBe(1/6.02214076e23); 85 | 86 | 87 | manager = new UnitManager(); 88 | let yr = manager.unitsFromString("Years"); 89 | yr.addBase(); 90 | expect(yr.toBase).toBe(31536000); 91 | mol = manager.unitsFromString("Molecules"); 92 | mol.addBase(); 93 | expect(mol.toBase).toBe(1/6.02214076e23); 94 | }); -------------------------------------------------------------------------------- /vendor/avl/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Alexander Milevski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /vendor/avl/utils.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | /** 4 | * Prints tree horizontally 5 | * @param {Node} root 6 | * @param {function(node:Node):String} [printNode] 7 | * @return {String} 8 | */ 9 | export function print (root, printNode = (n) => n.key) { 10 | let out = []; 11 | row(root, "", true, (v) => out.push(v), printNode); 12 | return out.join(""); 13 | } 14 | 15 | /** 16 | * Prints level of the tree 17 | * @param {Node} root 18 | * @param {String} prefix 19 | * @param {Boolean} isTail 20 | * @param {Function(in:string):void} out 21 | * @param {Function(node:Node):String} printNode 22 | */ 23 | function row (root, prefix, isTail, out, printNode) { 24 | if (root) { 25 | out(`${ prefix }${ isTail ? "└── " : "├── " }${ printNode(root) }\n`); 26 | const indent = prefix + (isTail ? " " : "│ "); 27 | if (root.left) row(root.left, indent, false, out, printNode); 28 | if (root.right) row(root.right, indent, true, out, printNode); 29 | } 30 | } 31 | 32 | /** 33 | * Is the tree balanced (none of the subtrees differ in height by more than 1) 34 | * @param {Node} root 35 | * @return {Boolean} 36 | */ 37 | export function isBalanced(root) { 38 | if (root === null) return true; // If node is empty then return true 39 | 40 | // Get the height of left and right sub trees 41 | let lh = height(root.left); 42 | let rh = height(root.right); 43 | 44 | if (Math.abs(lh - rh) <= 1 && 45 | isBalanced(root.left) && 46 | isBalanced(root.right)) return true; 47 | 48 | // If we reach here then tree is not height-balanced 49 | return false; 50 | } 51 | 52 | /** 53 | * The function Compute the 'height' of a tree. 54 | * Height is the number of nodes along the longest path 55 | * from the root node down to the farthest leaf node. 56 | * 57 | * @param {Node} node 58 | * @return {Number} 59 | */ 60 | function height(node) { 61 | return node ? (1 + Math.max(height(node.left), height(node.right))) : 0; 62 | } 63 | 64 | export function loadRecursive (parent, keys, values, start, end) { 65 | const size = end - start; 66 | if (size > 0) { 67 | const middle = start + Math.floor(size / 2); 68 | const key = keys[middle]; 69 | const data = values[middle]; 70 | const node = { key, data, parent }; 71 | node.left = loadRecursive(node, keys, values, start, middle); 72 | node.right = loadRecursive(node, keys, values, middle + 1, end); 73 | return node; 74 | } 75 | return null; 76 | } 77 | 78 | export function markBalance(node) { 79 | if (node === null) return 0; 80 | const lh = markBalance(node.left); 81 | const rh = markBalance(node.right); 82 | 83 | node.balanceFactor = lh - rh; 84 | return Math.max(lh, rh) + 1; 85 | } 86 | 87 | export function sort(keys, values, left, right, compare) { 88 | if (left >= right) return; 89 | 90 | // eslint-disable-next-line no-bitwise 91 | const pivot = keys[(left + right) >> 1]; 92 | let i = left - 1; 93 | let j = right + 1; 94 | 95 | // eslint-disable-next-line no-constant-condition 96 | while (true) { 97 | do i++; while (compare(keys[i], pivot) < 0); 98 | do j--; while (compare(keys[j], pivot) > 0); 99 | if (i >= j) break; 100 | 101 | let tmp = keys[i]; 102 | keys[i] = keys[j]; 103 | keys[j] = tmp; 104 | 105 | tmp = values[i]; 106 | values[i] = values[j]; 107 | values[j] = tmp; 108 | } 109 | 110 | sort(keys, values, left, j, compare); 111 | sort(keys, values, j + 1, right, compare); 112 | } -------------------------------------------------------------------------------- /vendor/bigjs/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © `<2021>` `Michael Mclaughlin` 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /vendor/graph.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | /* eslint-disable */ 3 | /** 4 | * Springy v1.0.1 5 | * 6 | * Copyright (c) 2010 Dennis Hotson 7 | * 8 | * Permission is hereby granted, free of charge, to any person 9 | * obtaining a copy of this software and associated documentation 10 | * files (the "Software"), to deal in the Software without 11 | * restriction, including without limitation the rights to use, 12 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the 14 | * Software is furnished to do so, subject to the following 15 | * conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | */ 29 | 30 | export let Graph = function() { 31 | this.nodeSet = {}; 32 | this.nodes = []; 33 | this.edges = []; 34 | this.adjacency = {}; 35 | 36 | this.nextNodeId = 0; 37 | this.nextEdgeId = 0; 38 | this.eventListeners = []; 39 | }; 40 | 41 | var Node = function(id, data) { 42 | this.id = id; 43 | this.data = typeof(data) !== 'undefined' ? data : {}; 44 | }; 45 | 46 | var Edge = function(id, source, target, data) { 47 | this.id = id; 48 | this.source = source; 49 | this.target = target; 50 | this.data = typeof(data) !== 'undefined' ? data : {}; 51 | }; 52 | 53 | Graph.prototype.addNode = function(node) { 54 | if (typeof(this.nodeSet[node.id]) === 'undefined') { 55 | this.nodes.push(node); 56 | } 57 | 58 | this.nodeSet[node.id] = node; 59 | 60 | this.notify(); 61 | return node; 62 | }; 63 | 64 | Graph.prototype.addEdge = function(edge) { 65 | var exists = false; 66 | this.edges.forEach(function(e) { 67 | if (edge.id === e.id) { exists = true; } 68 | }); 69 | 70 | if (!exists) { 71 | this.edges.push(edge); 72 | } 73 | 74 | if (typeof(this.adjacency[edge.source.id]) === 'undefined') { 75 | this.adjacency[edge.source.id] = {}; 76 | } 77 | if (typeof(this.adjacency[edge.source.id][edge.target.id]) === 'undefined') { 78 | this.adjacency[edge.source.id][edge.target.id] = []; 79 | } 80 | 81 | exists = false; 82 | this.adjacency[edge.source.id][edge.target.id].forEach(function(e) { 83 | if (edge.id === e.id) { exists = true; } 84 | }); 85 | 86 | if (!exists) { 87 | this.adjacency[edge.source.id][edge.target.id].push(edge); 88 | } 89 | 90 | this.notify(); 91 | return edge; 92 | }; 93 | 94 | Graph.prototype.newNode = function(data) { 95 | var node = new Node(this.nextNodeId++, data); 96 | this.addNode(node); 97 | return node; 98 | }; 99 | 100 | Graph.prototype.newEdge = function(source, target, data) { 101 | var edge = new Edge(this.nextEdgeId++, source, target, data); 102 | this.addEdge(edge); 103 | return edge; 104 | }; 105 | 106 | // find the edges from node1 to node2 107 | Graph.prototype.getEdges = function(node1, node2) { 108 | if (typeof(this.adjacency[node1.id]) !== 'undefined' 109 | && typeof(this.adjacency[node1.id][node2.id]) !== 'undefined') { 110 | return this.adjacency[node1.id][node2.id]; 111 | } 112 | 113 | return []; 114 | }; 115 | 116 | // remove a node and it's associated edges from the graph 117 | Graph.prototype.removeNode = function(node) { 118 | if (typeof(this.nodeSet[node.id]) !== 'undefined') { 119 | delete this.nodeSet[node.id]; 120 | } 121 | 122 | for (var i = this.nodes.length - 1; i >= 0; i--) { 123 | if (this.nodes[i].id === node.id) { 124 | this.nodes.splice(i, 1); 125 | } 126 | } 127 | 128 | this.detachNode(node); 129 | 130 | }; 131 | 132 | // removes edges associated with a given node 133 | Graph.prototype.detachNode = function(node) { 134 | var tmpEdges = this.edges.slice(); 135 | tmpEdges.forEach(function(e) { 136 | if (e.source.id === node.id || e.target.id === node.id) { 137 | this.removeEdge(e); 138 | } 139 | }, this); 140 | 141 | this.notify(); 142 | }; 143 | 144 | // remove a node and it's associated edges from the graph 145 | Graph.prototype.removeEdge = function(edge) { 146 | for (var i = this.edges.length - 1; i >= 0; i--) { 147 | if (this.edges[i].id === edge.id) { 148 | this.edges.splice(i, 1); 149 | } 150 | } 151 | 152 | for (var x in this.adjacency) { 153 | for (var y in this.adjacency[x]) { 154 | var edges = this.adjacency[x][y]; 155 | 156 | for (var j=edges.length - 1; j>=0; j--) { 157 | if (this.adjacency[x][y][j].id === edge.id) { 158 | this.adjacency[x][y].splice(j, 1); 159 | } 160 | } 161 | } 162 | } 163 | 164 | this.notify(); 165 | }; 166 | 167 | /* Merge a list of nodes and edges into the current graph. eg. 168 | var o = { 169 | nodes: [ 170 | {id: 123, data: {type: 'user', userid: 123, displayname: 'aaa'}}, 171 | {id: 234, data: {type: 'user', userid: 234, displayname: 'bbb'}} 172 | ], 173 | edges: [ 174 | {from: 0, to: 1, type: 'submitted_design', directed: true, data: {weight: }} 175 | ] 176 | } 177 | */ 178 | Graph.prototype.merge = function(data) { 179 | var nodes = []; 180 | data.nodes.forEach(function(n) { 181 | nodes.push(this.addNode(new Node(n.id, n.data))); 182 | }, this); 183 | 184 | data.edges.forEach(function(e) { 185 | var from = nodes[e.from]; 186 | var to = nodes[e.to]; 187 | 188 | var id = (e.directed) 189 | ? (id = e.type + "-" + from.id + "-" + to.id) 190 | : (from.id < to.id) // normalise id for non-directed edges 191 | ? e.type + "-" + from.id + "-" + to.id 192 | : e.type + "-" + to.id + "-" + from.id; 193 | 194 | var edge = this.addEdge(new Edge(id, from, to, e.data)); 195 | edge.data.type = e.type; 196 | }, this); 197 | }; 198 | 199 | Graph.prototype.filterNodes = function(fn) { 200 | var tmpNodes = this.nodes.slice(); 201 | tmpNodes.forEach(function(n) { 202 | if (!fn(n)) { 203 | this.removeNode(n); 204 | } 205 | }, this); 206 | }; 207 | 208 | Graph.prototype.filterEdges = function(fn) { 209 | var tmpEdges = this.edges.slice(); 210 | tmpEdges.forEach(function(e) { 211 | if (!fn(e)) { 212 | this.removeEdge(e); 213 | } 214 | }, this); 215 | }; 216 | 217 | 218 | Graph.prototype.addGraphListener = function(obj) { 219 | this.eventListeners.push(obj); 220 | }; 221 | 222 | Graph.prototype.notify = function() { 223 | this.eventListeners.forEach(function(obj){ 224 | obj.graphChanged(); 225 | }); 226 | }; 227 | 228 | // ----------- 229 | export let Layout = {}; 230 | Layout.ForceDirected = function(graph, stiffness, repulsion, damping) { 231 | this.graph = graph; 232 | this.stiffness = stiffness; // spring stiffness constant 233 | this.repulsion = repulsion; // repulsion constant 234 | this.damping = damping; // velocity damping factor 235 | 236 | this.nodePoints = {}; // keep track of points associated with nodes 237 | this.edgeSprings = {}; // keep track of springs associated with edges 238 | this.getRandom = () => Math.random(); 239 | }; 240 | 241 | Layout.ForceDirected.prototype.point = function(node) { 242 | if (typeof(this.nodePoints[node.id]) === 'undefined') { 243 | var mass = typeof(node.data.mass) !== 'undefined' ? node.data.mass : 1.0; 244 | this.nodePoints[node.id] = new Layout.ForceDirected.Point(gVector.random(this.getRandom), mass); 245 | } 246 | 247 | return this.nodePoints[node.id]; 248 | }; 249 | 250 | Layout.ForceDirected.prototype.spring = function(edge) { 251 | if (typeof(this.edgeSprings[edge.id]) === 'undefined') { 252 | var length = typeof(edge.data.length) !== 'undefined' ? edge.data.length : 1.0; 253 | 254 | var existingSpring = false; 255 | 256 | var from = this.graph.getEdges(edge.source, edge.target); 257 | from.forEach(function(e) { 258 | if (existingSpring === false && typeof(this.edgeSprings[e.id]) !== 'undefined') { 259 | existingSpring = this.edgeSprings[e.id]; 260 | } 261 | }, this); 262 | 263 | if (existingSpring !== false) { 264 | return new Layout.ForceDirected.Spring(existingSpring.point1, existingSpring.point2, 0.0, 0.0); 265 | } 266 | 267 | var to = this.graph.getEdges(edge.target, edge.source); 268 | from.forEach(function(e){ 269 | if (existingSpring === false && typeof(this.edgeSprings[e.id]) !== 'undefined') { 270 | existingSpring = this.edgeSprings[e.id]; 271 | } 272 | }, this); 273 | 274 | if (existingSpring !== false) { 275 | return new Layout.ForceDirected.Spring(existingSpring.point2, existingSpring.point1, 0.0, 0.0); 276 | } 277 | 278 | this.edgeSprings[edge.id] = new Layout.ForceDirected.Spring( 279 | this.point(edge.source), this.point(edge.target), length, this.stiffness 280 | ); 281 | } 282 | 283 | return this.edgeSprings[edge.id]; 284 | }; 285 | 286 | // callback should accept two arguments: Node, Point 287 | Layout.ForceDirected.prototype.eachNode = function(callback) { 288 | var t = this; 289 | this.graph.nodes.forEach(function(n){ 290 | callback.call(t, n, t.point(n)); 291 | }); 292 | }; 293 | 294 | // callback should accept two arguments: Edge, Spring 295 | Layout.ForceDirected.prototype.eachEdge = function(callback) { 296 | var t = this; 297 | this.graph.edges.forEach(function(e){ 298 | callback.call(t, e, t.spring(e)); 299 | }); 300 | }; 301 | 302 | // callback should accept one argument: Spring 303 | Layout.ForceDirected.prototype.eachSpring = function(callback) { 304 | var t = this; 305 | this.graph.edges.forEach(function(e){ 306 | callback.call(t, t.spring(e)); 307 | }); 308 | }; 309 | 310 | 311 | // Physics stuff 312 | Layout.ForceDirected.prototype.applyCoulombsLaw = function() { 313 | this.eachNode(function(n1, point1) { 314 | this.eachNode(function(n2, point2) { 315 | if (point1 !== point2) 316 | { 317 | var d = point1.p.subtract(point2.p); 318 | var distance = d.magnitude() + 0.1; // avoid massive forces at small distances (and divide by zero) 319 | var direction = d.normalise(); 320 | 321 | // apply force to each end point 322 | point1.applyForce(direction.multiply(this.repulsion).divide(distance * distance * 0.5)); 323 | point2.applyForce(direction.multiply(this.repulsion).divide(distance * distance * -0.5)); 324 | } 325 | }); 326 | }); 327 | }; 328 | 329 | Layout.ForceDirected.prototype.applyHookesLaw = function() { 330 | this.eachSpring(function(spring){ 331 | var d = spring.point2.p.subtract(spring.point1.p); // the direction of the spring 332 | var displacement = spring.length - d.magnitude(); 333 | var direction = d.normalise(); 334 | 335 | // apply force to each end point 336 | spring.point1.applyForce(direction.multiply(spring.k * displacement * -0.5)); 337 | spring.point2.applyForce(direction.multiply(spring.k * displacement * 0.5)); 338 | }); 339 | }; 340 | 341 | Layout.ForceDirected.prototype.attractToCentre = function() { 342 | this.eachNode(function(node, point) { 343 | var direction = point.p.multiply(-1.0); 344 | point.applyForce(direction.multiply(this.repulsion / 50.0)); 345 | }); 346 | }; 347 | 348 | 349 | Layout.ForceDirected.prototype.updateVelocity = function(timestep) { 350 | this.eachNode(function(node, point) { 351 | // Is this, along with updatePosition below, the only places that your 352 | // integration code exist? 353 | point.v = point.v.add(point.a.multiply(timestep)).multiply(this.damping); 354 | point.a = new gVector(0,0); 355 | }); 356 | }; 357 | 358 | Layout.ForceDirected.prototype.updatePosition = function(timestep) { 359 | this.eachNode(function(node, point) { 360 | // Same question as above; along with updateVelocity, is this all of 361 | // your integration code? 362 | point.p = point.p.add(point.v.multiply(timestep)); 363 | }); 364 | }; 365 | 366 | // Calculate the total kinetic energy of the system 367 | Layout.ForceDirected.prototype.totalEnergy = function(timestep) { 368 | var energy = 0.0; 369 | this.eachNode(function(node, point) { 370 | var speed = point.v.magnitude(); 371 | energy += 0.5 * point.m * speed * speed; 372 | }); 373 | 374 | return energy; 375 | }; 376 | 377 | 378 | // Find the nearest point to a particular position 379 | Layout.ForceDirected.prototype.nearest = function(pos) { 380 | var min = {node: null, point: null, distance: null}; 381 | var t = this; 382 | this.graph.nodes.forEach(function(n){ 383 | var point = t.point(n); 384 | var distance = point.p.subtract(pos).magnitude(); 385 | 386 | if (min.distance === null || distance < min.distance) { 387 | min = {node: n, point: point, distance: distance}; 388 | } 389 | }); 390 | 391 | return min; 392 | }; 393 | 394 | // returns [bottomleft, topright] 395 | Layout.ForceDirected.prototype.getBoundingBox = function() { 396 | var bottomleft = new gVector(-2,-2); 397 | var topright = new gVector(2,2); 398 | 399 | this.eachNode(function(n, point) { 400 | if (point.p.x < bottomleft.x) { 401 | bottomleft.x = point.p.x; 402 | } 403 | if (point.p.y < bottomleft.y) { 404 | bottomleft.y = point.p.y; 405 | } 406 | if (point.p.x > topright.x) { 407 | topright.x = point.p.x; 408 | } 409 | if (point.p.y > topright.y) { 410 | topright.y = point.p.y; 411 | } 412 | }); 413 | 414 | var padding = topright.subtract(bottomleft).multiply(0.07); // ~5% padding 415 | 416 | return {bottomleft: bottomleft.subtract(padding), topright: topright.add(padding)}; 417 | }; 418 | 419 | 420 | // gVector 421 | var gVector = function(x, y) { 422 | this.x = x; 423 | this.y = y; 424 | }; 425 | 426 | gVector.random = function(fn) { 427 | return new gVector(10.0 * (fn() - 0.5), 10.0 * (fn() - 0.5)); 428 | }; 429 | 430 | gVector.prototype.add = function(v2) { 431 | return new gVector(this.x + v2.x, this.y + v2.y); 432 | }; 433 | 434 | gVector.prototype.subtract = function(v2) { 435 | return new gVector(this.x - v2.x, this.y - v2.y); 436 | }; 437 | 438 | gVector.prototype.multiply = function(n) { 439 | return new gVector(this.x * n, this.y * n); 440 | }; 441 | 442 | gVector.prototype.divide = function(n) { 443 | return new gVector((this.x / n) || 0, (this.y / n) || 0); // Avoid divide by zero errors.. 444 | }; 445 | 446 | gVector.prototype.magnitude = function() { 447 | return Math.sqrt(this.x*this.x + this.y*this.y); 448 | }; 449 | 450 | gVector.prototype.normal = function() { 451 | return new gVector(-this.y, this.x); 452 | }; 453 | 454 | gVector.prototype.normalise = function() { 455 | return this.divide(this.magnitude()); 456 | }; 457 | 458 | // Point 459 | Layout.ForceDirected.Point = function(position, mass) { 460 | this.p = position; // position 461 | this.m = mass; // mass 462 | this.v = new gVector(0, 0); // velocity 463 | this.a = new gVector(0, 0); // acceleration 464 | }; 465 | 466 | Layout.ForceDirected.Point.prototype.applyForce = function(force) { 467 | this.a = this.a.add(force.divide(this.m)); 468 | }; 469 | 470 | // Spring 471 | Layout.ForceDirected.Spring = function(point1, point2, length, k) { 472 | this.point1 = point1; 473 | this.point2 = point2; 474 | this.length = length; // spring length at rest 475 | this.k = k; // spring constant (See Hooke's law) .. how stiff the spring is 476 | }; 477 | 478 | -------------------------------------------------------------------------------- /vendor/jstat/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 jStat 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /vendor/random.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | /* eslint-disable */ 3 | /* 4 | Copyright 2019 David Bau. 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | export let SeedRandom = {}; 24 | 25 | (function (global, pool, math) { 26 | // 27 | // The following constants are related to IEEE 754 limits. 28 | // 29 | 30 | var width = 256, // each RC4 output is 0 <= x < 256 31 | chunks = 6, // at least six RC4 outputs for each double 32 | digits = 52, // there are 52 significant digits in a double 33 | rngname = 'random', // rngname: name for Math.random and Math.seedrandom 34 | startdenom = math.pow(width, chunks), 35 | significance = math.pow(2, digits), 36 | overflow = significance * 2, 37 | mask = width - 1, 38 | nodecrypto; // node.js crypto module, initialized at the bottom. 39 | 40 | // 41 | // seedrandom() 42 | // This is the seedrandom function described above. 43 | // 44 | function seedrandom(seed, options, callback) { 45 | var key = []; 46 | options = (options == true) ? { entropy: true } : (options || {}); 47 | 48 | // Flatten the seed string or build one from local entropy if needed. 49 | var shortseed = mixkey(flatten( 50 | options.entropy ? [seed, tostring(pool)] : 51 | (seed == null) ? autoseed() : seed, 3), key); 52 | 53 | // Use the seed to initialize an ARC4 generator. 54 | var arc4 = new ARC4(key); 55 | 56 | // This function returns a random double in [0, 1) that contains 57 | // randomness in every bit of the mantissa of the IEEE 754 value. 58 | var prng = function() { 59 | var n = arc4.g(chunks), // Start with a numerator n < 2 ^ 48 60 | d = startdenom, // and denominator d = 2 ^ 48. 61 | x = 0; // and no 'extra last byte'. 62 | while (n < significance) { // Fill up all significant digits by 63 | n = (n + x) * width; // shifting numerator and 64 | d *= width; // denominator and generating a 65 | x = arc4.g(1); // new least-significant-byte. 66 | } 67 | while (n >= overflow) { // To avoid rounding up, before adding 68 | n /= 2; // last byte, shift everything 69 | d /= 2; // right using integer math until 70 | x >>>= 1; // we have exactly the desired bits. 71 | } 72 | return (n + x) / d; // Form the number within [0, 1). 73 | }; 74 | 75 | prng.int32 = function() { return arc4.g(4) | 0; } 76 | prng.quick = function() { return arc4.g(4) / 0x100000000; } 77 | prng.double = prng; 78 | 79 | // Mix the randomness into accumulated entropy. 80 | mixkey(tostring(arc4.S), pool); 81 | 82 | // Calling convention: what to return as a function of prng, seed, is_math. 83 | return (options.pass || callback || 84 | function(prng, seed, is_math_call, state) { 85 | if (state) { 86 | // Load the arc4 state from the given state if it has an S array. 87 | if (state.S) { copy(state, arc4); } 88 | // Only provide the .state method if requested via options.state. 89 | prng.state = function() { return copy(arc4, {}); } 90 | } 91 | 92 | // If called as a method of Math (Math.seedrandom()), mutate 93 | // Math.random because that is how seedrandom.js has worked since v1.0. 94 | if (is_math_call) { math[rngname] = prng; return seed; } 95 | 96 | // Otherwise, it is a newer calling convention, so return the 97 | // prng directly. 98 | else return prng; 99 | })( 100 | prng, 101 | shortseed, 102 | 'global' in options ? options.global : (this == math), 103 | options.state); 104 | } 105 | 106 | // 107 | // ARC4 108 | // 109 | // An ARC4 implementation. The constructor takes a key in the form of 110 | // an array of at most (width) integers that should be 0 <= x < (width). 111 | // 112 | // The g(count) method returns a pseudorandom integer that concatenates 113 | // the next (count) outputs from ARC4. Its return value is a number x 114 | // that is in the range 0 <= x < (width ^ count). 115 | // 116 | function ARC4(key) { 117 | var t, keylen = key.length, 118 | me = this, i = 0, j = me.i = me.j = 0, s = me.S = []; 119 | 120 | // The empty key [] is treated as [0]. 121 | if (!keylen) { key = [keylen++]; } 122 | 123 | // Set up S using the standard key scheduling algorithm. 124 | while (i < width) { 125 | s[i] = i++; 126 | } 127 | for (i = 0; i < width; i++) { 128 | s[i] = s[j = mask & (j + key[i % keylen] + (t = s[i]))]; 129 | s[j] = t; 130 | } 131 | 132 | // The "g" method returns the next (count) outputs as one number. 133 | (me.g = function(count) { 134 | // Using instance members instead of closure state nearly doubles speed. 135 | var t, r = 0, 136 | i = me.i, j = me.j, s = me.S; 137 | while (count--) { 138 | t = s[i = mask & (i + 1)]; 139 | r = r * width + s[mask & ((s[i] = s[j = mask & (j + t)]) + (s[j] = t))]; 140 | } 141 | me.i = i; me.j = j; 142 | return r; 143 | // For robust unpredictability, the function call below automatically 144 | // discards an initial batch of values. This is called RC4-drop[256]. 145 | // See http://google.com/search?q=rsa+fluhrer+response&btnI 146 | })(width); 147 | } 148 | 149 | // 150 | // copy() 151 | // Copies internal state of ARC4 to or from a plain object. 152 | // 153 | function copy(f, t) { 154 | t.i = f.i; 155 | t.j = f.j; 156 | t.S = f.S.slice(); 157 | return t; 158 | }; 159 | 160 | // 161 | // flatten() 162 | // Converts an object tree to nested arrays of strings. 163 | // 164 | function flatten(obj, depth) { 165 | var result = [], typ = (typeof obj), prop; 166 | if (depth && typ == 'object') { 167 | for (prop in obj) { 168 | try { result.push(flatten(obj[prop], depth - 1)); } catch (e) {} 169 | } 170 | } 171 | return (result.length ? result : typ == 'string' ? obj : obj + '\0'); 172 | } 173 | 174 | // 175 | // mixkey() 176 | // Mixes a string seed into a key that is an array of integers, and 177 | // returns a shortened string seed that is equivalent to the result key. 178 | // 179 | function mixkey(seed, key) { 180 | var stringseed = seed + '', smear, j = 0; 181 | while (j < stringseed.length) { 182 | key[mask & j] = 183 | mask & ((smear ^= key[mask & j] * 19) + stringseed.charCodeAt(j++)); 184 | } 185 | return tostring(key); 186 | } 187 | 188 | // 189 | // autoseed() 190 | // Returns an object for autoseeding, using window.crypto and Node crypto 191 | // module if available. 192 | // 193 | function autoseed() { 194 | try { 195 | var out; 196 | if (nodecrypto && (out = nodecrypto.randomBytes)) { 197 | // The use of 'out' to remember randomBytes makes tight minified code. 198 | out = out(width); 199 | } else { 200 | out = new Uint8Array(width); 201 | (global.crypto || global.msCrypto).getRandomValues(out); 202 | } 203 | return tostring(out); 204 | } catch (e) { 205 | var browser = global.navigator, 206 | plugins = browser && browser.plugins; 207 | return [+new Date, global, plugins, global.screen, tostring(pool)]; 208 | } 209 | } 210 | 211 | // 212 | // tostring() 213 | // Converts an array of charcodes to a string 214 | // 215 | function tostring(a) { 216 | return String.fromCharCode.apply(0, a); 217 | } 218 | 219 | // 220 | // When seedrandom.js is loaded, we immediately mix a few bits 221 | // from the built-in RNG into the entropy pool. Because we do 222 | // not want to interfere with deterministic PRNG state later, 223 | // seedrandom will not call math.random on its own again after 224 | // initialization. 225 | // 226 | mixkey(math.random(), pool); 227 | 228 | SeedRandom.seedrandom = seedrandom; 229 | 230 | 231 | // End anonymous scope, and pass initial values. 232 | })( 233 | // global: `self` in browsers (including strict mode and web workers), 234 | // otherwise `this` in Node and other environments 235 | (typeof self !== 'undefined') ? self : this, 236 | [], // pool: entropy pool starts empty 237 | Math // math: package containing random, pow, and seedrandom 238 | ); -------------------------------------------------------------------------------- /vendor/toposort.js: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | /* eslint-disable */ 3 | 4 | /** 5 | * Toposort - Topological sorting for node.js 6 | Copyright (c) 2012 by Marcel Klehr 7 | MIT LICENSE 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | */ 26 | 27 | 28 | 29 | /** 30 | * Topological sorting function 31 | * 32 | * @param {Array} edges 33 | * @returns {Array} 34 | */ 35 | 36 | export default function(edges) { 37 | return toposort(uniqueNodes(edges), edges); 38 | } 39 | 40 | 41 | function toposort(nodes, edges) { 42 | let cursor = nodes.length 43 | , sorted = new Array(cursor) 44 | , visited = {} 45 | , i = cursor 46 | // Better data structures make algorithm much faster. 47 | , outgoingEdges = makeOutgoingEdges(edges) 48 | , nodesHash = makeNodesHash(nodes); 49 | 50 | // check for unknown nodes 51 | edges.forEach((edge) => { 52 | if (!nodesHash.has(edge[0]) || !nodesHash.has(edge[1])) { 53 | throw new Error("Unknown node. There is an unknown node in the supplied edges."); 54 | } 55 | }); 56 | 57 | while (i--) { 58 | if (!visited[i]) visit(nodes[i], i, new Set()); 59 | } 60 | 61 | return sorted; 62 | 63 | function visit(node, i, predecessors) { 64 | if(predecessors.has(node)) { 65 | let nodeRep; 66 | try { 67 | nodeRep = ", node was:" + JSON.stringify(node); 68 | } catch(e) { 69 | nodeRep = ""; 70 | } 71 | throw new Error("Cyclic dependency" + nodeRep); 72 | } 73 | 74 | if (!nodesHash.has(node)) { 75 | throw new Error("Found unknown node. Make sure to provided all involved nodes. Unknown node: "+JSON.stringify(node)); 76 | } 77 | 78 | if (visited[i]) return; 79 | visited[i] = true; 80 | 81 | let outgoing = outgoingEdges.get(node) || new Set(); 82 | outgoing = Array.from(outgoing); 83 | 84 | if (i = outgoing.length) { 85 | predecessors.add(node); 86 | do { 87 | let child = outgoing[--i]; 88 | visit(child, nodesHash.get(child), predecessors); 89 | } while (i); 90 | predecessors.delete(node); 91 | } 92 | 93 | sorted[--cursor] = node; 94 | } 95 | } 96 | 97 | function uniqueNodes(arr){ 98 | let res = new Set(); 99 | for (let i = 0, len = arr.length; i < len; i++) { 100 | let edge = arr[i]; 101 | res.add(edge[0]); 102 | res.add(edge[1]); 103 | } 104 | return Array.from(res); 105 | } 106 | 107 | function makeOutgoingEdges(arr){ 108 | let edges = new Map(); 109 | for (let i = 0, len = arr.length; i < len; i++) { 110 | let edge = arr[i]; 111 | if (!edges.has(edge[0])) edges.set(edge[0], new Set()); 112 | if (!edges.has(edge[1])) edges.set(edge[1], new Set()); 113 | edges.get(edge[0]).add(edge[1]); 114 | } 115 | return edges; 116 | } 117 | 118 | function makeNodesHash(arr){ 119 | let res = new Map(); 120 | for (let i = 0, len = arr.length; i < len; i++) { 121 | res.set(arr[i], i); 122 | } 123 | return res; 124 | } -------------------------------------------------------------------------------- /vendor/xmldom/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 - present Christopher J. Brody and other contributors, as listed in: https://github.com/xmldom/xmldom/graphs/contributors 2 | Copyright 2012 - 2017 @jindw and other contributors, as listed in: https://github.com/jindw/xmldom/graphs/contributors 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /vendor/xmldom/conventions.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | /* eslint-disable */ 3 | 4 | /** 5 | * "Shallow freezes" an object to render it immutable. 6 | * Uses `Object.freeze` if available, 7 | * otherwise the immutability is only in the type. 8 | * 9 | * Is used to create "enum like" objects. 10 | * 11 | * @template T 12 | * @param {T} object the object to freeze 13 | * @param {Pick = Object} oc `Object` by default, 14 | * allows to inject custom object constructor for tests 15 | * @returns {Readonly} 16 | * 17 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze 18 | */ 19 | export function freeze(object, oc) { 20 | if (oc === undefined) { 21 | oc = Object 22 | } 23 | return oc && typeof oc.freeze === 'function' ? oc.freeze(object) : object 24 | } 25 | 26 | /** 27 | * All mime types that are allowed as input to `DOMParser.parseFromString` 28 | * 29 | * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 MDN 30 | * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparsersupportedtype WHATWG HTML Spec 31 | * @see DOMParser.prototype.parseFromString 32 | */ 33 | export let MIME_TYPE = freeze({ 34 | /** 35 | * `text/html`, the only mime type that triggers treating an XML document as HTML. 36 | * 37 | * @see DOMParser.SupportedType.isHTML 38 | * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration 39 | * @see https://en.wikipedia.org/wiki/HTML Wikipedia 40 | * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN 41 | * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring WHATWG HTML Spec 42 | */ 43 | HTML: 'text/html', 44 | 45 | /** 46 | * Helper method to check a mime type if it indicates an HTML document 47 | * 48 | * @param {string} [value] 49 | * @returns {boolean} 50 | * 51 | * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration 52 | * @see https://en.wikipedia.org/wiki/HTML Wikipedia 53 | * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN 54 | * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring */ 55 | isHTML: function (value) { 56 | return value === MIME_TYPE.HTML 57 | }, 58 | 59 | /** 60 | * `application/xml`, the standard mime type for XML documents. 61 | * 62 | * @see https://www.iana.org/assignments/media-types/application/xml IANA MimeType registration 63 | * @see https://tools.ietf.org/html/rfc7303#section-9.1 RFC 7303 64 | * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia 65 | */ 66 | XML_APPLICATION: 'application/xml', 67 | 68 | /** 69 | * `text/html`, an alias for `application/xml`. 70 | * 71 | * @see https://tools.ietf.org/html/rfc7303#section-9.2 RFC 7303 72 | * @see https://www.iana.org/assignments/media-types/text/xml IANA MimeType registration 73 | * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia 74 | */ 75 | XML_TEXT: 'text/xml', 76 | 77 | /** 78 | * `application/xhtml+xml`, indicates an XML document that has the default HTML namespace, 79 | * but is parsed as an XML document. 80 | * 81 | * @see https://www.iana.org/assignments/media-types/application/xhtml+xml IANA MimeType registration 82 | * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument WHATWG DOM Spec 83 | * @see https://en.wikipedia.org/wiki/XHTML Wikipedia 84 | */ 85 | XML_XHTML_APPLICATION: 'application/xhtml+xml', 86 | 87 | /** 88 | * `image/svg+xml`, 89 | * 90 | * @see https://www.iana.org/assignments/media-types/image/svg+xml IANA MimeType registration 91 | * @see https://www.w3.org/TR/SVG11/ W3C SVG 1.1 92 | * @see https://en.wikipedia.org/wiki/Scalable_Vector_Graphics Wikipedia 93 | */ 94 | XML_SVG_IMAGE: 'image/svg+xml', 95 | }) 96 | 97 | /** 98 | * Namespaces that are used in this code base. 99 | * 100 | * @see http://www.w3.org/TR/REC-xml-names 101 | */ 102 | export let NAMESPACE = freeze({ 103 | /** 104 | * The XHTML namespace. 105 | * 106 | * @see http://www.w3.org/1999/xhtml 107 | */ 108 | HTML: 'http://www.w3.org/1999/xhtml', 109 | 110 | /** 111 | * Checks if `uri` equals `NAMESPACE.HTML`. 112 | * 113 | * @param {string} [uri] 114 | * 115 | * @see NAMESPACE.HTML 116 | */ 117 | isHTML: function (uri) { 118 | return uri === NAMESPACE.HTML 119 | }, 120 | 121 | /** 122 | * The SVG namespace. 123 | * 124 | * @see http://www.w3.org/2000/svg 125 | */ 126 | SVG: 'http://www.w3.org/2000/svg', 127 | 128 | /** 129 | * The `xml:` namespace. 130 | * 131 | * @see http://www.w3.org/XML/1998/namespace 132 | */ 133 | XML: 'http://www.w3.org/XML/1998/namespace', 134 | 135 | /** 136 | * The `xmlns:` namespace 137 | * 138 | * @see https://www.w3.org/2000/xmlns/ 139 | */ 140 | XMLNS: 'http://www.w3.org/2000/xmlns/', 141 | }) -------------------------------------------------------------------------------- /vendor/xmldom/dom-parser.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | /* eslint-disable */ 3 | 4 | import * as conventions from './conventions.js'; 5 | import * as entities from './entities.js'; 6 | import { DOMImplementation as DI, XMLSerializer as XS } from './dom.js'; 7 | import * as sax from './sax.js'; 8 | 9 | export let DOMImplementation = DI; 10 | export let XMLSerializer = XS; 11 | 12 | var NAMESPACE = conventions.NAMESPACE; 13 | 14 | export function DOMParser(options){ 15 | this.options = options ||{locator:{}}; 16 | } 17 | 18 | DOMParser.prototype.parseFromString = function(source,mimeType){ 19 | var options = this.options; 20 | var sax = new XMLReader(); 21 | var domBuilder = options.domBuilder || new DOMHandler();//contentHandler and LexicalHandler 22 | var errorHandler = options.errorHandler; 23 | var locator = options.locator; 24 | var defaultNSMap = options.xmlns||{}; 25 | var isHTML = /\/x?html?$/.test(mimeType);//mimeType.toLowerCase().indexOf('html') > -1; 26 | var entityMap = isHTML ? entities.HTML_ENTITIES : entities.XML_ENTITIES; 27 | if(locator){ 28 | domBuilder.setDocumentLocator(locator) 29 | } 30 | 31 | sax.errorHandler = buildErrorHandler(errorHandler,domBuilder,locator); 32 | sax.domBuilder = options.domBuilder || domBuilder; 33 | if(isHTML){ 34 | defaultNSMap[''] = NAMESPACE.HTML; 35 | } 36 | defaultNSMap.xml = defaultNSMap.xml || NAMESPACE.XML; 37 | if(source && typeof source === 'string'){ 38 | sax.parse(source,defaultNSMap,entityMap); 39 | }else{ 40 | sax.errorHandler.error("invalid doc source"); 41 | } 42 | return domBuilder.doc; 43 | } 44 | function buildErrorHandler(errorImpl,domBuilder,locator){ 45 | if(!errorImpl){ 46 | if(domBuilder instanceof DOMHandler){ 47 | return domBuilder; 48 | } 49 | errorImpl = domBuilder ; 50 | } 51 | var errorHandler = {} 52 | var isCallback = errorImpl instanceof Function; 53 | locator = locator||{} 54 | function build(key){ 55 | var fn = errorImpl[key]; 56 | if(!fn && isCallback){ 57 | fn = errorImpl.length == 2?function(msg){errorImpl(key,msg)}:errorImpl; 58 | } 59 | errorHandler[key] = fn && function(msg){ 60 | fn('[xmldom '+key+']\t'+msg+_locator(locator)); 61 | }||function(){}; 62 | } 63 | build('warning'); 64 | build('error'); 65 | build('fatalError'); 66 | return errorHandler; 67 | } 68 | 69 | //console.log('#\n\n\n\n\n\n\n####') 70 | /** 71 | * +ContentHandler+ErrorHandler 72 | * +LexicalHandler+EntityResolver2 73 | * -DeclHandler-DTDHandler 74 | * 75 | * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler 76 | * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2 77 | * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html 78 | */ 79 | function DOMHandler() { 80 | this.cdata = false; 81 | } 82 | function position(locator,node){ 83 | node.lineNumber = locator.lineNumber; 84 | node.columnNumber = locator.columnNumber; 85 | } 86 | /** 87 | * @see org.xml.sax.ContentHandler#startDocument 88 | * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html 89 | */ 90 | DOMHandler.prototype = { 91 | startDocument : function() { 92 | this.doc = new DOMImplementation().createDocument(null, null, null); 93 | if (this.locator) { 94 | this.doc.documentURI = this.locator.systemId; 95 | } 96 | }, 97 | startElement:function(namespaceURI, localName, qName, attrs) { 98 | var doc = this.doc; 99 | var el = doc.createElementNS(namespaceURI, qName||localName); 100 | var len = attrs.length; 101 | appendElement(this, el); 102 | this.currentElement = el; 103 | 104 | this.locator && position(this.locator,el) 105 | for (var i = 0 ; i < len; i++) { 106 | var namespaceURI = attrs.getURI(i); 107 | var value = attrs.getValue(i); 108 | var qName = attrs.getQName(i); 109 | var attr = doc.createAttributeNS(namespaceURI, qName); 110 | this.locator &&position(attrs.getLocator(i),attr); 111 | attr.value = attr.nodeValue = value; 112 | el.setAttributeNode(attr) 113 | } 114 | }, 115 | endElement:function(namespaceURI, localName, qName) { 116 | var current = this.currentElement 117 | var tagName = current.tagName; 118 | this.currentElement = current.parentNode; 119 | }, 120 | startPrefixMapping:function(prefix, uri) { 121 | }, 122 | endPrefixMapping:function(prefix) { 123 | }, 124 | processingInstruction:function(target, data) { 125 | var ins = this.doc.createProcessingInstruction(target, data); 126 | this.locator && position(this.locator,ins) 127 | appendElement(this, ins); 128 | }, 129 | ignorableWhitespace:function(ch, start, length) { 130 | }, 131 | characters:function(chars, start, length) { 132 | chars = _toString.apply(this,arguments) 133 | //console.log(chars) 134 | if(chars){ 135 | if (this.cdata) { 136 | var charNode = this.doc.createCDATASection(chars); 137 | } else { 138 | var charNode = this.doc.createTextNode(chars); 139 | } 140 | if(this.currentElement){ 141 | this.currentElement.appendChild(charNode); 142 | }else if(/^\s*$/.test(chars)){ 143 | this.doc.appendChild(charNode); 144 | //process xml 145 | } 146 | this.locator && position(this.locator,charNode) 147 | } 148 | }, 149 | skippedEntity:function(name) { 150 | }, 151 | endDocument:function() { 152 | this.doc.normalize(); 153 | }, 154 | setDocumentLocator:function (locator) { 155 | if(this.locator = locator){// && !('lineNumber' in locator)){ 156 | locator.lineNumber = 0; 157 | } 158 | }, 159 | //LexicalHandler 160 | comment:function(chars, start, length) { 161 | chars = _toString.apply(this,arguments) 162 | var comm = this.doc.createComment(chars); 163 | this.locator && position(this.locator,comm) 164 | appendElement(this, comm); 165 | }, 166 | 167 | startCDATA:function() { 168 | //used in characters() methods 169 | this.cdata = true; 170 | }, 171 | endCDATA:function() { 172 | this.cdata = false; 173 | }, 174 | 175 | startDTD:function(name, publicId, systemId) { 176 | var impl = this.doc.implementation; 177 | if (impl && impl.createDocumentType) { 178 | var dt = impl.createDocumentType(name, publicId, systemId); 179 | this.locator && position(this.locator,dt) 180 | appendElement(this, dt); 181 | } 182 | }, 183 | /** 184 | * @see org.xml.sax.ErrorHandler 185 | * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html 186 | */ 187 | warning:function(error) { 188 | console.warn('[xmldom warning]\t'+error,_locator(this.locator)); 189 | }, 190 | error:function(error) { 191 | console.error('[xmldom error]\t'+error,_locator(this.locator)); 192 | }, 193 | fatalError:function(error) { 194 | throw new ParseError(error, this.locator); 195 | } 196 | } 197 | function _locator(l){ 198 | if(l){ 199 | return '\n@'+(l.systemId ||'')+'#[line:'+l.lineNumber+',col:'+l.columnNumber+']' 200 | } 201 | } 202 | function _toString(chars,start,length){ 203 | if(typeof chars == 'string'){ 204 | return chars.substr(start,length) 205 | }else{//java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)") 206 | if(chars.length >= start+length || start){ 207 | return new java.lang.String(chars,start,length)+''; 208 | } 209 | return chars; 210 | } 211 | } 212 | 213 | /* 214 | * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html 215 | * used method of org.xml.sax.ext.LexicalHandler: 216 | * #comment(chars, start, length) 217 | * #startCDATA() 218 | * #endCDATA() 219 | * #startDTD(name, publicId, systemId) 220 | * 221 | * 222 | * IGNORED method of org.xml.sax.ext.LexicalHandler: 223 | * #endDTD() 224 | * #startEntity(name) 225 | * #endEntity(name) 226 | * 227 | * 228 | * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html 229 | * IGNORED method of org.xml.sax.ext.DeclHandler 230 | * #attributeDecl(eName, aName, type, mode, value) 231 | * #elementDecl(name, model) 232 | * #externalEntityDecl(name, publicId, systemId) 233 | * #internalEntityDecl(name, value) 234 | * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html 235 | * IGNORED method of org.xml.sax.EntityResolver2 236 | * #resolveEntity(String name,String publicId,String baseURI,String systemId) 237 | * #resolveEntity(publicId, systemId) 238 | * #getExternalSubset(name, baseURI) 239 | * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html 240 | * IGNORED method of org.xml.sax.DTDHandler 241 | * #notationDecl(name, publicId, systemId) {}; 242 | * #unparsedEntityDecl(name, publicId, systemId, notationName) {}; 243 | */ 244 | "endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g,function(key){ 245 | DOMHandler.prototype[key] = function(){return null} 246 | }) 247 | 248 | /* Private static helpers treated below as private instance methods, so don't need to add these to the public API; we might use a Relator to also get rid of non-standard public properties */ 249 | function appendElement (hander,node) { 250 | if (!hander.currentElement) { 251 | hander.doc.appendChild(node); 252 | } else { 253 | hander.currentElement.appendChild(node); 254 | } 255 | }//appendChild and setAttributeNS are preformance key 256 | 257 | //if(typeof require == 'function'){; 258 | var XMLReader = sax.XMLReader; 259 | var ParseError = sax.ParseError; 260 | 261 | export let __DOMHandler = DOMHandler; 262 | //} 263 | -------------------------------------------------------------------------------- /vendor/xmldom/entities.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | /* eslint-disable */ 3 | 4 | import { freeze } from './conventions.js'; 5 | 6 | /** 7 | * The entities that are predefined in every XML document. 8 | * 9 | * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-predefined-ent W3C XML 1.1 10 | * @see https://www.w3.org/TR/2008/REC-xml-20081126/#sec-predefined-ent W3C XML 1.0 11 | * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Predefined_entities_in_XML Wikipedia 12 | */ 13 | export let XML_ENTITIES = freeze({amp:'&', apos:"'", gt:'>', lt:'<', quot:'"'}) 14 | 15 | /** 16 | * A map of currently 241 entities that are detected in an HTML document. 17 | * They contain all entries from `XML_ENTITIES`. 18 | * 19 | * @see XML_ENTITIES 20 | * @see DOMParser.parseFromString 21 | * @see DOMImplementation.prototype.createHTMLDocument 22 | * @see https://html.spec.whatwg.org/#named-character-references WHATWG HTML(5) Spec 23 | * @see https://www.w3.org/TR/xml-entity-names/ W3C XML Entity Names 24 | * @see https://www.w3.org/TR/html4/sgml/entities.html W3C HTML4/SGML 25 | * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Character_entity_references_in_HTML Wikipedia (HTML) 26 | * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Entities_representing_special_characters_in_XHTML Wikpedia (XHTML) 27 | */ 28 | export let HTML_ENTITIES = freeze({ 29 | lt: '<', 30 | gt: '>', 31 | amp: '&', 32 | quot: '"', 33 | apos: "'", 34 | Agrave: "À", 35 | Aacute: "Á", 36 | Acirc: "Â", 37 | Atilde: "Ã", 38 | Auml: "Ä", 39 | Aring: "Å", 40 | AElig: "Æ", 41 | Ccedil: "Ç", 42 | Egrave: "È", 43 | Eacute: "É", 44 | Ecirc: "Ê", 45 | Euml: "Ë", 46 | Igrave: "Ì", 47 | Iacute: "Í", 48 | Icirc: "Î", 49 | Iuml: "Ï", 50 | ETH: "Ð", 51 | Ntilde: "Ñ", 52 | Ograve: "Ò", 53 | Oacute: "Ó", 54 | Ocirc: "Ô", 55 | Otilde: "Õ", 56 | Ouml: "Ö", 57 | Oslash: "Ø", 58 | Ugrave: "Ù", 59 | Uacute: "Ú", 60 | Ucirc: "Û", 61 | Uuml: "Ü", 62 | Yacute: "Ý", 63 | THORN: "Þ", 64 | szlig: "ß", 65 | agrave: "à", 66 | aacute: "á", 67 | acirc: "â", 68 | atilde: "ã", 69 | auml: "ä", 70 | aring: "å", 71 | aelig: "æ", 72 | ccedil: "ç", 73 | egrave: "è", 74 | eacute: "é", 75 | ecirc: "ê", 76 | euml: "ë", 77 | igrave: "ì", 78 | iacute: "í", 79 | icirc: "î", 80 | iuml: "ï", 81 | eth: "ð", 82 | ntilde: "ñ", 83 | ograve: "ò", 84 | oacute: "ó", 85 | ocirc: "ô", 86 | otilde: "õ", 87 | ouml: "ö", 88 | oslash: "ø", 89 | ugrave: "ù", 90 | uacute: "ú", 91 | ucirc: "û", 92 | uuml: "ü", 93 | yacute: "ý", 94 | thorn: "þ", 95 | yuml: "ÿ", 96 | nbsp: "\u00a0", 97 | iexcl: "¡", 98 | cent: "¢", 99 | pound: "£", 100 | curren: "¤", 101 | yen: "¥", 102 | brvbar: "¦", 103 | sect: "§", 104 | uml: "¨", 105 | copy: "©", 106 | ordf: "ª", 107 | laquo: "«", 108 | not: "¬", 109 | shy: "­­", 110 | reg: "®", 111 | macr: "¯", 112 | deg: "°", 113 | plusmn: "±", 114 | sup2: "²", 115 | sup3: "³", 116 | acute: "´", 117 | micro: "µ", 118 | para: "¶", 119 | middot: "·", 120 | cedil: "¸", 121 | sup1: "¹", 122 | ordm: "º", 123 | raquo: "»", 124 | frac14: "¼", 125 | frac12: "½", 126 | frac34: "¾", 127 | iquest: "¿", 128 | times: "×", 129 | divide: "÷", 130 | forall: "∀", 131 | part: "∂", 132 | exist: "∃", 133 | empty: "∅", 134 | nabla: "∇", 135 | isin: "∈", 136 | notin: "∉", 137 | ni: "∋", 138 | prod: "∏", 139 | sum: "∑", 140 | minus: "−", 141 | lowast: "∗", 142 | radic: "√", 143 | prop: "∝", 144 | infin: "∞", 145 | ang: "∠", 146 | and: "∧", 147 | or: "∨", 148 | cap: "∩", 149 | cup: "∪", 150 | 'int': "∫", 151 | there4: "∴", 152 | sim: "∼", 153 | cong: "≅", 154 | asymp: "≈", 155 | ne: "≠", 156 | equiv: "≡", 157 | le: "≤", 158 | ge: "≥", 159 | sub: "⊂", 160 | sup: "⊃", 161 | nsub: "⊄", 162 | sube: "⊆", 163 | supe: "⊇", 164 | oplus: "⊕", 165 | otimes: "⊗", 166 | perp: "⊥", 167 | sdot: "⋅", 168 | Alpha: "Α", 169 | Beta: "Β", 170 | Gamma: "Γ", 171 | Delta: "Δ", 172 | Epsilon: "Ε", 173 | Zeta: "Ζ", 174 | Eta: "Η", 175 | Theta: "Θ", 176 | Iota: "Ι", 177 | Kappa: "Κ", 178 | Lambda: "Λ", 179 | Mu: "Μ", 180 | Nu: "Ν", 181 | Xi: "Ξ", 182 | Omicron: "Ο", 183 | Pi: "Π", 184 | Rho: "Ρ", 185 | Sigma: "Σ", 186 | Tau: "Τ", 187 | Upsilon: "Υ", 188 | Phi: "Φ", 189 | Chi: "Χ", 190 | Psi: "Ψ", 191 | Omega: "Ω", 192 | alpha: "α", 193 | beta: "β", 194 | gamma: "γ", 195 | delta: "δ", 196 | epsilon: "ε", 197 | zeta: "ζ", 198 | eta: "η", 199 | theta: "θ", 200 | iota: "ι", 201 | kappa: "κ", 202 | lambda: "λ", 203 | mu: "μ", 204 | nu: "ν", 205 | xi: "ξ", 206 | omicron: "ο", 207 | pi: "π", 208 | rho: "ρ", 209 | sigmaf: "ς", 210 | sigma: "σ", 211 | tau: "τ", 212 | upsilon: "υ", 213 | phi: "φ", 214 | chi: "χ", 215 | psi: "ψ", 216 | omega: "ω", 217 | thetasym: "ϑ", 218 | upsih: "ϒ", 219 | piv: "ϖ", 220 | OElig: "Œ", 221 | oelig: "œ", 222 | Scaron: "Š", 223 | scaron: "š", 224 | Yuml: "Ÿ", 225 | fnof: "ƒ", 226 | circ: "ˆ", 227 | tilde: "˜", 228 | ensp: " ", 229 | emsp: " ", 230 | thinsp: " ", 231 | zwnj: "‌", 232 | zwj: "‍", 233 | lrm: "‎", 234 | rlm: "‏", 235 | ndash: "–", 236 | mdash: "—", 237 | lsquo: "‘", 238 | rsquo: "’", 239 | sbquo: "‚", 240 | ldquo: "“", 241 | rdquo: "”", 242 | bdquo: "„", 243 | dagger: "†", 244 | Dagger: "‡", 245 | bull: "•", 246 | hellip: "…", 247 | permil: "‰", 248 | prime: "′", 249 | Prime: "″", 250 | lsaquo: "‹", 251 | rsaquo: "›", 252 | oline: "‾", 253 | euro: "€", 254 | trade: "™", 255 | larr: "←", 256 | uarr: "↑", 257 | rarr: "→", 258 | darr: "↓", 259 | harr: "↔", 260 | crarr: "↵", 261 | lceil: "⌈", 262 | rceil: "⌉", 263 | lfloor: "⌊", 264 | rfloor: "⌋", 265 | loz: "◊", 266 | spades: "♠", 267 | clubs: "♣", 268 | hearts: "♥", 269 | diams: "♦" 270 | }); 271 | --------------------------------------------------------------------------------