{"id":1126,"date":"2016-11-10T22:12:46","date_gmt":"2016-11-11T03:12:46","guid":{"rendered":"http:\/\/rpchurchill.com\/?p=1126"},"modified":"2017-02-03T14:26:44","modified_gmt":"2017-02-03T19:26:44","slug":"a-simple-discrete-event-simulation-part-40","status":"publish","type":"post","link":"https:\/\/rpchurchill.com\/wordpress\/posts\/2016\/11\/10\/a-simple-discrete-event-simulation-part-40\/","title":{"rendered":"A Simple Discrete-Event Simulation: Part 40"},"content":{"rendered":"<p>Today I modified the framework to define Node components separately from Path components.  This allows us to have any number of paths going into or out of any node so that any arbitrary network can be defined.  In the code&#8217;s current state components that are not nodes or paths can hand entities off to other components that also aren&#8217;t nodes or paths, or to nodes.  Nodes can hand entities off to paths or to components that aren&#8217;t nodes or paths.  Paths can on receive entities from nodes and hand entities off to nodes.<\/p>\n<p>This is actually a little bit awkward, and we are on a bridge between two versions of what this kind of discrete-event simulation model can be.  One version is more abstract and considers only how entities move from component to component.  It might not be very interesting visually, but it would be very fast and would get the job done as far as performing the desired analysis.  The other version is more detailed and would effectively do away with all components that are not nodes or paths.  In this version the process activities would take place at nodes and queues would form wherever nodes backed up behind processes.  That can make it a bit trickier to figure out when an entity entered a given queue but that&#8217;s just a technicality.<\/p>\n<p>The more detailed version is much more complex to write and debug and takes more memory and execution time (potentially by a lot) but if way more visually interesting.  Observers can see exactly what&#8217;s going on inside the model as it runs (or as a run is replayed).  For the time being I&#8217;m going to back up and finish building out the more abstract version.  To give it some visual interest, however, I will retain the visual paths between components so users can get more of a feel for where entities are being sent in a simulation.  I might back the representation of the nodes and paths to the earlier version where I defined paths with nodes included on each end, and ensure that I never include more than one path between components (if possible).<\/p>\n<p>There&#8217;s a lot that can be done with a more abstract version of this tool and I have an interesting in getting something more interesting running and usable.  I plan to do the more interesting and detailed version going forward, and that will involve finding detailed solutions to the problems of entities competing to pass through intersections, as has been discussed over the last couple of days.  I think it&#8217;ll be helpful to build that kind of framework and play around with it rather than try to whiteboard everything up front where it&#8217;s hard to follow what might be going on.<\/p>\n<p>In the meantime here is the code for the new Node component.  Notice that there are currently no discrete-event handling mechanisms in this version.  When a component (path or otherwise) hands an entity off to the node it immediately gets forwarded to the next component.  This may not be enough in the long run, but it works well enough in my test case so it is sufficient for now.  The node can be connected to one or more paths or a single non-path component either coming in or going out, so the node figures out the kind of forwarding or receiving to do be checking against the number of incoming or outgoing paths that are linked to the node.  Also, when the end of a path is assigned to a node the assignment causes some relevant values to be assigned within the path component.<\/p>\n<pre class=\"toolbar-overlay:false wrap:false height-set:true lang:default decode:true \">\r\n    function NodeComponent(x,y) {\r\n      setOfComponents.push(this);\r\n      this.componentID = getNewID();\r\n      this.componentName = \"Node\";\r\n      this.xLoc = x;\r\n      this.yLoc = y;\r\n      this.incomingPaths = [];\r\n      this.outgoingPaths = [];\r\n      this.previousComponent = null;\r\n      this.nextComponent = null;\r\n      this.permissionID = -1;\r\n      this.firstArrivalTime = Infinity;\r\n      this.entity = null;\r\n      \r\n      this.getComponentID =function() {\r\n        return this.componentID;\r\n      };\r\n      this.getComponentName =function() {\r\n        return this.componentName;\r\n      };\r\n      this.assignIncomingPath = function(component) {\r\n        if (component.getComponentName() == \"Path\") {\r\n          if (this.previousComponent === null) {\r\n            this.incomingPaths.push(component);\r\n            component.setEndPoint(this.xLoc,this.yLoc);\r\n            component.assignNextComponent(this);\r\n          } else {\r\n            alert(\"Comp. \"+this.componentID+\" assigned incoming path when previousComponent already defined.\");\r\n          }\r\n        } else {\r\n          if (this.incomingPaths.length <= 0) {\r\n            this.previousComponent = component;\r\n          } else {\r\n            alert(\"Comp. \"+this.componentID+\" assigned previousComponent when incoming path already defined.\");\r\n          }\r\n        }\r\n      };\r\n      this.assignOutgoingPath = function(component) {\r\n        if (component.getComponentName() == \"Path\") {\r\n          if (this.nextComponent === null) {\r\n            this.outgoingPaths.push(component);\r\n            component.setStartPoint(this.xLoc,this.yLoc);\r\n            component.assignPreviousComponent(this);\r\n          } else {\r\n            alert(\"Comp. \"+this.componentID+\" assigned outgoing path when nextComponent already defined.\");\r\n          }\r\n        } else {\r\n          if (this.outgoingPaths.length <= 0) {\r\n            this.nextComponent = component;\r\n          } else {\r\n            alert(\"Comp. \"+this.componentID+\" assigned nextComponent when outgoing path already defined.\");\r\n          }\r\n        }\r\n      };\r\n      this.setLocation = function(x,y) {\r\n        this.xLoc = x;\r\n        this.yLoc = y;\r\n      };\r\n      this.getLocation = function() {\r\n        return {x: this.xLoc, y: this.yLoc};\r\n      };\r\n      \/*this.competeForPermission = function(path,proposedArrivalTime) {\r\n        if ((proposedArrivalTime < this.firstArrivalTime) &#038;&#038;\r\n            (this.permissionID < 0)) {\r\n          \r\n        }\r\n      }; *\/\r\n      this.drawData = function() {\r\n        this.nodeColor = \"#FF00FF\";\r\n        drawNode(this.xLoc+0.5,this.yLoc+0.5,3,this.nodeColor);\r\n      }\r\n      this.isOpen = function() {\r\n        if (this.outgoingPaths.length > 0) {\r\n          return this.outgoingPaths[0].isOpen();\r\n        } else {\r\n          return this.nextComponent.isOpen();\r\n        }\r\n      };\r\n      this.forwardEntity = function(entity) {\r\n        \/\/in the future figure out which outgoing path to take\r\n        \/\/based on routing and shortest path\r\n        if (this.outgoingPaths.length > 0) {\r\n          this.outgoingPaths[0].receiveEntity(entity);\r\n        } else {\r\n          this.nextComponent.receiveEntity(entity);\r\n        }\r\n      };\r\n      this.receiveEntity = function(entity) {\r\n        \/\/only called if the sender knows it's ok to do so\r\n        this.forwardEntity(entity);\r\n      };\r\n    \r\n    }  \/\/Node component\r\n<\/pre>\n<p>Here&#8217;s the updated code for the Path components.  It is almost untouched from its previous version.<\/p>\n<pre class=\"toolbar-overlay:false wrap:false height-set:true lang:default decode:true \">\r\n    function PathComponent() {\r\n      setOfComponents.push(this);\r\n      this.componentID = getNewID();\r\n      this.componentName = \"Path\";\r\n      this.entryTime = \"\";\r\n      this.entryEntityID = \"\";\r\n      this.countInQueue = 0;\r\n      this.exitTime = \"\";\r\n      this.exitEntityID = \"\";\r\n      this.exitResidenceTime = \"\";\r\n      this.x1 = 0.0;\r\n      this.y1 = 0.0;\r\n      this.x2 = 0.0;\r\n      this.y2 = 0.0;\r\n      this.pathLength = 0.0;\r\n      this.previousComponent = null;\r\n      this.nextComponent = null;\r\n      this.entityQueue = [];\r\n      this.traverseQueue = [];\r\n      this.endTraverse = 0.0;\r\n      \r\n      this.getComponentID =function() {\r\n        return this.componentID;\r\n      };\r\n      this.getComponentName =function() {\r\n        return this.componentName;\r\n      };\r\n      this.getEntryTime =function() {\r\n        return this.entryTime;\r\n      };\r\n      this.getEntryEntityID =function() {\r\n        return this.entryEntityID;\r\n      };\r\n      this.getExitTime =function() {\r\n        return this.exitTime;\r\n      };\r\n      this.getExitEntityID =function() {\r\n        return this.exitEntityID;\r\n      };\r\n      this.getCountInQueue =function() {\r\n        return this.countInQueue;\r\n      };\r\n      this.getExitResidenceTime =function() {\r\n        return this.exitResidenceTime;\r\n      };\r\n      \r\n      this.setStartPoint = function(x1,y1) {\r\n        this.x1 = x1;\r\n        this.y1 = y1;\r\n      };\r\n      this.setEndPoint = function(x2,y2) {\r\n        this.x2 = x2;\r\n        this.y2 = y2;\r\n      };\r\n      this.setSpeedTime = function(speed, maxRefreshTime) {\r\n        this.speed = speed;\r\n        this.maxRefreshTime = maxRefreshTime;\r\n      };\r\n      this.calcPathLength = function() {\r\n        this.pathLength = Math.sqrt((this.x2-this.x1)*(this.x2-this.x1) +\r\n                                    (this.y2-this.y1)*(this.y2-this.y1));\r\n        this.endTraverse = this.pathLength;\r\n      };\r\n      this.isOpen = function() {\r\n        if (this.entityQueue.length > 0) {\r\n          if (this.traverseQueue[0] < (10 + 2)) {\r\n            return false;\r\n          }\r\n        }\r\n        return true;\r\n      };\r\n      \r\n      this.drawData = function() {\r\n        \/\/this.nodeColor = \"#FF00FF\";\r\n        this.lineColor = \"#FFFFFF\";\r\n        globalCTX.strokeStyle = this.lineColor;\r\n        globalCTX.beginPath();\r\n        globalCTX.moveTo(this.x1+0.5,this.y1+0.5);\r\n        globalCTX.lineTo(this.x2+0.5,this.y2+0.5);\r\n        globalCTX.stroke();\r\n        \/\/drawNode(this.x1+0.5,this.y1+0.5,3,this.nodeColor);\r\n        \/\/drawNode(this.x2+0.5,this.y2+0.5,3,this.nodeColor);\r\n        \r\n        var numEntities = this.entityQueue.length;\r\n        if (numEntities > 0) {\r\n          for (var i=0; i<numEntities; i++) {\r\n            this.entityQueue[i].drawEntity();\r\n          }\r\n        }\r\n      };\r\n      this.assignPreviousComponent = function(previous) {\r\n        this.previousComponent = previous;\r\n      };\r\n      this.assignNextComponent = function(next) {\r\n        this.nextComponent = next;\r\n      };\r\n      this.backPropogateEndTraverse = function(traverse) {\r\n        if (this.previousComponent !== null) {\r\n          \r\n        }        \r\n      };\r\n      this.moveEntity = function(entity) {\r\n        \/\/moves entities down path, but keeps them from overlapping\r\n        \/\/for now assumes radius of 5 and clearance of 2\r\n        \/\/start from discharge end of list\r\n        var index = this.entityQueue.length - 1;\r\n        while ((this.entityQueue[index].entityID != entity.entityID) &#038;&#038; (index > 0)) {\r\n          index--;\r\n        }\r\n        var distanceIncrement = this.speed * this.maxRefreshTime;\r\n        var dist = distanceIncrement;\r\n        var timeIncrement = this.maxRefreshTime;\r\n        var nextState = \"move\";\r\n        if (index < this.entityQueue.length - 1) {\r\n          \/\/not the lead entity, check against next entity\r\n          if (this.traverseQueue[index]+distanceIncrement > (this.traverseQueue[index+1] - 10 - 2)) {\r\n            dist = this.traverseQueue[index+1] - 10 - 2 - this.traverseQueue[index];\r\n            if (dist <= 0.0) {\r\n              dist = 0.0;\r\n              timeIncrement = this.maxRefreshTime; \/\/0.5;\r\n            } else {\r\n              timeIncrement *= dist \/ distanceIncrement;\r\n            }\r\n          }\r\n        } else {\r\n          \/\/this is the lead entity, check against end of path\r\n          if (this.traverseQueue[index]+distanceIncrement > this.pathLength) {\r\n            dist = this.pathLength - this.traverseQueue[index] + 0.001;\r\n            timeIncrement *= dist \/ distanceIncrement;\r\n            nextState = \"forward\";\r\n          }\r\n        }  \/\/TODO: also check against endTraverse, which considers possible entity on subsequent path\r\n        this.traverseQueue[index] += dist; \/\/distanceIncrement;\r\n        var frac = this.traverseQueue[index] \/ this.pathLength;\r\n        var x = this.x1 + (this.x2 - this.x1) * frac;\r\n        var y = this.y1 + (this.y2 - this.y1) * frac;\r\n        entity.setLocation(x,y);\r\n        feq.newItem(globalSimClock+timeIncrement,this,nextState,entity);\r\n        displayProgressText(\"Path comp. \"+this.componentID+\" moves entity \"+entity.entityID+\" at time \"+globalSimClock.toFixed(6));\r\n      };\r\n      this.forwardEntity = function() {\r\n        if (this.nextComponent.isOpen()) {\r\n          this.traverseQueue.pop();\r\n          var entity = this.entityQueue.pop();\r\n          if (entity) {\r\n            this.countInQueue--;\r\n            this.exitTime = globalSimClock;\r\n            this.exitEntityID = entity.entityID;\r\n            displayProgressText(\"Path comp. \"+this.componentID+\" forwards entity: \"+this.exitEntityID+\" at time \"+globalSimClock.toFixed(6));\r\n            this.nextComponent.receiveEntity(entity);\r\n          }\r\n        }\r\n      };\r\n      this.receiveEntity = function(entity) {\r\n        \/\/receive the entity\r\n        entity.setLocalEntryTime();  \/\/record time entity entered queue\r\n        entity.setLocation(this.x1,this.y1);\r\n        feq.newItem(globalSimClock+this.maxRefreshTime,this,\"move\",entity);\r\n        this.traverseQueue.unshift(0.0);\r\n        this.entityQueue.unshift(entity);\r\n        this.countInQueue++;\r\n        \/\/display what was done\r\n        this.entryTime = globalSimClock;\r\n        this.entryEntityID = entity.entityID;\r\n        displayProgressText(\"Path comp. \"+this.componentID+\" receives entity: \"+this.entryEntityID+\" at time \"+globalSimClock.toFixed(6));\r\n        \/\/if (this.nextComponent.isOpen()) {\r\n        \/\/  this.forwardEntity();\r\n        \/\/}\r\n      };\r\n      this.activate = function(nextState,entity2) {\r\n        if (nextState == \"move\") {\r\n          this.moveEntity(entity2);\r\n        } else if (nextState == \"forward\") {\r\n          this.forwardEntity();\r\n        } else {\r\n          errorUndefinedAdvanceState(this.entityID,this.nextState);\r\n        }      \r\n      };  \/\/this.activate\r\n    }  \/\/PathComponent\r\n<\/pre>\n<p>Here&#8217;s the new code that initializes everything.<\/p>\n<pre class=\"toolbar-overlay:false wrap:false height-set:true lang:default decode:true \">\r\n    \/\/schedule for six hours of arrivals in half-hour blocks\r\n    var arrivalSchedule = [0,0,1,3,4,4,5,6,3,2,1,0]; \r\n    \r\n    var arrival1 = new ArrivalsComponent(30.0,arrivalSchedule);\r\n    arrival1.defineDataGroup(10,10,80,\"#00FFFF\",\"#FF0000\",\"#FFFF00\");\r\n\r\n    var entry1 = new EntryComponent(2.0);\r\n    entry1.defineDataGroup(175,10,80,\"#00FFFF\",\"#FF0000\",\"#FFFF00\");\r\n    \r\n    arrival1.assignNextComponent(entry1);\r\n    \r\n    var node1 = new NodeComponent(180,73);\r\n    var node2 = new NodeComponent(320,134);\r\n    var node3 = new NodeComponent(180,196);\r\n    \r\n    entry1.assignNextComponent(node1);\r\n    \r\n    var path1 = new PathComponent();\r\n    node1.assignOutgoingPath(path1);\r\n    node2.assignIncomingPath(path1);\r\n    path1.setSpeedTime(7.5,1.0);\r\n    path1.calcPathLength();\r\n    \r\n    var path2 = new PathComponent();\r\n    node2.assignOutgoingPath(path2);\r\n    node3.assignIncomingPath(path2);\r\n    path2.setSpeedTime(7.5,1.0);\r\n    path2.calcPathLength();\r\n\r\n    var exit1 = new ExitComponent(2.0);\r\n    exit1.defineDataGroup(175,196,80,\"#00FFFF\",\"#FF0000\",\"#FFFF00\");\r\n    \r\n    node3.assignOutgoingPath(exit1);\r\n<\/pre>\n<p>It doesn&#8217;t look any different than it did before, and it still works, so that&#8217;s always good!<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.rpchurchill.com\/images\/articles\/20161110_DES.png\" \/><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Today I modified the framework to define Node components separately from Path components. This allows us to have any number of paths going into or out of any node so that any arbitrary network can be defined. In the code&#8217;s &hellip; <a href=\"https:\/\/rpchurchill.com\/wordpress\/posts\/2016\/11\/10\/a-simple-discrete-event-simulation-part-40\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[60],"tags":[121,49],"_links":{"self":[{"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/posts\/1126"}],"collection":[{"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/comments?post=1126"}],"version-history":[{"count":3,"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/posts\/1126\/revisions"}],"predecessor-version":[{"id":1425,"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/posts\/1126\/revisions\/1425"}],"wp:attachment":[{"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/media?parent=1126"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/categories?post=1126"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rpchurchill.com\/wordpress\/wp-json\/wp\/v2\/tags?post=1126"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}