From 933609774194212bd732243a7c9521e7c48dda27 Mon Sep 17 00:00:00 2001 From: Magnus93 Date: Thu, 18 Jul 2024 15:32:53 +0200 Subject: [PATCH] Add Ghost to Model --- src/ModelNode.js | 4 ++- src/api/Blocks.js | 20 ++++++++++++ src/api/Model.js | 24 ++++++++++++++- test/suites/ghost.test.js | 64 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 test/suites/ghost.test.js diff --git a/src/ModelNode.js b/src/ModelNode.js index bc8f51b..f69f14d 100755 --- a/src/ModelNode.js +++ b/src/ModelNode.js @@ -1,6 +1,6 @@ import { DOMParser } from "../vendor/xmldom/dom-parser.js"; // eslint-disable-next-line -import { Stock, Variable, State, Action, Population, Transition, Flow, Link, Folder, Agent, Converter, Primitive } from "./api/Blocks.js"; +import { Stock, Variable, State, Action, Population, Transition, Flow, Link, Folder, Agent, Converter, Ghost, Primitive } from "./api/Blocks.js"; @@ -43,6 +43,8 @@ export class ModelNode { this._primitive = new Variable(this, config); } else if (this.value.nodeName === "Converter") { this._primitive = new Converter(this, config); + } else if (this.value.nodeName === "Ghost") { + this._primitive = new Ghost(this, config); } else if (this.value.nodeName === "State") { this._primitive = new State(this, config); } else if (this.value.nodeName === "Action") { diff --git a/src/api/Blocks.js b/src/api/Blocks.js index a9e5ca2..a7a9ea9 100644 --- a/src/api/Blocks.js +++ b/src/api/Blocks.js @@ -359,6 +359,26 @@ export class ValuedPrimitive extends Primitive { } +/** + * @typedef {object} GhostConfig + * @property {Stock|Variable|Converter=} source + */ +export class Ghost extends Primitive { + /** + * @return {GhostConfig["source"]} + */ + get source() { + const sourceId = this._node.getAttribute("Source") + return /** @type {Stock} */ this.model.find(p => p.id == sourceId)[0] + } + /** + * @param {GhostConfig["source"]} node + */ + set source(node) { + this._node.setAttribute("Source", node.id) + } +} + /** * @typedef {object} StockConfig diff --git a/src/api/Model.js b/src/api/Model.js index ccd638e..3a55b03 100644 --- a/src/api/Model.js +++ b/src/api/Model.js @@ -2,7 +2,7 @@ import { loadXML, modelNodeClone, ModelNode, primitives } from "../ModelNode.js" import { runSimulation } from "../Modeler.js"; import { nodeBase } from "../Constants.js"; // eslint-disable-next-line -import { Primitive, Stock, Variable, Converter, State, Action, Population, Flow, Transition, Link, Agent, Folder } from "./Blocks.js"; +import { Primitive, Stock, Variable, Converter, Ghost, State, Action, Population, Flow, Transition, Link, Agent, Folder } from "./Blocks.js"; import { Results } from "./Results.js"; import { SimulationError } from "./SimulationError.js"; @@ -218,6 +218,17 @@ export class Model { return /** @type {Converter} */ (converter); } + /** + * @param {import("./Blocks.js").PrimitiveConfig & import("./Blocks.js").ValuedConfig & import("./Blocks.js").GhostConfig} config + * + * @return {Ghost} + */ + Ghost(config = {}) { + let ghost = /** @type {Ghost} */ (this._createNode("ghost").primitive(this, excludeKeys(config, []))); + ghost.model = this; + return ghost; + } + /** * @param {import("./Blocks.js").PrimitiveConfig & import("./Blocks.js").StateConfig} config * @@ -555,6 +566,17 @@ export class Model { return items.filter(x => selector(x.primitive())).map(x => x.primitive()); } + /** + * @param {function(Ghost):boolean=} selector + * + * @return {Ghost[]} + */ + findGhosts(selector = (() => true)) { + let items = this.p(this._graph, "Ghost"); + + return items.filter(x => selector(x.primitive())).map(x => x.primitive()); + } + /** * @param {function(State):boolean=} selector * diff --git a/test/suites/ghost.test.js b/test/suites/ghost.test.js new file mode 100644 index 0000000..d249b5d --- /dev/null +++ b/test/suites/ghost.test.js @@ -0,0 +1,64 @@ +import { Model, loadInsightMaker } from "../../src/api/Model.js"; + + +describe("ghost %s", + + /** + * @param {"Euler"|"RK4"} algorithm + */ + (algorithm) => { + + test("Ghost", () => { + let m = new Model({ algorithm }); + + m.timeUnits = "Years"; + m.timeStep = 1; + m.timeStart = 0; + m.timeLength = 10; + + let s = m.Stock({ + name: "My Stock" + }); + let f = m.Flow(s, null, { + name: "My Flow" + }); + + let c = m.Converter() + + let gs = m.Ghost({source: s}) + let gf = m.Ghost({source: f}) + let gc = m.Ghost({source: c}) + expect(gs.source == s).toBe(true) + expect(gf.source == f).toBe(true) + expect(gc.source == c).toBe(true) + }); + + test("From file", () => { + let m = loadInsightMaker(` + + + + + + + + + + + + + + + + + + + + `); + let stock = m.findStocks(s => s.id == "44")[0]; + let ghost = m.findGhosts(g => g.id == "45")[0]; + console.log("model", m, "stock", stock, "ghost", ghost); + expect(stock == ghost.source).toBe(true); + }) + }); +