Process: Base class for network processes

class epydemic.Process

Bases: object

A process that runs over a network. This is the abstract base class for all network processes. It provides the essential routines to build, set-up, run, and extract results from a process. Sub-classes provide the actual behaviour by defining simulation events and attaching them to the network in different ways.

A process definition is largely declarative, in the sense that it sets up event handlers and their probabilities. These are used by a process dynamics (a sub-class of Dynamics) to actually run a simulation of the process.

Processes can be composed into larger structures, for example as part of a ProcessSequence.

The process class includes an interface for interacting with the working network. The basic interface is extended and overridden in different sub-classes that interact with the network in different ways. The process also provides helper methods to save explicit use of the Dynamics when writing events.

Setup and initialisation

Five methods provide the core API for defining new processes, and are typically overridden by sub-classes.

Process.reset()

Reset the process ready to be built. This resets all the internal process state variables.

Process.build(params)

Build the process model. This should be overridden by sub-classes, and should create the various elements of the model.

Parameters:

params (Dict[str, Any]) – the model parameters

Process.setUp(params)

Set up the network under the given dynamics. The default does nothing: sub-classes should override it to initialise node states or establish other properties ready for the experiment.

Parameters:

params (Dict[str, Any]) – the simulation parameters

Process.tearDown()

Tear down any structures built for this run of the process. The default does nothing.

Process.dynamics()

Return the instance of Dynamics running this process.

Return type:

Dynamics

Returns:

the dynamics

Process.currentSimulationTime()

Return the current simulation time. Only makes sense when called from a running simulation.

Return type:

float

Returns:

the time

Process.results()

Create and return an empty dict to be filled with experimental results. Sub-classes should extend this method to add results to the dict.

Return type:

Dict[str, Any]

Returns:

an empty dict for experimental results

Process hierarchy

Processes can be nested, for example using a ProcessSequence. The component processes can be accessed using a common interface:

Process.processes()

Return a list of component processes. For a standard process this is just the process itself.

Return type:

List[Process]

Returns:

a list containing this process

Process.allProcesses()

Return a recursive list of component processes. For a standard process this is the same as calling \(processes\)..

Return type:

List[Process]

Returns:

a list containing this process

Getting ready to run

Several other methods provide information for the process.

Process.setNetwork(g)

Set the network the process is running over.

Parameters:

g (Graph) – the network

Process.network()

Return the network the process is running over.

Return type:

Graph

Returns:

the network

Process.setDynamics(d)

Set the instance of Dynamics that runs the process.

Parameters:

d (Dynamics) – the dynamics

Process.dynamics()

Return the instance of Dynamics running this process.

Return type:

Dynamics

Returns:

the dynamics

Process.setMaximumTime(t)

Set the maximum default simulation time. The default is given by DEFAULT_MAX_TIME. This is used by atEquilibrium() as the default way to determine equilibrium.

The maximum time may be slightly exceeded due to the ways in which events are drawn.

Setting the maximum time persists across runs of the process, and isn’t reset to its default by calling reset().

Parameters:

t (float) – the maximum simulation time

Process.maximumTime()

Return the maximum assumed simulation time.

Return type:

float

Returns:

the maximum simulation time

Accessing and evolving the network

A process will generally want to access the working network in the course of its execution, mainly in event functions. Accessing the network can be done directly, through network(): however, processes often need to track changes made to the network, and for this reason the class provides an interface for evolving the network, paralleling the methods available in networkx.

The interface may be overridden and extended by sub-classes. Three methods form the general core.

Process.addNode(n, **kwds)

Add a node to the working network. Any keyword arguments added as node attributes

Parameters:
  • n (Any) – the new node

  • kwds – (optional) node attributes

Process.removeNode(n)

Remove a node from the working network.

Parameters:

n (Any) – the node

Process.addEdge(n, m, **kwds)

Add an edge between nodes. Any keyword arguments are added as edge attributes. If the endpoint nodes do not already exist then an exception is raised.

