~ubuntu-branches/ubuntu/precise/brian/precise

« back to all changes in this revision

Viewing changes to brian/network.py

  • Committer: Bazaar Package Importer
  • Author(s): Yaroslav Halchenko
  • Date: 2010-11-02 18:19:15 UTC
  • Revision ID: james.westby@ubuntu.com-20101102181915-ivwy29820copccu2
Tags: upstream-1.2.2~svn2229
ImportĀ upstreamĀ versionĀ 1.2.2~svn2229

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# ----------------------------------------------------------------------------------
 
2
# Copyright ENS, INRIA, CNRS
 
3
# Contributors: Romain Brette (brette@di.ens.fr) and Dan Goodman (goodman@di.ens.fr)
 
4
 
5
# Brian is a computer program whose purpose is to simulate models
 
6
# of biological neural networks.
 
7
 
8
# This software is governed by the CeCILL license under French law and
 
9
# abiding by the rules of distribution of free software.  You can  use, 
 
10
# modify and/ or redistribute the software under the terms of the CeCILL
 
11
# license as circulated by CEA, CNRS and INRIA at the following URL
 
12
# "http://www.cecill.info". 
 
13
 
14
# As a counterpart to the access to the source code and  rights to copy,
 
15
# modify and redistribute granted by the license, users are provided only
 
16
# with a limited warranty  and the software's author,  the holder of the
 
17
# economic rights,  and the successive licensors  have only  limited
 
18
# liability. 
 
19
 
20
# In this respect, the user's attention is drawn to the risks associated
 
21
# with loading,  using,  modifying and/or developing or reproducing the
 
22
# software by the user in light of its specific status of free software,
 
23
# that may mean  that it is complicated to manipulate,  and  that  also
 
24
# therefore means  that it is reserved for developers  and  experienced
 
25
# professionals having in-depth computer knowledge. Users are therefore
 
26
# encouraged to load and test the software's suitability as regards their
 
27
# requirements in conditions enabling the security of their systems and/or 
 
28
# data to be ensured and,  more generally, to use and operate it in the 
 
29
# same conditions as regards security. 
 
30
 
31
# The fact that you are presently reading this means that you have had
 
32
# knowledge of the CeCILL license and that you accept its terms.
 
33
# ----------------------------------------------------------------------------------
 
34
 
35
'''
 
36
Network class
 
37
'''
 
38
__all__ = ['Network', 'MagicNetwork', 'NetworkOperation', 'network_operation', 'run',
 
39
           'reinit', 'stop', 'clear', 'forget', 'recall']
 
40
 
 
41
from Queue import Queue
 
42
from connections import *
 
43
from neurongroup import NeuronGroup
 
44
from clock import guess_clock, Clock
 
45
import magic
 
46
from inspect import *
 
47
from operator import isSequenceType
 
48
import types
 
49
from itertools import chain
 
50
from collections import defaultdict
 
51
import copy
 
52
from base import *
 
53
from units import second
 
54
import time
 
55
from utils.progressreporting import *
 
56
from globalprefs import *
 
57
import gc
 
58
 
 
59
globally_stopped = False
 
60
 
 
61
 
 
62
class Network(object):
 
