瀏覽代碼

Fixed computation times

rparedis 3 年之前
父節點
當前提交
466b5d31b1

+ 22 - 17
examples/scripts/BouncingBall/BouncingBall_experiment.py

@@ -6,8 +6,8 @@ import matplotlib.pyplot as plt
 bb = BouncingBall()
 
 DELTA = 0.1
-# TIME = 15.0
-TIME = 4
+TIME = 15.0
+# TIME = 4
 
 fig = plt.figure(figsize=(5, 5), dpi=100)
 ax = fig.add_subplot(111)
@@ -24,38 +24,43 @@ from CBD.state_events.locators import ITPStateEventLocator
 def bounce(_, model):
 	model.bounce()
 
-import time
-pre = 0
-times = []
 lengs = []
-def prestep():
-	global pre
-	pre = time.time()
+times = []
 
-def post():
-	times.append(sim.getTime())
-	lengs.append(time.time() - pre)
+def poststep(o, t, st):
+	times.append(st)
+	lengs.append(t - o)
 
 sim = Simulator(bb)
 sim.setStateEventLocator(ITPStateEventLocator())
 sim.registerStateEvent(StateEvent("height", direction=Direction.FROM_ABOVE, event=bounce))
-sim.connect("prestep", prestep)
-sim.connect("poststep", post)
+sim.connect("poststep", poststep)
 sim.setRealTime()
 sim.setDeltaT(DELTA)
 sim.run(TIME)
 
+# import time
+# while sim.is_running():
+# 	time.sleep(0.1)
+
 plt.show()
 
+import numpy as np
+
 fig = plt.figure(figsize=(5, 5), dpi=100)
 ax = fig.add_subplot(111)
-ax.set_xlim((0, TIME))
+ax.set_xticks(np.arange(0, TIME, DELTA), minor=True)
+ax.set_xlim((0, TIME+DELTA))
 
-ax.bar(times[2:], lengs[2:], width=0.05)
-ax.plot((0, TIME), (DELTA, DELTA), c='red', ls='--')
+ax.bar(times, lengs, width=0.1, color='green', label='duration')
+# ax.plot((0, TIME+DELTA), (DELTA, DELTA), c='red', ls='--')
 
 ax2 = ax.twinx()
 ax2.set_ylim((0, 105))
-ax2.plot(*bb.getBlockByName('plot').data_xy, c='green')
+ax2.plot(*bb.getBlockByName('plot').data_xy, c='blue', label='simulation')
+
+handles, labels = ax.get_legend_handles_labels()
+handles2, labels2 = ax2.get_legend_handles_labels()
 
+ax.legend(handles + handles2, labels + labels2)
 plt.show()

+ 3 - 2
src/CBD/realtime/accurate_time.py

@@ -11,9 +11,10 @@ def time():
 	platform.
 	"""
 	if PYTHON_VERSION == 2 and sys.platform == "win32":
-		return python_time.clock()  # << better precision on windows
+		# better precision on windows, but deprecated since 3.3
+		return python_time.clock()
 	else:
-		return python_time.time()
+		return python_time.process_time()
 
 def sleep(t):
 	"""

+ 4 - 3
src/CBD/realtime/plotting.py

@@ -3,12 +3,13 @@ This module contains all classes and structures needed for live plotting of data
 polling an object. While specifically made for the CBD simulator, it is independent
 of any internal workings.
 """
+# import CBD.realtime.accurate_time as time
 import time
 import threading
 try:
+	import matplotlib
 	import matplotlib.pyplot as plt
 	import matplotlib.animation as animation
-	import matplotlib
 	from matplotlib.patches import Arrow as mplArrow
 	_MPL_FOUND = True
 
@@ -157,7 +158,7 @@ class PlotHandler:
 		- :class:`PlotKind`
 		- :class:`PlotManager`
 	"""
-	def __init__(self, object, figure, kind, backend=Backend.MPL, interval=100, frames=None, static=False):
+	def __init__(self, object, figure, kind, backend=Backend.MPL, interval=10, frames=None, static=False):
 		assert Backend.exists(backend), "Invalid backend."
 		self.object = object
 		self.kind = kind
@@ -185,7 +186,7 @@ class PlotHandler:
 		if not static:
 			if Backend.compare("MPL", backend) or Backend.compare("SNS", backend):
 				self.__ani = animation.FuncAnimation(figure[0], lambda _: self.update(),
-				                                     interval=interval, frames=frames)
+				                                     interval=self.interval, frames=frames)
 				figure[0].canvas.mpl_connect('close_event', lambda _: self.__close_event())
 			elif Backend.compare("BOKEH", backend):
 				self.__periodic_callback = bokeh.io.curdoc().add_periodic_callback(lambda: self.update(), interval)

