Selaa lähdekoodia

Bouncing Ball now exists!

rparedis 3 vuotta sitten
vanhempi
commit
f6c2b96685

+ 28 - 0
examples/scripts/BouncingBall/BouncingBall.py

@@ -0,0 +1,28 @@
+from CBD.Core import *
+from CBD.lib.std import *
+from CBD.lib.endpoints import SignalCollectorBlock
+
+class BouncingBall(CBD):
+	def __init__(self, k=0.7):
+		super(BouncingBall, self).__init__("BouncingBall", output_ports=["height"])
+		self.k = k
+
+		self.addBlock(ConstantBlock("g", -9.81))
+		self.addBlock(ConstantBlock("v0", 3))
+		self.addBlock(ConstantBlock("y0", 100))
+		self.addBlock(IntegratorBlock("v"))
+		self.addBlock(IntegratorBlock("y"))
+		self.addBlock(SignalCollectorBlock("plot"))
+
+		self.addConnection("g", "v")
+		self.addConnection("v", "y")
+		self.addConnection("y", "height")
+		self.addConnection("y", "plot")
+		self.addConnection("v0", "v", input_port_name="IC")
+		self.addConnection("y0", "y", input_port_name="IC")
+
+	def bounce(self):
+		v_pre = self.getSignal("v")[-1].value
+		v_new = -v_pre * self.k
+		self.getBlockByName("v0").setValue(v_new)
+		self.getBlockByName("y0").setValue(0.0)

+ 33 - 0
examples/scripts/BouncingBall/BouncingBall_experiment.py

@@ -0,0 +1,33 @@
+from BouncingBall import *
+from CBD.realtime.plotting import PlotManager, LinePlot
+from CBD.simulator import Simulator
+import matplotlib.pyplot as plt
+
+bb = BouncingBall()
+
+TIME = 20.0
+
+fig = plt.figure(figsize=(5, 5), dpi=100)
+ax = fig.add_subplot(111)
+ax.set_xlim((0, TIME))
+ax.set_ylim((0, 105))
+plot = fig, ax
+
+manager = PlotManager()
+manager.register("height", bb.getBlockByName('plot'), plot, LinePlot(color='red'))
+
+from CBD.state_events import StateEvent, Direction
+from CBD.state_events.locators import ITPStateEventLocator
+
+def bounce(t, model):
+	print(t)
+	model.bounce()
+
+sim = Simulator(bb)
+sim.setStateEventLocator(ITPStateEventLocator())
+sim.registerStateEvent(StateEvent("height", direction=Direction.FROM_ABOVE, event=bounce))
+sim.setRealTime()
+sim.setDeltaT(0.1)
+sim.run(TIME)
+
+plt.show()

+ 0 - 1
src/CBD/Core.py

@@ -294,7 +294,6 @@ class BaseBlock:
         assert name_input in self.__nameLinks, "Cannot link blocks, no such input '%s' in %s" % (name_input, self.getPath())
         del self._linksIn[name_input]
 
-
     def __repr__(self):
         return self.getPath() + ":" + self.getBlockType()
 

+ 3 - 0
src/CBD/lib/endpoints.py

@@ -37,6 +37,9 @@ class CollectorBlock(BaseBlock):
 		"""
 		return self._data
 
+	def _rewind(self):
+		self._data.pop()
+
 
 class SignalCollectorBlock(CollectorBlock):
 	"""

+ 9 - 0
src/CBD/lib/std.py

@@ -1094,6 +1094,12 @@ class Clock(CBD):
 			return 0.0
 		return dSig[-1].value
 
+	def reset(self):
+		"""
+		Resets the clock. Required for restarting a simulation.
+		"""
+		self.clearSignals()
+
 	def _rewind(self):
 		CBD._rewind(self)
 		time = self.getInputSignal(-1, "h").value
@@ -1166,6 +1172,9 @@ class DummyClock(BaseBlock):
 		"""
 		self.__delta_t = dt
 
+	def setStartTime(self, st):
+		self.__start_time = st
+
 	def _rewind(self):
 		self.__start_time -= self.__delta_t
 

+ 45 - 5
src/CBD/simulator.py

@@ -8,6 +8,7 @@ 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
 
 _TQDM_FOUND = True
 try:
@@ -22,7 +23,7 @@ class Simulator:
 	This class implements the semantics of CBDs.
 
 	Args:
-		model (CBD):    A :class:`CBD` model to simulate.
+		model (CBD.Core.CBD):    A :class:`CBD` model to simulate.
 
 	:Defaults:
 		The following properties further define the internal mechanisms of
@@ -61,7 +62,10 @@ class Simulator:
 	     - :func:`setProgressBar`
 	   * - strong component system solver
 	     - :class:`CBD.linearsolver.LinearSolver`
-	     - N/A
+	     - :func:`setAlgebraicLoopSolver`
+	   * - state event locator
+	     - :class:`CBD.state_events.locators.RegulaFalsiStateEventLocator`
+	     - :func:`setStateEventLocator`
 	"""
 	def __init__(self, model):
 		self.model = model