63
    '''
 
64
    Contains simulation objects and runs simulations
 
65
    
 
66
    **Initialised as:** ::
 
67
    
 
68
        Network(...)
 
69
    
 
70
    with ``...`` any collection of objects that should be added to the :class:`Network`.
 
71
    You can also pass lists of objects, lists of lists of objects, etc. Objects
 
72
    that need to passed to the :class:`Network` object are:
 
73
    
 
74
    * :class:`NeuronGroup` and anything derived from it such as :class:`PoissonGroup`.
 
75
    * :class:`Connection` and anything derived from it.
 
76
    * Any monitor such as :class:`SpikeMonitor` or :class:`StateMonitor`.
 
77
    * Any network operation defined with the :func:`network_operation` decorator.
 
78
    
 
79
    Models, equations, etc. do not need to be passed to the :class:`Network` object. 
 
80
    
 
81
    The most important method is the ``run(duration)`` method which runs the simulation
 
82
    for the given length of time (see below for details about what happens when you
 
83
    do this).
 
84
    
 
85
    **Example usage:** ::
 
86
    
 
87
        G = NeuronGroup(...)
 
88
        C = Connection(...)
 
89
        net = Network(G,C)
 
90
        net.run(1*second)
 
91
    
 
92
    **Methods**
 
93
    
 
94
    ``add(...)``
 
95
        Add additional objects after initialisation, works the same way
 
96
        as initialisation.
 
97
    ``run(duration[, report[, report_period]])``
 
98
        Runs the network for the given duration. See below for details about
 
99
        what happens when you do this. See documentation for :func:`run` for
 
100
        an explanation of the ``report`` and ``report_period`` keywords.
 
101
    ``reinit()``
 
102
        Reinitialises the network, runs each object's ``reinit()`` and each
 
103
        clock's ``reinit()`` method (resetting them to 0).
 
104
    ``stop()``
 
105
        Can be called from a :func:`network_operation` for example to stop the
 
106
        network from running.
 
107
    ``__len__()``
 
108
        Returns the number of neurons in the network.
 
109
    ``__call__(obj)``
 
110
        Similar to ``add``, but you can only pass one object and that
 
111
        object is returned. You would only need this in obscure
 
112
        circumstances where objects needed to be added to the network
 
113
        but were either not stored elsewhere or were stored in a way
 
114
        that made them difficult to extract, for example below the
 
115
        NeuronGroup object is only added to the network if certain
 
116
        conditions hold::
 
117
        
 
118
            net = Network(...)
 
119
            if some_condition:
 
120
                x = net(NeuronGroup(...))
 
121
    
 
122
    **What happens when you run**
 
123
    
 
124
    For an overview, see the Concepts chapter of the main documentation.
 
125
    
 
126
    When you run the network, the first thing that happens is that it
 
127
    checks if it has been prepared and calls the ``prepare()`` method
 
128
    if not. This just does various housekeeping tasks and optimisations
 
129
    to make the simulation run faster. Also, an update schedule is
 
130
    built at this point (see below).
 
131
    
 
132
    Now the ``update()`` method is repeatedly called until every clock
 
133
    has run for the given length of time. After each call of the
 
134
    ``update()`` method, the clock is advanced by one tick, and if
 
135
    multiple clocks are being used, the next clock is determined (this
 
136
    is the clock whose value of ``t`` is minimal amongst all the clocks).
 
137
    For example, if you had two clocks in operation, say ``clock1`` with
 
138
    ``dt=3*ms`` and ``clock2`` with ``dt=5*ms`` then this will happen:
 
139
    
 
140
    1. ``update()`` for ``clock1``, tick ``clock1`` to ``t=3*ms``, next
 
141
       clock is ``clock2`` with ``t=0*ms``.
 
142
    2. ``update()`` for ``clock2``, tick ``clock2`` to ``t=5*ms``, next
 
143
       clock is ``clock1`` with ``t=3*ms``.
 
144
    3. ``update()`` for ``clock1``, tick ``clock1`` to ``t=6*ms``, next
 
145
       clock is ``clock2`` with ``t=5*ms``.
 
146
    4. ``update()`` for ``clock2``, tick ``clock2`` to ``t=10*ms``, next
 
147
       clock is ``clock1`` with ``t=6*ms``.
 
148
    5. ``update()`` for ``clock1``, tick ``clock1`` to ``t=9*ms``, next
 
149
       clock is ``clock1`` with ``t=9*ms``.
 
150
    6. ``update()`` for ``clock1``, tick ``clock1`` to ``t=12*ms``, next
 
151
       clock is ``clock2`` with ``t=10*ms``. etc.
 
152
    
 
153
    The ``update()`` method simply runs each operation in the current clock's
 
154
    update schedule. See below for details on the update schedule.
 
155
    
 
156
    **Update schedules**
 
157
    
 
158
    An update schedule is the sequence of operations that are
 
159
    called for each ``update()`` step. The standard update schedule is:
 
160
    
 
161
    *  Network operations with ``when = 'start'``
 
162
    *  Network operations with ``when = 'before_groups'``
 
163
    *  Call ``update()`` method for each :class:`NeuronGroup`, this typically
 
164
       performs an integration time step for the differential equations
 
165
       defining the neuron model.
 
166
    *  Network operations with ``when = 'after_groups'``
 
167
    *  Network operations with ``when = 'middle'``
 
168
    *  Network operations with ``when = 'before_connections'``
 
169
    *  Call ``do_propagate()`` method for each :class:`Connection`, this
 
170
       typically adds a value to the target state variable of each neuron
 
171
       that a neuron that has fired is connected to. See Tutorial 2: Connections for
 
172
       a more detailed explanation of this.
 
173
    *  Network operations with ``when = 'after_connections'``
 
174
    *  Network operations with ``when = 'before_resets'``
 
175
    *  Call ``reset()`` method for each :class:`NeuronGroup`, typically resets a
 
176
       given state variable to a given reset value for each neuron that fired
 
177
       in this update step.
 
178
    *  Network operations with ``when = 'after_resets'``
 
179
    *  Network operations with ``when = 'end'``
 
180
    
 
181
    There is one predefined alternative schedule, which you can choose by calling
 
182
    the ``update_schedule_groups_resets_connections()`` method before running the
 
183
    network for the first time. As the name suggests, the reset operations are
 
184
    done before connections (and the appropriately named network operations are
 
185
    called relative to this rearrangement). You can also define your own update
 
186
    schedule with the ``set_update_schedule`` method (see that method's API documentation for
 
187
    details). This might be useful for example if you have a sequence of network
 
188
    operations which need to be run in a given order.    
 
189
    '''
 
190
 
 
191
    operations = property(fget=lambda self:self._all_operations)
 
192
 
 
193
    def __init__(self, *args, **kwds):
 