+ 0 - 1
src/CBD/realtime/threadingBackend.py

@@ -94,7 +94,6 @@ class ThreadingBackend:
     """
     def __init__(self, subsystem, args):
         self.interrupted_value = None
-        self.value_lock = threading.Lock()
         if subsystem.lower() == Platform.THREADING:
             from .threadingPython import ThreadingPython
             self._subsystem = ThreadingPython()

+ 1 - 0
src/CBD/realtime/threadingGameLoop.py

@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import CBD.realtime.accurate_time as time
 from threading import Lock
 
 _GL_LOCK = Lock()

+ 1 - 1
src/CBD/realtime/threadingPython.py

@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from threading import Thread
-from . import accurate_time
+from CBD.realtime import accurate_time
 
 class ThreadingPython(object):
     """

+ 44 - 28
src/CBD/simulator.py

@@ -1,14 +1,14 @@
 import sys
-import time
 import logging
 import threading
-from . import naivelog
-from .depGraph import createDepGraph
-from .loopsolvers.linearsolver import LinearSolver
-from .realtime.threadingBackend import ThreadingBackend, Platform
-from .scheduling import TopologicalScheduler
-from .tracers import Tracers
-from .state_events.locators import RegulaFalsiStateEventLocator
+from CBD import naivelog
+from CBD.depGraph import createDepGraph
+from CBD.loopsolvers.linearsolver import LinearSolver
+from CBD.realtime.threadingBackend import ThreadingBackend, Platform
+from CBD.scheduling import TopologicalScheduler
+from CBD.tracers import Tracers
+from CBD.state_events.locators import RegulaFalsiStateEventLocator
+import CBD.realtime.accurate_time as time
 
 _TQDM_FOUND = True
 try:
@@ -101,12 +101,11 @@ class Simulator:
 		self.__progress_event = None
 		self.__progress_finished = True
 		self.__logger = naivelog.getLogger("CBD")
-		self.__tracer = Tracers()
+		self.__tracer = Tracers(self)
 
 		self.__lasttime = None
 
 		self.__event_bus = []
