PulseCoupledOscillator
: The canonical synchronisation process
- class epydemic.PulseCoupledOscillator
Bases:
Process
A pulse-coupled oscillator synchronisation process. This follows closely on the definition provided by Mirollo and Strogatz [MS90], a model inspired by synchronising biological systems such as fireflies and human heart cells.
The basic model consists of a collection of \(N\) nodes, each equipped with a state and an oscillator. As the phase of each oscillator advances it advances the corresponding state variable. When the state reaches 1 the node is triggered and fires – for example by emitting a pulse of light – and resets its state and phase to 0. On observing such a pulse, each other node advances its own state by some amount \(\epsilon\). If this takes the node’s state above 1, then it too resets its phase and state to 0, and is henceforth synchronised with the node that triggered it; otherwise the node’s phase is advanced to match its new state.
Note that, in this model, a node does not flash when it becomes synchronised, but is will flash after the next period (unless it again synchronises to another node before it fires). Note also that all synchronised nodes will fire at the same time, so there will be a potentially large number of flashes happening at the same time. (Both these properties can be relaxed.)
In the original formulation [MS90] the oscillators sit in a complete network, so that every node observes flashes from every other node. This has the interesting property that, once synchronised, two nodes will stay synchronised, so the number of phases and the size of the largest set of synchronised are both monotonic (decreasing and increasing respectively). This isn’t necessarily true for other networks.
Parameters
-
PulseCoupledOscillator.PERIOD:
Final
[str
] = 'epydemic.pulsecoupled.period' Parameter name for the oscillator period.
-
PulseCoupledOscillator.B:
Final
[str
] = 'epydemic.pulsecoupled.dissipation' Paramater name for the dissipation constant.
-
PulseCoupledOscillator.COUPLING:
Final
[str
] = 'epydemic.pulsecoupled.coupling' Parameter name for firing coupling strength.
Note that, unlike many processes, these parameters all have defaults
set in PulseCoupledOscillator.build()
.
Results
-
PulseCoupledOscillator.PHASES:
Final
[str
] = 'epydemic.pulsecoupled.phases' The final phases of all the nodes.
-
PulseCoupledOscillator.FIRING_TIMES:
Final
[str
] = 'epydemic.pulsecouplepd.firingTimes' A list of firing times.
-
PulseCoupledOscillator.FIRING_NODES:
Final
[str
] = 'epydemic.pulsecoupled.firingNodes' A list of nodes firing at these times.
State variables
The only state variable holds an internal identifier for the posted event that will fire the node. It’s highly unlikely this will be useful outside the mechanics of the simulation.
-
PulseCoupledOscillator.NODE_EVENT_ID:
str
= None Identifier of the next firing event.
Core parts of the oscillator process
The model is initialised by giving a phase to every oscillator.
- PulseCoupledOscillator.initialisePhases()
Initialise the phases of the oscillators at all the nodes to a random value on the range \([0.0, 1.0]\).
The behaviour of firing, obsevration of flashes, and synchronisation of one oscillator to another can all be overridden or extended.
- PulseCoupledOscillator.fire(t, n)
Handle the firing of an oscillator, when its phase hits 1. This marks all neighbours for update and resets the phase of this node to 0.
This method can be overridden to provide extra behaviours on firing. Be sure to call the base method first.
- Parameters:
t (
float
) – the simulation timen (
Any
) – the node that is firing
- PulseCoupledOscillator.cascade(t, n, m)
Cascade the firing of one node into updating the phases of its neighbours, which may then synchronise them.
- Parameters:
t (
float
) – the simulation timen (
Any
) – the node that firedm (
Any
) – the node being updated as a result of n firing
- PulseCoupledOscillator.synchronised(t, n, m)
What to do when a node becomes synchronised with another. The default sets its phase to 0, meaning it will fire at the end of its next cycle: it won’t fire now.
- Parameters:
t (
float
) – the timen (
Any
) – the node that firedm (
Any
) – the node that is now newly-synchronised with n
Setup and initialisation
- PulseCoupledOscillator.build(params)
Build the oscillator model.
Each parameter gets a default of 1 if not set explicitly.
- Parameters:
params (
Dict
[str
,Any
]) – the experimental parameters
- PulseCoupledOscillator.setUp(params)
Initialise the oscillator phases.
- Parameters:
params (
Dict
[str
,Any
]) – the experimental parameters
- PulseCoupledOscillator.results()
Add the final phases of all oscillators to the results.
- Return type:
Dict
[str
,Any
]- Returns:
the experimental results
Events
The process defines one event that occurs whenever a node fires.
- PulseCoupledOscillator.fired(t, n)
The event called when a node’s oscillator hits a phase of 1.0. This triggers the node, firing it and incrementing the phases of all its neighbours.
We track all the nodes that are triggered as a result of this event to ensure that they are only triggered at most once in any cascade.
- Parameters:
t (
float
) – the simulation timen (
Any
) – the node that is firing
-
PulseCoupledOscillator.FIRED:
Final
[str
] = 'epydemic.pulsecoupled.fired' The name of the firing event.
Managing state and phase
The state and phase parts of the system are managed by a pair of functions that convert between them.
- PulseCoupledOscillator.phaseToState(phi)
Convert a phase to the corresponding state (function \(f\) in [MS90]).
- Parameters:
phi (
float
) – the phase- Return type:
float
- Returns:
the state
- PulseCoupledOscillator.stateToPhase(u)
Convert a state to the corresponding phase (function \(g\) in [MS90]).
- Parameters:
u (
float
) – the state- Return type:
float
- Returns:
the phase
These are combined to advance the phase of an oscillator according to flashes it observes.
- PulseCoupledOscillator.bumpPhase(phi)
Advance the phase and state of a node after it has observed a firing (the “return map” function \(h\) in [MS90]). By default this increments the state by the value given in the
COUPLING
parameter.- Parameters:
phi (
float
) – the phase- Return type:
float
- Returns:
the updated phase
The state and phase of an oscillator are managed using a small set of methods that manage the next firing time of an oscillator (if left to its own devices, without being updated).
- PulseCoupledOscillator.getPhase(t, n, normalise=False)
Get the phase of a node. This is computed by working back from the next-scheduled firing time.
By default this method returns the “raw” phase, and so treats phases of 0.0 and 1.0 as being different. Calling with normalise = True will map any phase of 1.0 to 0.0. This makes it easier to find all the nodes with oscillators at the same phase: however, it’s sometimes important to differentiate the two values (such as when deciding if an oscillator should fire), so the default keeps the two phases distinct.
- Parameters:
t (
float
) – the timem – the node
normalise (
bool
) – (optional) if True, combine phases of 0.0 and 1.0 (defaults to False)
- Return type:
float
- Returns:
the current phase
- PulseCoupledOscillator.setPhase(t, n, phi)
Set the phase of the given node. This re-sets the next firing time for the node.
- Parameters:
t (
float
) – the timen (
Any
) – the nodephi (
float
) – the phase
- PulseCoupledOscillator.getState(t, n)
Get the state of the given node.
- Parameters:
t (
float
) – the timen (
Any
) – the node
- Return type:
float
- Returns:
the state of the node
- PulseCoupledOscillator.setFiringTime(n, et)
Set the next firing time of a node, replacing the currently-scheduled event.
- Parameters:
n (
Any
) – the nodeet (
float
) – the next scheduled firing time
- PulseCoupledOscillator.getFiringTime(n)
Get the next firing time of a node.
- Parameters:
n (
Any
) – the node- Returns:
the next scheduled firing time
It is important to manage the phase carefully to avoid numerical
instability caused by unrestricted use of floating-point operations.
Phases are kept in the range \([0.0, 1.0]\) and held to a fixed
numerical precision defined by
PulseCoupledOscillator.PHASE_PRECISION
. (See the
implementation note on event times for a
discussion of this.)
- PulseCoupledOscillator.normalisePhase(phi)
Ensure the phase is normalised and quantised. This ensures that the phase is in the range \([0.0, 1.0]\) and stored to a precision set by
PHASE_PRECISION
to avoid problems with floating-point rounding.- Parameters:
phi (
float
) – the phase- Return type:
float
- Returns:
the normalised phase
Tuning parameters
The precision with which phases are held can be changed if required – although the default is almost certainly adequate.
-
PulseCoupledOscillator.PHASE_PRECISION:
int
= 5 Number of places or precision for phases.