194
        self.clock = None # Initialized later
 
195
        self.groups = []
 
196
        self.connections = []
 
197
        # The following dict keeps a copy of which operations are in which slot
 
198
        self._operations_dict = defaultdict(list)
 
199
        self._all_operations = []
 
200
        self.update_schedule_standard()
 
201
        self.prepared = False
 
202
        for o in chain(args, kwds.itervalues()):
 
203
            self.add(o)
 
204
 
 
205
    def add(self, *objs):
 
206
        """
 
207
        Add an object or container of objects to the network
 
208
        """
 
209
        for obj in objs:
 
210
            if isinstance(obj, NeuronGroup):
 
211
                if obj not in self.groups:
 
212
                    self.groups.append(obj)
 
213
            elif isinstance(obj, Connection):
 
214
                if obj not in self.connections:
 
215
                    self.connections.append(obj)
 
216
            elif isinstance(obj, NetworkOperation):
 
217
                if obj not in self._all_operations:
 
218
                    self._operations_dict[obj.when].append(obj)
 
219
                    self._all_operations.append(obj)
 
220
            elif isSequenceType(obj):
 
221
                for o in obj:
 
222
                    self.add(o)
 
223
            else:
 
224
                raise TypeError('Only the following types of objects can be added to a network: NeuronGroup, Connection or NetworkOperation')
 
225
 
 
226
            try:
 
227
                gco = obj.contained_objects
 
228
                if gco is not None:
 
229
                    self.add(gco)
 
230
            except AttributeError:
 
231
                pass
 
232
 
 
233
    def __call__(self, obj):
 
234
        """
 
235
        Add an object to the network and return it
 
236
        """
 
237
        self.add(obj)
 
238
        return obj
 
239
 
 
240
    def reinit(self):
 
241
        '''
 
242
        Resets the objects and clocks.
 
243
        '''
 
244
        objs = self.groups + self.connections + self.operations
 
245
        if self.clock is not None:
 
246
            objs.append(self.clock)
 
247
        else:
 
248
            guess_clock(None).reinit()
 
249
        if hasattr(self, 'clocks'):
 
250
            objs.extend(self.clocks)
 
251
        for P in objs:
 
252
            if hasattr(P, 'reinit'):
 
253
                P.reinit()
 
254
 
 
255
    def prepare(self):
 
256
        '''
 
257
        Prepares the network for simulation:
 
258
        + Checks the clocks of the neuron groups
 
259
        + Gather connections with identical subgroups
 
260
        + Compresses the connection matrices for faster simulation
 
261
        Calling this function is not mandatory but speeds up the simulation.
 
262
        '''
 
263
        # Set the clock
 
264
        if self.same_clocks():
 
265
            self.set_clock()
 
266
        else:
 
267
            self.set_many_clocks()
 
268
 
 
269
        # Gather connections with identical subgroups
 
270
        # 'subgroups' maps subgroups to connections (initialize with immutable object (not [])!)
 
271
        subgroups = dict.fromkeys([(C.source, C.delay) for C in self.connections], None)
 
272
        for C in self.connections:
 
273
            if subgroups[(C.source, C.delay)] == None:
 
274
                subgroups[(C.source, C.delay)] = [C]
 
275
            else:
 
276
                subgroups[(C.source, C.delay)].append(C)
 
277
        self.connections = subgroups.values()
 
278
        cons = self.connections # just for readability
 
279
        for i in range(len(cons)):
 
280
            if len(cons[i]) > 1: # at least 2 connections with the same subgroup
 
281
                cons[i] = MultiConnection(cons[i][0].source, cons[i])
 
282
            else:
 
283
                cons[i] = cons[i][0]
 
284
 
 
285
        # Compress connections
 
286
        for C in self.connections:
 
287
            C.compress()
 
288
 
 
289
        # Experimental support for new propagation code
 
290
        if get_global_preference('usenewpropagate') and get_global_preference('useweave'):
 
291
            from experimental.new_c_propagate import make_new_connection
 
292
            for C in self.connections:
 
293
                make_new_connection(C)
 
294
 
 
295
        # build operations list for each clock
 
296
        self._build_update_schedule()
 
297
 
 
298
        self.prepared = True
 
299
 
 
300
    def update_schedule_standard(self):
 
301
        self._schedule = ['ops start',
 
302
                          'ops before_groups',
 
303
                          'groups',
 
304
                          'ops after_groups',
 
305
                          'ops middle',
 
306
                          'ops before_connections',
 
307
                          'connections',
 
308
                          'ops after_connections',
 
309
                          'ops before_resets',
 
310
                          'resets',
 
311
                          'ops after_resets',
 
312
                          'ops end'
 
313
                          ]
 
314
        self._build_update_schedule()
 
315
 
 
316
    def update_schedule_groups_resets_connections(self):
 
