1 """
2 A general-purpose Simulation class for discrete events.
3
4 The Simulation object is the central object of a simulation. It
5 handles the simulation clock time and maintains communication between
6 the components of the simulation.
7
8 A simulation is structured as a directed graph of event-processing
9 nodes called EventProcessors (EPs). EventProcessors generate
10 data-carrying Events, which are routed through the graph to other
11 EventProcessors via delayed connections. Most objects in the
12 simulation will be a subclass of EventProcessor, customized to provide
13 some specific behavior.
14
15 There are different subclasses of Event, each doing different types of
16 computation, and each can contain any arbitrary Python data. A
17 simulation begins by giving each EventProcessor an opportunity to send
18 any initial events. It then proceeds by processing and delivering
19 events to EventProcessors in time order. After all events for the
20 current time are processed, the simulation gives each EventProcessor a
21 chance to do any final computation, after which simulation time skips
22 to the time of the earliest event remaining in the queue.
23
24
25 PORTS
26
27 All connections between EPs are tagged with a source port and a
28 destination port. Ports are internal addresses that EPs can use to
29 distinguish between inputs and outputs. A port specifier can be any
30 hashable Python object. If not specified, the input and output ports
31 for a connection default to None.
32
33 src_ports distinguish different types of output that an EP may
34 produce. When sending output, the EP must call self.send_output()
35 separately for each port. dest_ports distinguish different types of
36 input an EP may receive and process. The EP is free to interpret the
37 input port designation on an incoming event in any way it chooses.
38
39 An example dest_port might be for an EP that receives 'ordinary'
40 neural activity on the default port, and receives a separate
41 modulatory signal that influences learning. The originator of the
42 modulatory signal might have a connection to the EP with dest_port =
43 'Modulation'. Multiple ports can be grouped by a dest EP by assuming
44 a convention that the keys are tuples,
45 e.g. ('JointNormalize','Group1'), ('JointNormalize','Group2').
46
47 An example src_port use might be an EP that sends different events to
48 itself than it sends out to other EPs. In this case the self
49 connections might have src_port = 'Recurrent', and probably also a
50 special dest_port.
51
52
53 $Id: simulation.py 11090 2010-06-22 11:09:01Z ceball $
54 """
55 __version__='$Revision: 11090 $'
56
57 import param
58 from param.parameterized import as_uninitialized,OptionalSingleton
59
60 from copy import copy, deepcopy
61
62 import bisect
63
64
65 SLEEP_EXCEPTION = "Sleep Exception"
66 STOP = "Simulation Stopped"
67
68
69
70
71
72 Forever = -1
73
74
75
76
77
78 simulation_path="topo.sim"
84 """
85 Base class for EventProcessors, i.e. objects that can accept and
86 handle events. This base class handles the basic mechanics of
87 connections and sending events, and stores both incoming and outgoing
88 connections.
89
90 The dest_ports attribute specifies which dest_ports are supported
91 by this class; subclasses can augment or change this list if they
92 wish. The special value dest_ports=None means to accept
93 connections to any dest_port, while dest_ports=[None,'Trigger']
94 means that only connections to port None or port 'Trigger' are
95 accepted.
96
97 Similarly, the src_ports attribute specifies which src_ports will
98 be given output by this class.
99 """
100 __abstract = True
101
102 dest_ports=[None]
103
104 src_ports=[None]
105
106
108 """
109 Create an EventProcessor.
110
111 Note that just creating an EventProcessor does not mean it is
112 part of the simulation (i.e. it is not in the simulation's list
113 of EventProcessors, and it will not have its start() method called).
114 To add an EventProcessor e to a simulation s, simply do
115 s['name_of_e']=e. At this point, e's 'name' attribute will be set
116 to 'name_of_e'.
117 """
118 super(EventProcessor,self).__init__(**params)
119
120
121
122 self.in_connections = []
123 self.out_connections = []
124
125 self.simulation = None
126
128 """
129 Returns True if the given key matches any port on the given list.
130
131 In the default implementation, a port is considered a match if
132 the port is == to the key, but subclasses of EventProcessor can
133 override this to provide weaker forms of matching.
134 """
135 return key in portlist
136
137
138
139
141 """
142 Add the specified connection to the list of outgoing connections.
143 Should only be called from Simulation.connect().
144 """
145
146 if self.src_ports and not self._port_match(conn.src_port,self.src_ports):
147 raise ValueError("%s is not on the list of ports provided for outgoing connections for %s: %s." %
148 (str(conn.src_port), self.__class__, str(self.src_ports)))
149
150
151
152 for existing_connection in self.out_connections:
153 if existing_connection.name==conn.name and existing_connection.dest==conn.dest:
154 raise ValueError('A connection out of an EventProcessor must have a unique name among connections to a particular destination; "%s" out of %s into %s already exists'%(conn.name,conn.dest,self.name))
155
156
157
158
159
160
161 self.out_connections.append(conn)
162
163
165 """
166 Add the specified connection to the list of incoming connections.
167 Should only be called from Simulation.connect().
168 """
169
170 if self.dest_ports and not self._port_match(conn.dest_port,self.dest_ports):
171 raise ValueError("%s is not on the list of ports allowed for incoming connections for %s: %s." %
172 (str(conn.dest_port), self.__class__, str(self.dest_ports)))
173
174 for existing_connection in self.in_connections:
175 if existing_connection.name == conn.name:
176 raise ValueError('A connection into an EventProcessor must have a unique name; "%s" into %s already exists'%(conn.name,self.name))
177
178 self.in_connections.append(conn)
179
180
182 """
183 Called by the simulation when the EventProcessor is added to
184 the simulation.
185
186 If an EventProcessor needs to have any code run when it is
187 added to the simulation, the code can be put into this method
188 in the subclass.
189 """
190 pass
191
192
194 """
195 Send some data out to all connections on the given src_port.
196 The data is deepcopied before it is sent out, to ensure that
197 future changes to the data are not reflected in events from
198 the past.
199 """
200
201 out_conns_on_src_port = [conn for conn in self.out_connections
202 if self._port_match(conn.src_port,[src_port])]
203
204 data=deepcopy(data)
205 for conn in out_conns_on_src_port:
206
207 e=EPConnectionEvent(conn.delay+self.simulation.time(),conn,data,deep_copy=False)
208 self.simulation.enqueue_event(e)
209
210
217
218
220 """
221 Called by the simulation before advancing the simulation
222 time. Allows the event processor to do any computation that
223 requires that all events for this time have been delivered.
224 Computations performed in this method should not generate any
225 events with a zero time delay, or else causality could be
226 violated. (By default, does nothing.)
227 """
228 pass
229
234
239 """Parameter whose value can be any EventProcessor instance."""
240
243
249
250
251 from param import parameterized
254 """
255 EPConnection stores basic information for a connection between
256 two EventProcessors.
257 """
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279 src = param.Parameter(default=None,constant=True,precedence=0.10,doc=
280 """The EventProcessor from which messages originate.""")
281
282 dest = param.Parameter(default=None,constant=True,precedence=0.11,doc=
283 """The EventProcessor to which messages are delivered.""")
284
285 src_port = param.Parameter(default=None,precedence=0.20,doc=
286 """
287 Identifier that can be used to distinguish different types of outgoing connections.
288
289 EventProcessors that generate only a single type of
290 outgoing event will typically use a src_port of None. However,
291 if multiple types of communication are meaningful, the
292 EventProcessor can accept other values for src_port. It is up
293 to the src EventProcessor to deliver appropriate data to each
294 port, and to declare what will be sent over that port.
295 """)
296
297 dest_port = param.Parameter(default=None,precedence=0.21,doc=
298 """
299 Identifier that can be used to distinguish different types of incoming connections.
300
301 EventProcessors that accept only a single type of incoming
302 event will typically use a src_port of None. However, if
303 multiple types of communication are meaningful, the
304 EventProcessor can accept other values for dest_port. It is up
305 to the dest EventProcessor to process the data appropriately
306 for each port, and to define what is expected to be sent to
307 that port.
308 """)
309
310 delay = param.Number(default=0,doc=
311 """Simulation time between when each Event is generated by the src and when it is delivered to the dest.""")
312
313 private = param.Boolean(default=False,doc=
314 """Set to true if this connection is for internal use only, not to be manipulated by a user.""")
315
316
317
318
320 """
321 Remove this connection from its src's list of out_connections and its
322 dest's list of in_connections.
323 """
324
325 i = 0
326 to_del = []
327 for in_conn in self.dest.in_connections:
328 if in_conn is self:
329 to_del.append(i)
330 i+=1
331
332 for i in to_del:
333 del self.dest.in_connections[i]
334
335
336 i = 0
337 to_del = []
338 for out_conn in self.src.out_connections:
339 if out_conn is self:
340 to_del.append(i)
341 i+=1
342
343 for i in to_del:
344 del self.src.out_connections[i]
345
346
348 """Generate a runnable command for creating this connection."""
349
350 if self.private:
351 return ""
352
353 settings=[]
354 for name,val in self.get_param_values():
355 try:
356 if issubclass(val,object):
357 rep=val.__name__
358
359 cls = val.__name__
360 mod = val.__module__
361 imports.append("from %s import %s" % (mod,cls))
362 except TypeError:
363 if name=="src" or name=="dest":
364 rep=None
365 else:
366 rep = parameterized.script_repr(val,imports,prefix,settings)
367
368 if rep is not None:
369 settings.append('%s=%s' % (name,rep))
370
371
372 cls = self.__class__.__name__
373 mod = self.__module__
374 imports.append("from %s import %s" % (mod,cls))
375
376 return simulation_path+".connect('"+self.src.name+"','"+self.dest.name+ \
377 "',connection_type="+self.__class__.__name__+ \
378 ",\n"+prefix+(",\n"+prefix).join(settings) + ")"
379
380
381
382 -class Event(object):
383 """Hierarchy of classes for storing simulation events of various types."""
384
387
389 """
390 Cause some computation to be performed, deliver a message, etc.,
391 as appropriate for each subtype of Event. Should be passed the
392 simulation object, to allow access to .time() etc.
393 """
394 raise NotImplementedError
395
397 """
398 Implements event comparison by time, allowing sorting,
399 and queue maintenance using bisect module or minheap
400 implementations, if needed.
401
402 NOTE: identity comparisons should always be done using the
403 'is' operator, not '=='.
404 """
405 if self.time > ev.time:
406 return 1
407 elif self.time < ev.time:
408 return -1
409 else:
410 return 0
411
414 """
415 An Event for delivery to an EPConnection.
416
417 Provides access to a data field, which can be used for anything
418 the src wants to provide, and a link to the connection over which
419 it has arrived, so that the dest can determine what to do with the
420 data.
421
422 By default, the data is deepcopied before being added to this
423 instance for safety (e.g. so that future changes to data
424 structures do not affect messages arriving from the past).
425 However, if you can ensure that the copying is not
426 necessary (e.g. if you deepcoy before sending a set of
427 identical messages), then you can pass deep_copy=False
428 to avoid the copy.
429 """
430
431 - def __init__(self,time,conn,data=None,deep_copy=True):
436
439
441 return "EPConnectionEvent(time="+`self.time`+",conn="+`self.conn`+")"
442
445 """An Event consisting of a command string to execute."""
446
447 - def __init__(self,time,command_string):
448 """
449 Add the event to the simulation.
450
451 Raises an exception if the command_string contains a syntax
452 error.
453 """
454 self.command_string = command_string
455 self.__test()
456 super(CommandEvent,self).__init__(time)
457
459 return "CommandEvent(time="+`self.time`+", command_string='"+self.command_string+"')"
460
461
463 """Generate a runnable command for creating this CommandEvent."""
464 return simulation_path+'.schedule_command('\
465 +`self.time`+',"'+self.command_string+'")'
466
467
469 """
470 exec's the command_string in __main__.__dict__.
471
472 Be sure that any required items will be present in
473 __main__.__dict__; in particular, consider what will be present
474 after the network is saved and restored. For instance, results of
475 scripts you have run, or imports they make---all currently
476 available in __main__.__dict__---will not be saved with the
477 network.
478 """
479
480 import __main__
481
482 param.Parameterized(name='CommandEvent').message("Running command %s" \
483 % (self.command_string))
484 try:
485 exec self.command_string in __main__.__dict__
486 except:
487 print "Error in scheduled command:"
488 raise
489
491 """
492 Check for SyntaxErrors in the command.
493 """
494 try:
495 compile(self.command_string,"CommandString","single")
496 except SyntaxError:
497 print "Error in scheduled command:"
498 raise
499
502 """
503 Event that executes a given function function(*args,**kw).
504 """
510
512 self.fn(*self.args,**self.kw)
513
515 return 'FunctionEvent(%s,%s,*%s,**%s)' % (`self.time`,`self.fn`,`self.args`,`self.kw`)
516
518 """
519 Event that contains a sequence of other events to be scheduled and
520 executed.
521
522 The .time attributes of the events in the sequence are interpreted
523 as offsets relative to the start time of the sequence itself.
524 """
528
530
531
532
533 sched_time = sim.time()
534 for ev in self.sequence:
535 new_ev = copy(ev)
536 sched_time += ev.time
537 new_ev.time = sched_time
538 sim.enqueue_event(new_ev)
539
541 return 'EventSequence(%s,%s)' % (`self.time`,`self.sequence`)
542
546 """
547 An EventSequence that reschedules itself periodically
548
549 Takes a period argument that determines how often the sequence
550 will be scheduled. If the length of the sequence is longer than
551 the period, then the length of the sequence will be used as the period.
552 """
553
554
555
556
557
558
559
560
561
562 - def __init__(self,time,period,sequence):
565
567 super(PeriodicEventSequence,self).__call__(sim)
568
569
570 seq_length = sum(e.time for e in self.sequence)
571
572 if seq_length < self.period:
573
574
575 self.time += self.period
576 else:
577
578
579 self.time += seq_length
580 sim.enqueue_event(self)
581
583 return 'PeriodicEventSequence(%s,%s,%s)' % (`self.time`,`self.period`,`self.sequence`)
584
585
586
587
588
589
590
591
592
593
594 import time
595 from math import floor