Parameters:
  • n (Any) – the start node

  • m (Any) – the end node

  • kwds – (optional) edge attributes

Process.removeEdge(n, m)

Remove an edge from the working network.

Parameters:
  • n (Any) – the start node

  • m (Any) – the end node

Four other “bulk” methods are deinfed in terms of the basic methods, and so don’t typically need to be overridden specifically.

Process.addNodesFrom(ns, **kwds)

Add all the nodes in the given iterable to the working network. Any keyword arguments are added as node attributes. This works by calling addNode() for each element of the iterable.

Parameters:
  • ns (Iterable[Any]) – an iterable collection of nodes

  • kwds – (optional) node attributes

Process.removeNodesFrom(ns)

Remove all the nodes in the given iterable from the working network. This works by iteratively calling removeNode() for each element of the iterable collection.

Parameters:

ns (Iterable[Any]) – an iterable collection of nodes

Process.addEdgesFrom(es, **kwds)

Add all the edges in the given iterable to the working network. Any keyword arguments are added as node attributes. This works by calling addEdge() for each element of the iterable.

Parameters:
  • es (Iterable[Tuple[Any, Any]]) – an iterable collection of edges

  • kwds – (optional) node attributes

Process.removeEdgesFrom(es)

Remove all the edges in the given iterable collection from the working network. This works by iteratively calling removeEdge() for each edge in the iterable.

Parameters:

es (Iterable[Tuple[Any, Any]]) – an iterable collection of edges

Loci

Loci are the “locations” at which things happen. The purpose of a Locus is to keep track of something – a set of nodes, the entire network, and so forth – so that simulation can proceed efficiently.

Process.addLocus(n, l=None)

Add a named locus.

Parameters:
  • n (str) – the locus name

  • l (Optional[Locus]) – the locus (defaults to a simple set-based locus)

Return type:

Locus

Returns:

the locus

Process.loci()

Return the names of the loci that this process added.

Return type:

Dict[str, Locus]

Returns:

a dict from names to loci

Process.locus(n)

Return the named locus.

Parameters:

n (str) – the locus name

Return type:

Locus

Returns:

the locus

Events

Events are the code fragments that run as part of the simulation. The collection of events defined by a process form all the possible actions that the simulation will perform. Events can be given meaningful names, which don’t affect the execution of the simulation.

There are three broad classes of events. Per-element events occur with a probability on each element of a locis. This means that loci with more elements will generate a higher rate of events.

Process.addEventPerElement(l, pr, ef, name=None)

Add a probabilistic event at a locus, occurring with a particular (fixed) probability for each element of the locus, and calling the event function when it is selected.

The optional name is used in conjunction with event taps when calling eventFired().

Parameters:
  • l (Union[str, Locus]) – the locus or locus name

  • pr (float) – the event probability

  • ef (Callable[[float, Union[Any, Tuple[Any, Any]]], None]) – the event function

  • name (Optional[str]) – (optional) meaningful name of the event

Fixed-rate events, by contrast, occur with a probability that’s independent of the number of elements in a locus, as long as it’s not empty. This means that the rate at which such events fire is independent of the size of the locus.

Process.addFixedRateEvent(l, pr, ef, name=None)

Add a probabilistic event at a locus, occurring with a particular (fixed) probability, and calling the event function when it is selected. The locus may be a Locus object or a string, which is taken to be the name of a locus of this process. This is a helper method that calls Dynamics.addFixedRateEvent() on the dynamics running the process.

Unlike fixed rate events added by addEventPerElement(), a fixed probability event happens with the same probability regardless of how many elements are in the locus.

Parameters:
  • l (Union[str, Locus]) – the locus or locus name

  • pr (float) – the event probability

  • ef (Callable[[float, Union[Any, Tuple[Any, Any]]], None]) – the event function

  • name (Optional[str]) – (optional) meaningful name of the event