317
        self._schedule = ['ops start',
 
318
                          'ops before_groups',
 
319
                          'groups',
 
320
                          'ops after_groups',
 
321
                          'ops middle',
 
322
                          'ops before_resets',
 
323
                          'resets',
 
324
                          'ops after_resets',
 
325
                          'ops before_connections',
 
326
                          'connections',
 
327
                          'ops after_connections',
 
328
                          'ops end'
 
329
                          ]
 
330
        self._build_update_schedule()
 
331
 
 
332
    def set_update_schedule(self, schedule):
 
333
        """
 
334
        Defines a custom update schedule
 
335
        
 
336
        A custom update schedule is a list of schedule items. Each update
 
337
        step of the network, the schedule items will be run in turn. A
 
338
        schedule item can be defined as a string or tuple. The following
 
339
        string definitions are possible:
 
340
        
 
341
        'groups'
 
342
            Calls the 'update' function of each group in turn, this is
 
343
            typically the integration step of the simulation.
 
344
        'connections'
 
345
            Calls the 'do_propagate' function of each connection in
 
346
            turn, this is typically propagating spikes forward (and
 
347
            backward in the case of STDP).
 
348
        'resets'
 
349
            Calls the 'reset' function of each group in turn.
 
350
        'ops '+name
 
351
            Calls each operation in turn whose 'when' parameter is
 
352
            set to 'name'. The standard set of 'when' names is
 
353
            start, before_groups, after_groups, before_resets,
 
354
            after_resets, before_connections, after_connections,
 
355
            end, but you can use any you like.
 
356
        
 
357
        If a tuple is provided, it should be of the form:
 
358
        
 
359
            (objset, func, allclocks)
 
360
        
 
361
        with:
 
362
        
 
363
        objset
 
364
            a list of objects to be processed
 
365
        func
 
366
            Either None or a string. In the case of none, each
 
367
            object in objset must be callable and will be called.
 
368
            In the case of a string, obj.func will be called for
 
369
            each obj in objset.
 
370
        allclocks
 
371
            Either True or False. If it's set to True, then the
 
372
            object will be placed in the update schedule of
 
373
            every clock in the network. If False, it will be
 
374
            placed in the update schedule only of the clock
 
375
            obj.clock.
 
376
        """
 
377
        self._schedule = schedule
 
378
        self._build_update_schedule()
 
379
 
 
380
    def _build_update_schedule(self):
 
381
        '''
 
382
        Defines what the update step does
 
383
        
 
384
        For each clock we build a list self._update_schedule[id(clock)]
 
385
        of functions which are called at the update step if that clock
 
386
        is active. This is generic and works for single or multiple
 
387
        clocks.
 
388
        
 
389
        See documentation for set_update_schedule for an explanation of
 
390
        the self._schedule object. 
 
391
        '''
 
392
        self._update_schedule = defaultdict(list)
 
393
        if hasattr(self, 'clocks'):
 
394
            clocks = self.clocks
 
395
        else:
 
396
            clocks = [self.clock]
 
397
        clockset = clocks
 
398
        for item in self._schedule:
 
399
            # we define some simple names for common schedule items
 
400
            if isinstance(item, str):
 
401
                if item == 'groups':
 
402
                    objset = self.groups
 
403
                    objfun = 'update'
 
404
                    allclocks = False
 
405
                elif item == 'resets':
 
406
                    objset = self.groups
 
407
                    objfun = 'reset'
 
408
                    allclocks = False
 
409
                elif item == 'connections':
 
410
                    objset = self.connections
 
411
                    objfun = 'do_propagate'
 
412
                    allclocks = False
 
413
                    # Connections do not define their own clock, but they should
 
414
                    # be updated on the schedule of their source group
 
415
                    for obj in objset:
 
416
                        obj.clock = obj.source.clock
 
417
                elif len(item) > 4 and item[0:3] == 'ops': # the item is of the forms 'ops when'
 
418
                    objset = self._operations_dict[item[4:]]
 
419
                    objfun = None
 
420
                    allclocks = False
 
421
            else:
 
422
                # we allow the more general form of usage as well
 
423
                objset, objfun, allclocks = item
 
424
            for obj in objset:
 
425
                if objfun is None:
 
426
                    f = obj
 
427
                else:
 
428
                    f = getattr(obj, objfun)
 
429
                if not allclocks:
 
430
                    useclockset = [obj.clock]
 
431
                else:
 
432
                    useclockset = clockset
 
433
                for clock in useclockset:
 
434
                    self._update_schedule[id(clock)].append(f)
 
435
 
 
436
    def update(self):
 
437
        for f in self._update_schedule[id(self.clock)]:
 
438
            f()
 
439
 
 
440
    """
 
441
    def update_threaded(self,queue):
 
442
        '''
 
443
        EXPERIMENTAL (not useful for the moment)
 
444
        Parallel update of the network (using threads).
 
445
        '''
 
446
        # Update groups: one group = one thread
 
447
        for P in self.groups:
 
448
            queue.put(P)
 
449
        queue.join() # Wait until job is done
 
450
 
 
451
        # The following is done serially
 
452
        # Propagate spikes
 
453
        for C in self.connections:
 
454
            C.propagate(C.source.get_spikes(C.delay))
 
455
            
 
456
        # Miscellanous operations
 
457
        for op in self.operations:
 
458
            op()
 
459
    """
 
