123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- ..
- Copyright 2014 Modelling, Simulation and Design Lab (MSDL) at
- McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- Examples for Dynamic Structure DEVS
- ===================================
- We used the same approach to Dynamic Structure as adevs, meaning that a *modelTransition* method will be called at every step in simulated time on every model that transitioned.
- A small *trafficLight* model is included in the *examples* folder of the PyPDEVS distribution. In this section, we introduce the use of the new constructs.
- Dynamic structure is possible for both Classic and Parallel DEVS, but only for local simulation.
- Starting point
- --------------
- We will start from a very simple Coupled DEVS model, which contains 2 Atomic DEVS models.
- The first model sends messages on its output port, which are then routed to the second model.
- Note that, for compactness, we include the experiment part in the same file as the model.
- This can be expressed as follows (:download:`base_dsdevs.py <base_dsdevs.py>`)::
- from pypdevs.DEVS import AtomicDEVS, CoupledDEVS
- from pypdevs.simulator import Simulator
- class Root(CoupledDEVS):
- def __init__(self):
- CoupledDEVS.__init__(self, "Root")
- self.models = []
- # First model
- self.models.append(self.addSubModel(Generator()))
- # Second model
- self.models.append(self.addSubModel(Consumer(0)))
- # And connect them
- self.connectPorts(self.models[0].outport, self.models[1].inport)
- class Generator(AtomicDEVS):
- def __init__(self):
- AtomicDEVS.__init__(self, "Generator")
- # Keep a counter of how many events were sent
- self.outport = self.addOutPort("outport")
- self.state = 0
- def intTransition(self):
- # Increment counter
- return self.state + 1
- def outputFnc(self):
- # Send the amount of messages sent on the output port
- return {self.outport: [self.state]}
- def timeAdvance(self):
- # Fixed 1.0
- return 1.0
- class Consumer(AtomicDEVS):
- def __init__(self, count):
- AtomicDEVS.__init__(self, "Consumer_%i" % count)
- self.inport = self.addInPort("inport")
- def extTransition(self, inputs):
- for inp in inputs[self.inport]:
- print("Got input %i on model %s" % (inp, self.name))
- sim = Simulator(Root())
- sim.setTerminationTime(5)
- sim.simulate()
- All undefined functions will just use the default implementation.
- As such, this model will simply print messages::
- Got input 0 on model Consumer_0
- Got input 1 on model Consumer_0
- Got input 2 on model Consumer_0
- Got input 3 on model Consumer_0
- Got input 4 on model Consumer_0
- We will now extend this model to include dynamic structure.
- Dynamic Structure
- -----------------
- To allow for dynamic structure, we need to augment both our experiment and our model.
- Experiment
- ^^^^^^^^^^
- First, the dynamic structure configuration parameter should be enabled in the experiment.
- For performance reasons, this feature is disabled by default. It can be enabled as follows::
- sim = Simulator(Root())
- sim.setTerminationTime(5)
- sim.setDSDEVS(True)
- sim.simulate()
- Model
- ^^^^^
- With dynamic structure enabled, models can define a new method: *modelTransition(state)*.
- This method is called on all Atomic DEVS models that executed a transition in that time step.
- This method is invoked on the model, and can thus access the state of the model.
- For now, ignore the *state* parameter.
- In this method, all modifications on the model are allowed, as it was during model construction.
- That is, it is possible to invoke the following methods::
- setInPort(portname)
- setOutPort(portname)
- addSubModel(model)
- connectPorts(output, input)
- But apart from these known methods, it is also possible to delete existing constructs, with the operations::
- removePort(port)
- removeSubModel(model)
- disconnectPorts(output, input)
- On Atomic DEVS models, only the port operations are available. On Coupled DEVS models, all of these are available.
- Removing a port or submodel will automatically disconnect all its connections.
- The method will also return a boolean, indicating whether or not to propagate the structural changes on to the parent model.
- If it is *True*, the method is invoked on the parent as well. Note that the root model should not return *True*.
- Propagation is necessary, as models are only allowed to change the structure of their subtree.
- .. NOTE::
- In the latest implementation, modifying the structure outside of your own subtree has no negative consequences. However, it should be seen as a best practice to only modify yourself.
- For example, to create a second receiver as soon as the generator has output 3 messages, you can modify the following methods (:download:`simple_dsdevs.py <simple_dsdevs.py>`)::
- class Generator(AtomicDEVS):
- ...
- def modelTransition(self, state):
- # Notify parent of structural change if state equals 3
- return self.state == 3
- class Root(CoupledDEVS):
- ...
- def modelTransition(self, state):
- # We are notified, so are required to add a new model and link it
- self.models.append(self.addSubModel(Consumer(1)))
- self.connectPorts(self.models[0].outport, self.models[-1].inport)
- ## Optionally, we could also remove the Consumer(0) instance as follows:
- # self.removeSubModel(self.models[1])
- # Always returns False, as this is top-level
- return False
- This would give the following output (or similar, due to concurrency)::
- Got input 0 on model Consumer_0
- Got input 1 on model Consumer_0
- Got input 2 on model Consumer_0
- Got input 3 on model Consumer_0
- Got input 3 on model Consumer_1
- Got input 4 on model Consumer_0
- Got input 4 on model Consumer_1
- .. NOTE::
- As structural changes are not a common operation, their performance is not optimized extensively. To make matters worse, many structural optimizations done by PythonPDEVS will automatically be redone after each structural change.
- Passing state
- ^^^^^^^^^^^^^
- Finally, we come to the *state* parameter of the modelTransition call.
- In some cases, it will be necessary to pass arguments to the parent, to notify it of how the structure should change.
- This is useful if the child knows information that is vital to the change.
- Since Coupled DEVS models cannot hold state, and should not directly access the state of their children, we can use the *state* parameter for this.
- The *state* parameter is simply a dictionary object, which is passed between all the different *modelTransition* calls.
- Simply put, it is an object shared by all calls.
- For example, if we would want the structural change from before to create a new consumer every time, with an ID provided by the Generator, this can be done as follows (:download:`state_dsdevs.py <state_dsdevs.py>`)::
- class Generator(AtomicDEVS):
- ...
- def modelTransition(self, state):
- # We pass on the ID that we would like to create, which is equal to our counter
- state["ID"] = self.state
- # Always create a new element
- return True
- class Root(CoupledDEVS):
- ...
- def modelTransition(self, state):
- # We are notified, so are required to add a new model and link it
- # We can use the ID provided by the model below us
- self.models.append(self.addSubModel(Consumer(state["ID"])))
- self.connectPorts(self.models[0].outport, self.models[-1].inport)
- # Always returns False, as this is top-level
- return False
- This would then create the output (or similar, due to concurrency)::
- Got input 0 on model Consumer_0
- Got input 1 on model Consumer_0
- Got input 1 on model Consumer_1
- Got input 2 on model Consumer_0
- Got input 2 on model Consumer_1
- Got input 2 on model Consumer_2
- Got input 3 on model Consumer_0
- Got input 3 on model Consumer_1
- Got input 3 on model Consumer_2
- Got input 3 on model Consumer_3
- More complex example
- --------------------
- In the PyPDEVS distribution, a more complex example is provided.
- That example provides a model of two traffic lights, with a policeman who periodically changes the traffic light he is interrupting.
|