These two kinds of events are both stochastic, in the sense that they are generated according to an exponential probability distribution.

In contrast, posted events are set to occur at a particular simulation time. As the simulation proceeds it will execute posted events in the correct time sequence relative to the different stochastic events that are generated.

Process.postEvent(t, e, ef, name=None)

Post an event that calls the event function at time t. This is a helper method that calls Dynamics.postEvent() on the dynamics running the process.

Parameters:
  • t (float) – the time to fire the event

  • e (Any) – the element (node or edge) on which the event occurs

  • ef (Callable[[float, Union[Any, Tuple[Any, Any]]], None]) – the event function

  • name (Optional[str]) – (optional) meaningful name of the event

Return type:

int

Process.unpostEvent(id, fatal=True)

Un-post the given event. This is a helper method that calls Dynamics.postEvent() on the dynamics running the process.

A KeyError will normally be raised if the event is not queued, which typically means it’s been fired already (i.e., its posting time lies in the past relative to the current simulation time): set fatal to False to avoid this.

Parameters:
  • id (int) – the event id

  • fatal (bool) – whether to raise KeyError for missing events (defaults to True)

Return type:

Optional[float]

Returns:

the time for which the event was posted, or None

Process.pendingEventTime(id)

Return the time for which the given event is posted. This is a helper event trhat calls Dynamics.pendingEventTime(). A KeyError will be raised if the event is not queued, which typically means it’s been fired already (i.e., its posting time lies in the past relative to the current simulation time).

Parfam, id:

the event

Return type:

float

Returns:

the event’s posted simulation time

Process.postRepeatingEvent(t, dt, e, ef, name=None)

Post an event that starts at time t and re-occurs at interval dt. This is a help[er methoid that calls Dynamics.postRepeatingEvent() on the dynamics running the process.

Parameters:
  • t (float) – the start time

  • dt (float) – the interval

  • e (Any) – the element (node or edge) on which the event occurs

  • ef (Callable[[float, Union[Any, Tuple[Any, Any]]], None]) – the element function

  • name (Optional[str]) – (optional) meaningful name of the event

Event distributions

The stochastic events form probability distributions from which events can be drawn in the course of the simulation. The events are added using the methods above; the distributions are computed automatically.

Process.perElementEventDistribution(t)

Return the distribution of per-element events at the given time. By default the distribution is time-independent.

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

Process.perElementEventRateDistribution(t)

Return the rates of per-element events at the given time. By default the distribution is time-independent.

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

Process.fixedRateEventDistribution(t)

Return the distribution of fixed-rate events at the given time. By default the distribution is time-independent.

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

In some cases it may be necessary to create the distributions directly, in which case these methods can be overridden.

Identifiers for instances, runs, and state

Every process instance has an identifier that’s guaranteed to be unique within this simulation, and a run identifier that’s guaranteed to be unique to different runs of the same process instance. Taken together, these two identifiers uniquely identify a single run of a single process instance.

Process.instanceId()

Return the unique instance identifier of this process.

Return type:

int

Returns:

the instacne id

Process.runId()

Return the unique run identifier for the current run. This is updated whenever the process is reset by a call to reset().

Return type:

int

Returns:

the run id

There is also a method for defining “constants” to be used as attributes on nodes and edges for storing process state.

Process.stateVariable(stem)

Create a unique name for a state variable using the given stem. The default combines the stem with the process instance id.

Parameters:

stem (str) – the name stem

Return type:

str

Returns:

the state variable name

(See Defining more new processes for an example of how to define state variables.)

Containment

Processes can have a hierarchy, for example when composed into a ProcessSequence. There are a couple of methods used to access this hierarchy.

Process.setContainer(ps)

Register this process as being composed as part of another process.

Parameters:

ps (Process) – the containing process

Process.container()

Return the container process this process is part of. This will be None for “simple” processes.

Return type:

Process

Returns:

the container process or None