460
 
 
461
    def run(self, duration, threads=1, report=None, report_period=10 * second):
 
462
        '''
 
463
        Runs the simulation for the given duration.
 
464
        '''
 
465
        global globally_stopped
 
466
        self.stopped = False
 
467
        globally_stopped = False
 
468
        if not self.prepared:
 
469
            self.prepare()
 
470
        self.clock.set_duration(duration)
 
471
        try:
 
472
            for c in self.clocks:
 
473
                c.set_duration(duration)
 
474
        except AttributeError:
 
475
            pass
 
476
        if report is not None:
 
477
            start_time = time.time()
 
478
            if not isinstance(report, ProgressReporter):
 
479
                report = ProgressReporter(report, report_period)
 
480
                next_report_time = start_time + float(report_period)
 
481
            else:
 
482
                report_period = report.period
 
483
                next_report_time = report.next_report_time
 
484
 
 
485
        if self.clock.still_running() and not self.stopped and not globally_stopped:
 
486
            not_same_clocks = not self.same_clocks()
 
487
            while self.clock.still_running() and not self.stopped and not globally_stopped:
 
488
                if report is not None:
 
489
                    cur_time = time.time()
 
490
                    if cur_time > next_report_time:
 
491
                        next_report_time = cur_time + float(report_period)
 
492
                        report.update((self.clock.t - self.clock.start) / duration)
 
493
                self.update()
 
494
                self.clock.tick()
 
495
                if not_same_clocks:
 
496
                    # Find the next clock to update
 
497
                    self.clock = min([(clock.t, clock) for clock in self.clocks])[1]
 
498
        if report is not None:
 
499
            report.update(1.0)
 
500
 
 
501
    def stop(self):
 
502
        '''
 
503
        Stops the network from running, this is reset the next time ``run()`` is called.
 
504
        '''
 
505
        self.stopped = True
 
506
 
 
507
    def same_clocks(self):
 
508
        '''
 
509
        Returns True if the clocks of all groups and operations are the same.
 
510
        '''
 
511
        clock = self.groups[0].clock
 
512
        return all([obj.clock == clock for obj in self.groups + self.operations])
 
513
 
 
514
    def set_clock(self):
 
515
        '''
 
516
        Sets the clock and checks that clocks of all groups are synchronized.
 
517
        '''
 
518
        if self.same_clocks():
 
519
            self.clock = self.groups[0].clock
 
520
        else:
 
521
            raise TypeError, 'Clocks are not synchronized!' # other error type?
 
522
 
 
523
    def set_many_clocks(self):
 
524
        '''
 
525
        Sets a list of clocks.
 
526
        self.clock points to the current clock between considered.
 
527
        '''
 
528
        self.clocks = list(set([obj.clock for obj in self.groups + self.operations]))
 
529
        self.clock = min([(clock.t, clock) for clock in self.clocks])[1]
 
530
 
 
531
    def __len__(self):
 
532
        '''
 
533
        Number of neurons in the network
 
534
        '''
 
535
        n = 0
 
536
        for P in self.groups:
 
537
            n += len(P) # use compact iterator function?
 
538
        return n
 
539
 
 
540
    def __repr__(self):
 
541
        return 'Network of' + str(len(self)) + 'neurons'
 
542
 
 
543
    # TODO: obscure custom update schedules might still lead to unpicklable Network object
 
544
    def __reduce__(self):
 
545
        # This code might need some explanation:
 
546
        #
 
547
        # The problem with pickling the Network object is that you cannot pickle
 
548
        # 'instance methods', that is a copy of a method of an instance. The
 
549
        # Network object does this because the _update_schedule attribute stores
 
550
        # a copy of all the functions that need to be called each time step, and
 
551
        # these are all instance methods (of NeuronGroup, Reset, etc.). So, we
 
552
        # solve the problem by deleting this attribute at pickling time, and then
 
553
        # rebuilding it at unpickling time. The function unpickle_network defined
 
554
        # below does the unpickling.
 
555
        #
 
556
        # We basically want to make a copy of the current object, and delete the
 
557
        # update schedule from it, and then pickle that. Some weird recursive
 
558
        # stuff happens if you try to do this in the obvious way, so we take the
 
559
        # seemingly mad step of converting the object to a general 'heap' class
 
560
        # (that is, a new-style class with no methods or anything, in this case
 
561
        # the NetworkNoMethods class defined below), do all our operations on
 
562
        # this, store a copy of the actual class of the object (which may not be
 
563
        # Network for derived classes), work with this, and then restore
 
564
        # everything back to the way it was when everything is done.
 
565
        #
 
566
        oldclass = self.__class__ # class may be derived from Network
 
567
        self.__class__ = NetworkNoMethods # stops recursion in copy.copy
 