-		# self.__event_thread = threading.Thread(target=self.__event_thread_loop)
 		self.__events = {
 			"started": [],
 			"finished": [],
@@ -156,6 +155,7 @@ class Simulator:
 			self.__realtime_start_time = time.time()
 			self.__lasttime = 0.0
 
+		self.__threading_backend.wait(0.0, self.__tracer.thread_loop)
 		self.__threading_backend.wait(0.0, self.__event_thread_loop)
 		self.signal("started")
 		if self.__realtime:
@@ -181,6 +181,9 @@ class Simulator:
 		self.signal("finished")
 		self.__finished = True
 
+		# Make sure all events are executed
+		self.__threading_backend.wait(0.0, lambda: self.__event_thread_loop(10))
+
 	def __check(self):
 		"""
 		Checks if the simulation still needs to continue.
@@ -509,16 +512,18 @@ class Simulator:
 			sufficient to do a viable simulation. This function should only be
 			used by the inner workings of the simulator and its functional parts.
 		"""
-		self.signal("prestep")
+		pre = time.time()
+		simT = self.getTime()
+		self.signal("prestep", pre, simT)
 		curIt = self.__sim_data[2]
-		self.__threading_backend.wait(0, lambda: self.__tracer.traceNewIteration(curIt, self.getTime()))
+		self.__tracer.trace(self.__tracer.traceNewIteration, (curIt, simT))
 
 		# Efficiency reasons: dep graph only changes at these times
 		#   in the given set of library blocks.
 		# TODO: Must be set to "every time" instead.
 		if curIt < 2 or self.__sim_data[0] is None:
 			self.__sim_data[0] = createDepGraph(self.model, curIt)
-		self.__sim_data[1] = self.__scheduler.obtain(self.__sim_data[0], curIt, self.getTime())
+		self.__sim_data[1] = self.__scheduler.obtain(self.__sim_data[0], curIt, simT)
 		self.__computeBlocks(self.__sim_data[1], self.__sim_data[0], self.__sim_data[2])
 		self.__sim_data[2] += 1
 		# TODO: multiple LCC
@@ -538,7 +543,8 @@ class Simulator:
 				self.__sim_data[2] = 0
 			elif event.fired:
 				event.fired = False
-		self.signal("poststep")
+		post = time.time()
+		self.signal("poststep", pre, post, self.getTime())
 
 	def _lcc_compute(self):
 		self.__computeBlocks(self.__sim_data[1], self.__sim_data[0], self.__sim_data[2])
@@ -615,7 +621,7 @@ class Simulator:
 				block = component[0]  # the strongly connected component has a single element
 				if curIteration == 0 or self.__scheduler.mustCompute(block, self.getTime()):
 					block.compute(curIteration)
-					self.__threading_backend.wait(0, lambda: self.__tracer.traceCompute(curIteration, block))
+					self.__tracer.trace(self.__tracer.traceCompute, (curIteration, block))
 			else:
 				# Detected a strongly connected component
 				self.__solver.checkValidity(self.model.getPath(), component)
@@ -625,7 +631,7 @@ class Simulator:
 					if curIteration == 0 or self.__scheduler.mustCompute(block, self.getTime()):
 						blockIndex = component.index(block)
 						block.appendToSignal(solutionVector[blockIndex])
-						self.__threading_backend.wait(0, lambda: self.__tracer.traceCompute(curIteration, block))
+						self.__tracer.trace(self.__tracer.traceCompute, (curIteration, block))
 
 	def __hasCycle(self, component, depGraph):
 		"""
@@ -671,17 +677,25 @@ class Simulator:
 			It is expected that the passed function terminates at a certain point
 			in time, to prevent an infinitely running process.
 
+		Warning:
+			There is no guarantee that these functions will be executed in-order of
+			connection. For safety, it is best to see these events as not thread-safe.
+
 		The functions will be called in the order they were connected to the
 		events, with the associated arguments. The accepted signals are:
 
-		- :code:`started`:              Raised whenever the simulation setup has
-										completed, but before the actual simulation
-										begins.
+		- :code:`started`:              Raised whenever the simulation setup has completed,
+										but before the actual simulation begins.
 		- :code:`finished`:             Raised whenever the simulation finishes.
-		- :code:`prestep`:              Raised before a step is done.
-		- :code:`poststep`:             Raised after a step is done.
-		- :code:`clock_update(delta)`:  Raised whenever the clock updates. It takes
-										the (new) delta for the simulation.
+		- :code:`prestep(t, st)`:       Raised before a step is done. :code:`t` is the real
+										time before the step and :code:`st` is the simulation
+										time.
+		- :code:`poststep(o, t, st)`:   Raised after a step is done. :code:`o` is the real
+										time before the step, :code:`t` is the real time after
+										the step and :code:`st` is the simulation time of the
+										step.
+		- :code:`clock_update(delta)`:  Raised whenever the clock updates. It takes the (new)
+										delta for the simulation.
 
 		Args:
 			name (str):     The name of the signal to raise.
@@ -721,13 +735,15 @@ class Simulator:
 			raise ValueError("Invalid signal '%s' in Simulator." % name)
 		self.__event_bus.append((name, args))
 
-	def __event_thread_loop(self):
-		if not self.__finished or len(self.__event_bus) > 0:
-			while len(self.__event_bus) > 0:
+	def __event_thread_loop(self, cnt=-1):
+		i = 0
+		while (not self.__finished or len(self.__event_bus) > 0) and (cnt == -1 or cnt <= i):
+			i += 1
+			if len(self.__event_bus) > 0:
 				name, args = self.__event_bus.pop()
 				for evt in self.__events[name]:
-					self.__threading_backend.wait(0.0, lambda: evt(*args))
-			self.__threading_backend.wait(0.05, self.__event_thread_loop)
+					evt(*args)
+			time.sleep(0.05)
 
 	def setCustomTracer(self, *tracer):
 		"""

+ 33 - 1
src/CBD/tracers/__init__.py

@@ -1,19 +1,51 @@
 """
 The tracers module provides an interface for tracing simulation data.
 """
+import time
+
 from CBD.tracers.baseTracer import BaseTracer
 
 class Tracers:
 	"""
 	Collection object for multiple tracers.
 
+	Arguments:
+		sim (Simulator):    The CBD simulator of the trace object.
+
 	Note:
 		This class will maintain and keep track of the UID of a tracer.
 		Don't set this yourself!
 	"""
-	def __init__(self):
+	def __init__(self, sim):
 		self.uid = 0
 		self.tracers = {}
+		self.bus = []
+		self.sim = sim
+
+	def hasTracers(self):
+		"""
+		Checks that there are registered tracers.
+		"""
+		return len(self.tracers) == 0
+
+	def thread_loop(self):
+		"""
+		Mainloop for tracing information. reduces the amount of threads created.
+		"""
+		while self.sim.is_running():
+			while len(self.bus) > 0:
+				evt, args = self.bus.pop(0)
+				evt(*args)
+			time.sleep(0.05)
+
+	def trace(self, event, args):
+		"""
+		Traces an event.
+		Args:
+			event (callable):   The event to trace.
+			args (iter):        The list of arguments for the event.
+		"""
+		self.bus.append((event, args))
 
 	def registerTracer(self, tracer, recover=False):
 		"""