diff --git a/build.sh b/build.sh index 6d64eda..ce6c7f0 100755 --- a/build.sh +++ b/build.sh @@ -9,7 +9,7 @@ cp install_mpi4py.sh pypdevs/install_mpi4py.sh cd doc/sphinx rm -r _build/html make html -./rewrite_documentation.sh +#./rewrite_documentation.sh cd ../.. cp -R doc/sphinx/_build/html/ pypdevs/doc cp -R src/ pypdevs/ @@ -18,7 +18,7 @@ rm test/output/* cp -R test/ pypdevs/ cp LICENSE pypdevs/ cp NOTICE pypdevs/ -mkdir pypdevs/tests/output +mkdir -p pypdevs/tests/output rm pypdevs/src/pypdevs/*.pyc rm pypdevs/examples/*/*.pyc rm pypdevs/examples/*/*.pyo @@ -37,3 +37,5 @@ rm -R pypdevs/src/pypdevs/__pycache__ tar -czf pypdevs.tgz pypdevs rm -R pypdevs + +read -p "Press [Enter] key to continue..." diff --git a/doc/sphinx/changelog.rst b/doc/sphinx/changelog.rst index 468b789..c8f3c34 100644 --- a/doc/sphinx/changelog.rst +++ b/doc/sphinx/changelog.rst @@ -17,5 +17,5 @@ Changelog ========= -.. literalinclude:: ../notes.txt +.. literalinclude:: ../../notes.txt diff --git a/doc/sphinx/installation.rst b/doc/sphinx/installation.rst index b4cd6a3..0bfa035 100644 --- a/doc/sphinx/installation.rst +++ b/doc/sphinx/installation.rst @@ -24,7 +24,7 @@ Dependencies The following dependencies are mandatory: -* python 2.7 +* Python 2.7 **or** Python 3.6+ For parallel and distributed simulation, the following additional dependencies are required: @@ -35,6 +35,16 @@ Installation instructions are given for these two dependencies further in this s Realtime simulation using the Tk backend, obviously requires Tk. +Download +-------- + +The most up-to-date version of PyPDEVS can be obtained from the git repository:: + + git clone https://msdl.uantwerpen.be/git/yentl/PythonPDEVS.git + +Alternatively, the latest release can be downloaded via: + https://msdl.uantwerpen.be/git/yentl/PythonPDEVS/archive/master.zip + PyPDEVS Installation -------------------- diff --git a/doc/sphinx/queueing.rst b/doc/sphinx/queueing.rst index 824b4fc..47b88d6 100644 --- a/doc/sphinx/queueing.rst +++ b/doc/sphinx/queueing.rst @@ -41,9 +41,9 @@ While examples could be given purely in their formal description, they would not To specify this model, we first define the event exchanged between different examples: the Job. A job is coded as a class ``Job``. It has the attributes ``size`` (i.e., indicative of processing time) and ``creation time`` (i.e., time the event was created, for statistic gathering). -The ``Job`` class definition is shown next and can de downloaded: :download:`job.py <../examples/queueing/job.py>`. +The ``Job`` class definition is shown next and can de downloaded: :download:`job.py <../../examples/queueing/job.py>`. -.. literalinclude:: ../examples/queueing/job.py +.. literalinclude:: ../../examples/queueing/job.py We now focus on each atomic model seperately, starting at the event generator. @@ -59,9 +59,9 @@ Finally, the output function returns a new customer event with a randomly define The job has an attribute containing the time at which it was generated. Recall, however, that the output function was invoked before the internal transition, so the current time has not yet been updated by the internal transition. Therefore, the output function also has to do this addition, without storing the result in the state (as it cannot write to the state). -The ``Generator`` class definition is shown next and can de downloaded: :download:`generator.py <../examples/queueing/generator.py>`. +The ``Generator`` class definition is shown next and can de downloaded: :download:`generator.py <../../examples/queueing/generator.py>`. -.. literalinclude:: ../examples/queueing/generator.py +.. literalinclude:: ../../examples/queueing/generator.py Next up is the queue, which is the most interesting component of the simulation, as it is the part we wish to analyze. The ``Queue`` implementation is similar in structure to the ``Generator``. @@ -83,9 +83,9 @@ An important consideration in this model is the ``remaining\_time`` counter, whi We can't simply put the processing time of events in the time advance, as interrupts could happen during this time. When an interrupt happens (e.g., another event arrives), the time advance is invoked again, and would return the total processing time, instead of the remaining time to process the event. To solve this problem, we maintain a counter that explicitly gets decremented when an external interrupt happens. -The ``Queue`` class definition is shown next and can de downloaded: :download:`queue.py <../examples/queueing/queue.py>`. +The ``Queue`` class definition is shown next and can de downloaded: :download:`queue.py <../../examples/queueing/queue.py>`. -.. literalinclude:: ../examples/queueing/queue.py +.. literalinclude:: ../../examples/queueing/queue.py The next atomic model is the ``Processor`` class. It merely receives an incoming event and starts processing it. @@ -95,32 +95,32 @@ We need to send out two events: one containing the job that was processed, and o For this, two different ports are used. Note that the definition of the processor would not be this simple in case there was no queue before it. We can now make the assumption that when we get an event, we are already idle and therefore don't need to queue new incoming events first. -The ``Processor`` class definition is shown next and can de downloaded: :download:`processor.py <../examples/queueing/processor.py>`. +The ``Processor`` class definition is shown next and can de downloaded: :download:`processor.py <../../examples/queueing/processor.py>`. -.. literalinclude:: ../examples/queueing/processor.py +.. literalinclude:: ../../examples/queueing/processor.py The processor finally sends the task to the ``Collector`` class. The collector is an artificial component that is not present in the system being modeled; it is only used for statistics gathering. For each job, it stores the time in the queue. -The ``Collector`` class definition is shown next and can de downloaded: :download:`collector.py <../examples/queueing/collector.py>`. +The ``Collector`` class definition is shown next and can de downloaded: :download:`collector.py <../../examples/queueing/collector.py>`. -.. literalinclude:: ../examples/queueing/collector.py +.. literalinclude:: ../../examples/queueing/collector.py With all atomic examples defined, we only have to couple them together in a coupled model: the ``System``. In this system, we instantiate a generator, queue, and collector, as well as a variable number of processors. The number of processors is variable, but is still static during simulation. The couplings also depend on the number of processors, as each processor is connected to the queue and the collector. -The ``System`` class definition is shown next and can de downloaded: :download:`system.py <../examples/queueing/system.py>`. +The ``System`` class definition is shown next and can de downloaded: :download:`system.py <../../examples/queueing/system.py>`. -.. literalinclude:: ../examples/queueing/system.py +.. literalinclude:: ../../examples/queueing/system.py Now that our DEVS model is completely specified, we can start running simulations on it. Simulation requires an *experiment* file though, which initializes the model with parameters and defines the simulation configuration. The experiment writes out the raw queueing times to a Comma Seperated Value (CSV) file. An experiment file often contains some configuration of the simulation tool, which differs for each tool. -The experiment file is shown next and can de downloaded: :download:`experiment.py <../examples/queueing/experiment.py>`. +The experiment file is shown next and can de downloaded: :download:`experiment.py <../../examples/queueing/experiment.py>`. -.. literalinclude:: ../examples/queueing/experiment.py +.. literalinclude:: ../../examples/queueing/experiment.py Performance Analysis -------------------- diff --git a/pypdevs.tgz b/pypdevs.tgz index e5e7100..a484f0a 100644 Binary files a/pypdevs.tgz and b/pypdevs.tgz differ diff --git a/src/pypdevs/basesimulator.py b/src/pypdevs/basesimulator.py index 9331dce..66e03aa 100644 --- a/src/pypdevs/basesimulator.py +++ b/src/pypdevs/basesimulator.py @@ -1092,7 +1092,10 @@ class BaseSimulator(Solver): interrupt = self.threading_backend.getInterrupt() if interrupt is None: self.realtime_counter = 100 - self.threading_backend.wait(wait_time, self.runsim) + if wait_time == float('inf') and getattr(self, "accept_external_input", False): + self.threading_backend.wait(0.01, self.runsim) + else: + self.threading_backend.wait(wait_time, self.runsim) return True try: info = interrupt.split(" ") diff --git a/src/pypdevs/controller.py b/src/pypdevs/controller.py index 08c508f..1f7a482 100644 --- a/src/pypdevs/controller.py +++ b/src/pypdevs/controller.py @@ -51,6 +51,7 @@ class Controller(BaseSimulator): self.running_irreversible = None self.initial_allocator = None self.prev_termination_time = 0.0 + self.accept_external_input = False def __setstate__(self, retdict): """ @@ -317,6 +318,14 @@ class Controller(BaseSimulator): self.termination_condition = termination_condition self.termination_time_check = False + def setAcceptExternalInputs(self, aei): + """ + Sets the controller to accept external inputs. + When enabled, the "early-return" of the simulator when all components have an infinite + time-advance is ignored. + """ + self.accept_external_input = aei + def findAndPerformRelocations(self, gvt, activities, horizon): """ First requests the relocator for relocations to perform, and afterwards actually perform them. diff --git a/src/pypdevs/realtime/threadingBackend.py b/src/pypdevs/realtime/threadingBackend.py index 09528e6..0debc88 100644 --- a/src/pypdevs/realtime/threadingBackend.py +++ b/src/pypdevs/realtime/threadingBackend.py @@ -51,7 +51,7 @@ class ThreadingBackend(object): def interrupt(self, value): """ - Interrupt a running wait call. + Interrupt a running wait call, overwriting any previous interrupts. :param value: the value that interrupts """ diff --git a/src/pypdevs/simconfig.py b/src/pypdevs/simconfig.py index 5476bd4..47ff207 100644 --- a/src/pypdevs/simconfig.py +++ b/src/pypdevs/simconfig.py @@ -623,6 +623,7 @@ class SimulatorConfiguration(object): if not isinstance(ports, dict): raise DEVSException("Realtime input port references should be a dictionary") self.simulator.realtime_port_references = ports + self.simulator.accept_external_input = True def setModelState(self, model, new_state): """ diff --git a/src/pypdevs/simulator.py b/src/pypdevs/simulator.py index 4c24c11..3d9c915 100644 --- a/src/pypdevs/simulator.py +++ b/src/pypdevs/simulator.py @@ -210,6 +210,7 @@ class Simulator(object): self.setSchedulerActivityHeap() self.locations_file = None self.allocator = None + self.accept_external_input = False self.realtime_extra = [] self.model_ids = [] @@ -584,6 +585,7 @@ class Simulator(object): self.controller.setDSDEVS(self.dsdevs) self.controller.setActivityTracking(self.activity_tracking) self.controller.setClassicDEVS(self.classicDEVS) + self.controller.setAcceptExternalInputs(self.accept_external_input) self.controller.setCellLocationTracer(self.x_size, self.y_size, self.location_cell_view)