568
        net = copy.copy(self) # we make a copy because after returning from this function we can't restore the class
 
569
        self.__class__ = oldclass # restore the class of the original, which is now back in its original state
 
570
        net._update_schedule = None # remove the problematic element from the copy
 
571
        return (unpickle_network, (oldclass, net)) # the unpickle_network function called with arguments oldclass, net restores it as it was
 
572
 
 
573
# This class just used as a general 'heap' class - has no methods but can have attributes
 
574
class NetworkNoMethods(object):
 
575
    pass
 
576
 
 
577
def unpickle_network(oldclass, net):
 
578
    # See Network.__reduce__ for an explanation, basically the _update_schedule
 
579
    # cannot be pickled because it contains instance methods, but it can just be
 
580
    # rebuilt.
 
581
    net.__class__ = oldclass
 
582
    net._build_update_schedule()
 
583
    return net
 
584
 
 
585
 
 
586
class NetworkOperation(magic.InstanceTracker, ObjectContainer):
 
587
    """Callable class for operations that should be called every update step
 
588
    
 
589
    Typically, you should just use the :func:`network_operation` decorator, but if you
 
590
    can't for whatever reason, use this. Note: current implementation only works for
 
591
    functions, not any callable object.
 
592
    
 
593
    **Initialisation:** ::
 
594
    
 
595
        NetworkOperation(function[,clock])
 
596
 
 
597
    If your function takes an argument, the clock will be passed
 
598
    as that argument.
 
599
    """
 
600
    def __init__(self, function, clock=None, when='end'):
 
601
        self.clock = guess_clock(clock)
 
602
        self.when = when
 
603
        self.function = function
 
604
 
 
605
    def __call__(self):
 
606
        if self.function.func_code.co_argcount == 1:
 
607
            self.function(self.clock)
 
608
        else:
 
609
            self.function()
 
610
 
 
611
 
 
612
def network_operation(*args, **kwds):
 
613
    """Decorator to make a function into a :class:`NetworkOperation`
 
614
    
 
615
    A :class:`NetworkOperation` is a callable class which is called every
 
616
    time step by the :class:`Network` ``run`` method. Sometimes it is useful
 
617
    to just define a function which is to be run every update step. This
 
618
    decorator can be used to turn a function into a :class:`NetworkOperation`
 
619
    to be added to a :class:`Network` object.
 
620
    
 
621
    **Example usages**
 
622
    
 
623
    Operation doesn't need a clock::
 
624
    
 
625
        @network_operation
 
626
        def f():
 
627
            ...
 
628
        
 
629
    Automagically detect clock::
 
630
    
 
631
        @network_operation
 
632
        def f(clock):
 
633
            ...
 
634
    
 
635
    Specify a clock::
 
636
    
 
637
        @network_operation(specifiedclock)
 
638
        def f(clock):
 
639
            ...
 
640
        
 
641
    Specify when the network operation is run (default is ``'end'``)::
 
642
    
 
643
        @network_operation(when='start')
 
644
        def f():
 
645
            ...
 
646
    
 
647
    Then add to a network as follows::
 
648
    
 
649
        net = Network(f,...)
 
650
    """
 
651
    # Notes on this decorator:
 
652
    # Normally, a decorator comes in two types, with or without arguments. If
 
653
    # it has no arguments, e.g.
 
654
    #   @decorator
 
655
    #   def f():
 
656
    #      ...
 
657
    # then the decorator function is defined with an argument, and that
 
658
    # argument is the function f. In this case, the decorator function
 
659
    # returns a new function in place of f.
 
660
    #
 
661
    # However, you can also define:
 
662
    #   @decorator(arg)
 
663
    #   def f():
 
664
    #      ...
 
665
    # in which case the argument to the decorator function is arg, and the
 
666
    # decorator function returns a 'function factory', that is a callable
 
667
    # object that takes a function as argument and returns a new function.
 
668
    #
 
669
    # It might be clearer just to note that the first form above is equivalent
 
670
    # to:
 
671
    #   f = decorator(f)
 
672
    # and the second to:
 
673
    #   f = decorator(arg)(f)
 
674
    #
 
675
    # In this case, we're allowing the decorator to be called either with or
 
676
    # without an argument, so we have to look at the arguments and determine
 
677
    # if it's a function argument (in which case we do the first case above),
 
678
    # or if the arguments are arguments to the decorator, in which case we
 
679
    # do the second case above.
 
680
    #
 
681
    # Here, the 'function factory' is the locally defined class
 
682
    # do_network_operation, which is a callable object that takes a function
 
683
    # as argument and returns a NetworkOperation object.
 
684
    class do_network_operation(object):
 
685
        def __init__(self, clock=None, when='end'):
 
686
            self.clock = clock
 
687
            self.when = when
 
688
        def __call__(self, f, level=1):
 
689
            new_network_operation = NetworkOperation(f, self.clock, self.when)
 
690
            # Depending on whether we were called as @network_operation or
 
