Dynamics
: Process dynamics over networks
- class epydemic.Dynamics(p, g=None)
Bases:
NetworkExperiment
An abstract simulation framework for running a process over a network.
This is the abstract base class for implementing different kinds of network process dynamics as computational experiments suitable for running under
epyc
. Sub-classes provide synchronous and stochastic (Gillespie) simulation dynamics.The dynamics runs a network process provided as a
Process
object. It is provided with a network generator that is called to generate a new experimental network instance for each run. The generator can be any iterator but will typically be an instance ofNetworkGenerator
.The dynamics also provides the interface for Event taps, allowing external code to tap-into the changes the experiment makes to the network. Sub-classes need to insert calls to this interface as appropriate, notably around the main body of the simulation and at each significant change.
- Parameters:
p (
Process
) – network process to rung (
Union
[Graph
,NetworkGenerator
,None
]) – (optional) prototype network or network generator
In use
A Dynamics
object is an instance of NetworkExperiment
which is turn an epyc experiment, and
hence can be run either stand-alone or as part of a larger planned
experimental protocol. Each simulation is parameterised by a dict
providing the parameters used to condition the simulation, typically
providing event probabilities for the various events that may happen.
Note
Simulations don’t use Dynamics
objects directly, but
instead use a sub-class. See Simulation dynamics for an
explanation of the differences between approaches.
In stand-alone mode, a simulation is run by calling the run()
method (inherited from epyc.Experiment), passing a dict of
parameters. The network dynamics then performs a single simulation
according to the following process:
The
Dynamics.setUp()
method usesNetworkExperiment.setUp()
to delete any old working network and build a new one. It then lets theProcess
configure the working copy: it callsProcess.reset()
to reset the process, sets its working network by callingProcess.setNetwork()
, then builds the process instance usingProcess.build()
and sets it up ready for simulation by callingProcess.setUp()
.The
Dynamics.do()
method is called to perform the simulation, returning a dict of experimental results. This method is overridden by sub-classes to define the style of simulation being performed.The
Dynamics.tearDown()
method is called to clean-up the simulation class, usingProcess.tearDown()
to tear-down the process
This decomposition is very flexible. At its simplest, a dynamics takes
a fixed prototype network as a parameter to its construction and copies it
for every run. More complex use cases supply an instance of
NetworkGenerator
that samples from a class of random networks defined
by the experimental parameters.
Note
In versions of epydemic prior to 1.13.1 the working network was discarded as part of tear-down, making it inaccessible once the experimental run had ended.
Starting with version 1.13.1, this behaviour was changed so that the working network is retained until the next experiment is performed, whereupon it is discarded and a new working network is created.
This change simplifies using epydemic at a small scale, since one can directly see the network that exists after an experimental run without having to explicitly save it. It makes no difference beyond this.
Note the division of labour. A Dynamics
object provides the scheduling
for events, which are themselves specified and defined in a Process
object. There is seldom any need to interact directly with a Dynamics
object
other than through its execution interface.
Attributes
-
Dynamics.TIME:
Final
[str
] = 'epydemic.monitor.time' Metadata element holding the logical simulation end-time.
-
Dynamics.EVENTS:
Final
[str
] = 'epydemic.monitor.events' Metadata element holding the number of events that happened.
Configuring the simulation
A Dynamics
object runs the process it describes over a
network. It also maintains the simulation time as the simulation progresses.
- Dynamics.currentSimulationTime()
Return the current simulation time.
- Return type:
float
- Returns:
the current time
- Dynamics.setCurrentSimulationTime(t)
Set the current simulation time. This should only be used by sub-classes when running the simulation: doing so in any other context risks damaging the simulation.
- Parameters:
t (
float
) – the new simualtion time
Running the experiment
A simulation takes the form of an epyc experiment which has set-up, execution, and tear-down phases.
- Dynamics.setUp(params)
Set up the experiment for a run. This performs the inherited actions, then builds the network process that the dynamics is to run.
- Params params:
the experimental parameters
- Dynamics.tearDown()
At the end of each experiment, throw away any posted by un-executed events.
- Dynamics.experimentalResults()
Report the process’ experimental results. This simply calls through to the
Process.results()
method of the process being simulated.- Return type:
Dict
[str
,Any
]- Returns:
the results of the process
Loci
Loci for stochastic events are craeted by Process
instances.
- Dynamics.addLocus(p, n, l=None)
Add a named locus associated with the given process.
- Dynamics.locus(n)
Retrieve a locus by name.
- Parameters:
n (
str
) – the locus name- Return type:
- Returns:
the locus
Stochastic event distributions
The stochastic events are defined and managed in the Process
class. The dynamics only cares about their distributions.
- Dynamics.perElementEventDistribution(t)
Return the distribution of of all processes’ per-element events at the given time.
Note that this method returns the probability of events, not their expected rates, for which use
perElementEventRateDistribution()
.- Parameters:
t (
float
) – the simulation time- Return type:
List
[Tuple
[Locus
,float
,Callable
[[float
,Union
[Any
,Tuple
[Any
,Any
]]],None
],str
]]- Returns:
a list of (locus, probability, event function) triples
- Dynamics.perElementEventRateDistribution(t)
Return the rates of per-element events for all processes at the given time.
Note that this is method returns event rates, not their probabilities as returned by
perElementEventDistribution()
. The rate is simply an event’s probability multiplied by the size of the locus on which it occurs, giving the expected number of events occurring from that locus in unit time.- Parameters:
t (
float
) – the simulation time- Return type:
List
[Tuple
[Locus
,float
,Callable
[[float
,Union
[Any
,Tuple
[Any
,Any
]]],None
],str
]]- Returns:
a list of (locus, rate, event function) triples
- Dynamics.fixedRateEventDistribution(t)
Return the distribution of fixed-rate events for all processes at the given time.
- Parameters:
t (
float
) – the simulation time- Return type:
List
[Tuple
[Locus
,float
,Callable
[[float
,Union
[Any
,Tuple
[Any
,Any
]]],None
],str
]]- Returns:
a list of (locus, probability, event function) triples
- Dynamics.eventRateDistribution(t)
Return the event distribution, a sequence of (l, r, f, n) tuples where l is the locus where the event occurs, r is the rate at which an event occurs, f is the event function called to make it happen, and n is the event name (which may be None).
Note the distinction between a rate and a probability: the former can be obtained from the latter simply by multiplying the event probability by the number of times it’s possible in the current network, which for per-element events is the population of nodes or edges in a given state.
It is perfectly fine for an event to have a zero rate. The process is assumed to have reached equilibrium if all events have zero rates.
- Parameters:
t (
float
) – current time- Return type:
List
[Tuple
[Locus
,float
,Callable
[[float
,Union
[Any
,Tuple
[Any
,Any
]]],None
],str
]]- Returns:
a list of (locus, rate, event function, event name) tuples
Posted events
A Dynamics
object also maintains a queue of posted event.
- Dynamics.postEvent(t, p, e, ef, name=None)
Post an event that calls the event function at time t. A unique id it returned that can be used to remove the event before it fires using
unpostEvent()
.The optional name is used in conjunction with event taps when calling
eventFired()
.- Parameters:
t (
float
) – the current timep (
Process
) – the process originating the evente (
Union
[Any
,Tuple
[Any
,Any
]]) – the element (node or edge) on which the event occursef (
Callable
[[float
,Union
[Any
,Tuple
[Any
,Any
]]],None
]) – the event functionname (
Optional
[str
]) – (optional) meaningful name of the event
- Return type:
int
- Returns:
the event id
- Dynamics.postRepeatingEvent(t, dt, p, e, ef, name=None)
Post an event that starts at time t and re-occurs at interval dt. Repeating events can’t be removed once posted.
The optional name is used in conjunction with event taps when calling
eventFired()
.- Parameters:
t (
float
) – the start timedt (
float
) – the intervalp (
Process
) – the process originating the evente (
Union
[Any
,Tuple
[Any
,Any
]]) – the element (node or edge) on which the event occursef (
Callable
[[float
,Union
[Any
,Tuple
[Any
,Any
]]],None
]) – the element functionname (
Optional
[str
]) – (optional) meaningful name of the event
This queue is then accessed to extract the events that need to be fired up to a given simulation time.
- Dynamics.nextPendingEvent()
Return the next posted event, or None if there are no events.
- Return type:
Optional
[Tuple
[float
,int
,Process
,Optional
[Callable
[[],None
]],Union
[Any
,Tuple
[Any
,Any
]],str
]]- Returns:
the next posted event or None
- Dynamics.nextPendingEventTime()
Return the simulation time for the next pending posted event, without affecting the event queue.
- Return type:
Optional
[float
]- Returns:
the simulation time or None
- Dynamics.nextPendingEventBefore(t)
Return the next pending event to occur at or before time t.
- Parameters:
t (
float
) – the current time- Return type:
Optional
[Tuple
[float
,int
,Process
,Optional
[Callable
[[],None
]],Union
[Any
,Tuple
[Any
,Any
]],str
]]- Returns:
a posted event or None
- Dynamics.pendingEventTime(id)
Return the time for which the given event is posted for. This will raise a KeyError if the event isn’t in the queue, thrtough having fired or being un-posted.
- Poram id:
the event
- Return type:
float
- Returns:
the event’s posted simulation time
- Dynamics.runPendingEvents(t)
Retrieve and fire any pending events at time t. This handles the case where firing an event posts another event that needs to be run before other already-posted events coming before time t: in other words, it ensures that the simulation order is respected.
- Parameters:
t (
float
) – the current time- Return type:
int
- Returns:
the number of events fired
Posted events can be un-posted at any time before they fire. (Repeating events can’t be un-posted once posted.)
- Dynamics.unpostEvent(id, fatal=True)
Un-post a posted event. This is only legal before the event has fired, and will normally raise a KeyError if called on one that’s not queued: set fatal to False to avoid this.
- Parameters:
id (
int
) – the event idfatal (
bool
) – whether to raise KeyError for missing events (defaults to True)
- Return type:
Optional
[float
]- Returns:
the simulation time at which the event would have fired, or None
Event taps
Whenever the network changes, there is an opportunity for the experiment to log it or take some other action. We refer to this as the event tap, as it captures the entire stream of events regardless of how they are defined. See Event taps for a discussion.
To use the event tap interface, you need to override these methods (they all have empty defaults) and ensure that they’re called from the right places.
- Dynamics.eventFired(t, p, name, e)
Respond to the occurrance of the given event. The method is passed the simulation time, originating process, event name, and the element affected – and isn’t passed the event function, which is used elsewhere.
This method is called in the past tense, after the event function has been run. This lets the effects of the event be observed.
The event name is simply the optional name that was given to the event when it was declared using
addEventPerElement()
oraddFixedRateEvent()
. It will be None if no name was provided.The default does nothing. It can be overridden by sub-classes to provide event-level logging or other functions.
- Parameters:
t (
float
) – the simulation timep (
Process
) – the process firing the eventname (
str
) – the event namee (
Union
[Any
,Tuple
[Any
,Any
]]) – the element
There are three other methods that are called within the core of the experiment to set up and manage the event tap.
- Dynamics.initialiseEventTaps()
Initialise the event tap sub-system, which allows external code access to the event stream of the simulation as it runs.
The default does nothing.
- Dynamics.simulationStarted(params)
Called when the simulation has been configured and set up, any processes built, and is ready to run.
The default does nothing.
- Parameters:
params (
Dict
[str
,Any
]) – the experimental parameters
- Dynamics.simulationEnded(res)
Called when the simulation has stopped, immediately before tear-down.
The default does nothing.
- Parameters:
res (
Union
[Dict
[str
,Any
],List
[Dict
[str
,Any
]]]) – the experimental results
This is simply an interface definition: all the default implementations are empty. To use the event taps you need to override these methods for every experiment. The methods should be called as follows:
NetworkExperiment.initialiseEventTaps()
: Early in the construction process, to allow any data structures to be created.NetworkExperiment.simulationStarted()
: After any set-up and immediately before the work of the experiment (simulation) starts, so that the overridden method gets to see the experiment right before execution.NetworkExperiment.eventFired()
: Immediately after each “event”, however defined, to that the overridden method gets to see the effect that the method had.NetworkExperiment.simulationEnded()
: After the last event and before any tear-down, so that the overridden method gets to see the final result of the simulation.
For example, in StochasticDynamics
,
NetworkExperiment.initialiseEventTaps()
is called from the
constructor of Dynamics
;
NetworkExperiment.simulationStarted()
is called before the first
stochastic event is drawn;
NetworkExperiment.eventFired()
is called immediately after each
event function has been called, for both stochastic and
posted events;
and NetworkExperiment.simulationEnded()
is called after the last
stochastic event has been fired.