@@ -110,6 +114,10 @@ class Simulator:
 			"poststep": [],
 			"clock_update": []
 		}
+
+		self.__state_events = []
+		self.__stel = None
+		self.setStateEventLocator(RegulaFalsiStateEventLocator())
 		
 		# TODO: make this variable, given more solver implementations
 		# self.__solver = SympySolver(self.__logger)
@@ -251,8 +259,7 @@ class Simulator:
 			- :func:`setDeltaT`
 			- :class:`CBD.lib.std.Clock`
 		"""
-		clock = self.getClock()
-		return clock.getInputSignal(input_port='h').value
+		return self.getClock().getDeltaT()
 
 	def setDeltaT(self, delta_t):
 		"""
@@ -299,6 +306,25 @@ class Simulator:
 		"""
 		self.__solver = solver
 
+	def setStateEventLocator(self, stel):
+		"""
+		Sets the state event locator to use when level crossings occur.
+
+		Args:
+			stel (CBD.state_events.locators.StateEventLocator): The new state event to use.
+		"""
+		self.__stel = stel
+		self.__stel.setSimulator(self)
+
+	def registerStateEvent(self, event):
+		"""
+		Registers a state event to the current simulator.
+
+		Args:
+			event (CBD.state_events.StateEvent):    The event to register.
+		"""
+		self.__state_events.append(event)
+
 	def setBlockRate(self, block_path, rate):
 		"""
 		Sets the rate for a specific block. Independent of the stepsize, the
@@ -496,7 +522,21 @@ class Simulator:
 		self.__sim_data[1] = self.__scheduler.obtain(self.__sim_data[0], curIt, self.getTime())
 		self.__computeBlocks(self.__sim_data[1], self.__sim_data[0], self.__sim_data[2])
 		self.__sim_data[2] += 1
-		# TODO: LCC
+		# TODO: multiple LCC
+		for event in self.__state_events:
+			if not event.fired and self.__stel.detect_signal(event.output_name, event.level, event.direction):
+				event.fired = True
+				t = self.__stel.run(event.output_name, event.level, event.direction)
+				event.event(t, self.model)
+
+				# Reset the model
+				self.model._rewind()
+				self.model.clearSignals()
+				self.model.getClock().setStartTime(t)
+				self.__sim_data[0] = None
+				self.__sim_data[2] = 0
+			elif event.fired:
+				event.fired = False
 		self.signal("poststep")
 
 	def _lcc_compute(self):

+ 23 - 0
src/CBD/state_events/__init__.py

@@ -19,3 +19,26 @@ class Direction(Enum):
 	FROM_ABOVE = 2
 	"""Only a crossing from above through the level causes an event."""
 
+
+class StateEvent:
+	"""
+	Data class that holds all generic state event information.
+
+	Args:
+		output_name (str):      The name of the output port to check.
+		level (float):          The value that the signal must pass through.
+								Defaults to 0.
+		direction (Direction):  The direction of the crossing.
+								Defaults to :attr:`Direction.ANY`.
+		event (callable):       A function that must be executed if the event
+								occurs. It takes two arguments: time and model.
+								In this function, it is allowed to alter any
+								and all attributes/properties/components of the
+								model. Defaults to a no-op.
+	"""
+	def __init__(self, output_name, level=0.0, direction=Direction.ANY, event=lambda t, m: None):
+		self.output_name = output_name
+		self.level = level
+		self.direction = direction
+		self.event = event
+		self.fired = False

+ 0 - 1
src/CBD/state_events/locators.py

@@ -145,7 +145,6 @@ class StateEventLocator:
 		self.setDeltaT(h)
 		return t_crossing
 
-	# TODO: is the direction even required? Isn't it automatically maintained?
 	def algorithm(self, p1, p2, output_name, level=0.0, direction=Direction.ANY):
 		"""
 		The algorithm that identifies the locator functionality. Must be implemented