691
            # @network_operation(...) we need different levels, the level is
 
692
            # 2 in the first case and 1 in the second case (because in the
 
693
            # first case we go originalcaller->network_operation->do_network_operation
 
694
            # and in the second case we go originalcaller->do_network_operation
 
695
            # at the time when this method is called).
 
696
            new_network_operation.set_instance_id(level=level)
 
697
            new_network_operation.__name__ = f.__name__
 
698
            new_network_operation.__doc__ = f.__doc__
 
699
            new_network_operation.__dict__.update(f.__dict__)
 
700
            return new_network_operation
 
701
    if len(args) == 1 and callable(args[0]):
 
702
        # We're in case (1), the user has written:
 
703
        # @network_operation
 
704
        # def f():
 
705
        #    ...
 
706
        # and the single argument to the decorator is the function f
 
707
        return do_network_operation()(args[0], level=2)
 
708
    else:
 
709
        # We're in case (2), the user has written:
 
710
        # @network_operation(...)
 
711
        # def f():
 
712
        #    ...
 
713
        # and the arguments might be clocks or strings, and may have been
 
714
        # called with or without names, so we check both the variable length
 
715
        # argument list *args, and the keyword dictionary **kwds, falling
 
716
        # back on the default values if nothing is given.
 
717
        clk = None
 
718
        when = 'end'
 
719
        for arg in args:
 
720
            if isinstance(arg, Clock):
 
721
                clk = arg
 
722
            elif isinstance(arg, str):
 
723
                when = arg
 
724
        for key, val in kwds.iteritems():
 
725
            if key == 'clock': clk = val
 
726
            if key == 'when': when = val
 
727
        return do_network_operation(clock=clk, when=when)
 
728
    #raise TypeError, "Decorator must be used as @network_operation or @network_operation(clock)"
 
729
 
 
730
 
 
731
class MagicNetwork(Network):
 
732
    '''
 
733
    Creates a :class:`Network` object from any suitable objects
 
734
    
 
735
    **Initialised as:** ::
 
736
    
 
737
        MagicNetwork()
 
738
    
 
739
    The object returned can then be used just as a regular
 
740
    :class:`Network` object. It works by finding any object in
 
741
    the ''execution frame'' (i.e. in the same function, script
 
742
    or section of module code where the :class:`MagicNetwork` was
 
743
    created) derived from :class:`NeuronGroup`, :class:`Connection` or
 
744
    :class:`NetworkOperation`.
 
745
    
 
746
    **Sample usage:** ::
 
747
    
 
748
        G = NeuronGroup(...)
 
749
        C = Connection(...)
 
750
        @network_operation
 
751
        def f():
 
752
            ...
 
753
        net = MagicNetwork()
 
754
    
 
755
    Each of the objects ``G``, ``C`` and ``f`` are added to ``net``.
 
756
    
 
757
    **Advanced usage:** ::
 
758
    
 
759
        MagicNetwork([verbose=False[,level=1]])
 
760
    
 
761
    with arguments:
 
762
    
 
763
    ``verbose``
 
764
        Set to ``True`` to print out a list of objects that were
 
765
        added to the network, for debugging purposes.
 
766
    ``level``
 
767
        Where to find objects. ``level=1`` means look for objects
 
768
        where the :class:`MagicNetwork` object was created. The ``level``
 
769
        argument says how many steps back in the stack to look.
 
770
    '''
 
771
    def __init__(self, verbose=False, level=1):
 
772
        '''
 
773
        Set verbose=False to turn off comments.
 
774
        The level variable contains the location of the namespace.
 
775
        '''
 
776
        (groups, groupnames) = magic.find_instances(NeuronGroup)
 
777
        groups = [g for g in groups if g._owner is g]
 
778
        groupnames = [gn for g, gn in zip(groups, groupnames) if g._owner is g]
 
779
        (connections, connectionnames) = magic.find_instances(Connection)
 
780
        (operations, operationnames) = magic.find_instances(NetworkOperation)
 
781
        if verbose:
 
782
            print "[MagicNetwork] Groups:", groupnames
 
783
            print "[MagicNetwork] Connections:", connectionnames
 
784
            print "[MagicNetwork] Operations:", operationnames
 
785
        # Use set() to discard duplicates
 
786
        Network.__init__(self, list(set(groups)), list(set(connections)), list(set(operations)))
 
787
 
 
788
 
 
789
def run(duration, threads=1, report=None, report_period=10 * second):
 
790
    '''
 
791
    Run a network created from any suitable objects that can be found
 
792
    
 
793
    Arguments:
 
794
    
 
795
    ``duration``
 
796
        the length of time to run the network for.
 
797
    ``report``
 
798
        How to report progress, the default ``None`` doesn't report the
 
799
        progress. Some standard values for ``report``:
 
800
        
 
801
        ``text``, ``stdout``
 
802
            Prints progress to the standard output.
 
803
        ``stderr``
 
804
            Prints progress to the standard error output stderr.
 
805
        ``graphical``, ``tkinter``
 
806
            Uses the Tkinter module to show a graphical progress bar,
 
807
            this may interfere with any other GUI code you have.
 
808
            
 
809
        Alternatively, you can provide your own callback function by
 
810
        setting ``report`` to be a function ``report(elapsed, complete)``
 
811
        of two variables ``elapsed``, the amount of time elapsed in
 
812
        seconds, and ``complete`` the proportion of the run duration
 
813
        simulated (between 0 and 1). The ``report`` function is
 
814
        guaranteed to be called at the end of the run with
 
815
        ``complete=1.0`` so this can be used as a condition for
 
816
        reporting that the computation is finished.
 
817
    ``report_period``
 
818
        How often the progress is reported (by default, every 10s).
 
819
    
 
820
    Works by constructing a :class:`MagicNetwork` object from all the suitable
 
821
    objects that could be found (:class:`NeuronGroup`, :class:`Connection`, etc.) and
 
822
    then running that network. Not suitable for repeated runs or situations
 
823
    in which you need precise control.
 
824
    '''
 
825
    MagicNetwork(verbose=False, level=2).run(duration, threads=threads,
 
826
                                            report=report, report_period=report_period)
 
827
 
 
828
 
 
829
def reinit():
 
830
    '''
 
831
    Reinitialises any suitable objects that can be found
 
832
    
 
833
    **Usage:** ::
 
834
    
 
835
        reinit()
 
836
    
 
837
    Works by constructing a :class:`MagicNetwork` object from all the suitable
 
838
    objects that could be found (:class:`NeuronGroup`, :class:`Connection`, etc.) and
 
839
    then calling ``reinit()`` for each of them. Not suitable for repeated
 
840
    runs or situations in which you need precise control.
 
841
    '''
 
842
    MagicNetwork(verbose=False, level=2).reinit()
 
843
 
 
844
 
 
845
def stop():
 
846
    '''
 
847
    Globally stops any running network, this is reset the next time a network is run
 
848
    '''
 
849
    global globally_stopped
 
850
    globally_stopped = True
 
851
 
 
852
def clear(erase=True, all=False):
 
853
    '''
 
854
    Clears all Brian objects.
 
855
    
 
856
    Specifically, it stops all existing Brian objects from being collected by
 
857
    :class:`MagicNetwork` (objects created after clearing will still be collected).
 
858
    If ``erase`` is ``True`` then it will also delete all data from these objects.
 
859
    This is useful in, for example, ``ipython`` which stores persistent references
 
860
    to objects in any given session, stopping the data and memory from being freed
 
861
    up.  If ``all=True`` then all Brian objects will be cleared. See also
 
862
    :func:`forget`.
 
863
    '''
 
864
    if all is False:
 
865
        net = MagicNetwork(level=2)
 
866
        objs = net.groups + net.connections + net.operations
 
867
    else:
 
868
        groups, _ = magic.find_instances(NeuronGroup, all=True)
 
869
        connections, _ = magic.find_instances(Connection, all=True)
 
870
        operations, _ = magic.find_instances(NetworkOperation, all=True)
 
871
        objs = groups+connections+operations
 
872
    for o in objs:
 
873
        o.set_instance_id(-1)
 
874
        if erase:
 
875
            for k, v in o.__dict__.iteritems():
 
876
                object.__setattr__(o, k, None)
 
877
    gc.collect()
 
878
 
 
879
def forget(*objs):
 
880
    '''
 
881
    Forgets the list of objects passed
 
882
    
 
883
    Forgetting means that :class:`MagicNetwork` will not pick up these objects,
 
884
    but all data is retained. You can pass objects or lists of objects. Forgotten
 
885
    objects can be recalled with :func:`recall`. See also :func:`clear`.
 
886
    '''
 
887
    for obj in objs:
 
888
        if isinstance(obj, (NeuronGroup, Connection, NetworkOperation)):
 
889
            obj._forgotten_instance_id = obj.get_instance_id()
 
890
            obj.set_instance_id(-1)
 
891
        elif isSequenceType(obj):
 
892
            for o in obj:
 
893
                forget(o)
 
894
        else:
 
895
            raise TypeError('Only the following types of objects can be forgotten: NeuronGroup, Connection or NetworkOperation')
 
896
 
 
897
def recall(*objs):
 
898
    '''
 
899
    Recalls previously forgotten objects
 
900
    
 
901
    See :func:`forget` and :func:`clear`.
 
902
    '''
 
903
    for obj in objs:
 
904
        if isinstance(obj, (NeuronGroup, Connection, NetworkOperation)):
 
905
            if hasattr(obj, '_forgotten_instance_id'):
 
906
                obj.set_instance_id(obj._forgotten_instance_id)
 
907
        elif isSequenceType(obj):
 
908
            for o in obj:
 
909
                recall(o)
 
910
        else:
 
911
            raise TypeError('Only the following types of objects can be recalled: NeuronGroup, Connection or NetworkOperation')