Events
An event is a set of procedures that occurs when a condition is satisfied (triggered). Tissue Forge provides a robust event system with an ever-increasing library of built-in events, as well as support for defining fully customized events. In Tissue Forge, the procedures that correspond to an event are specified in a user-specified, custom function. Each type of built-in event corresponds to a particular condition by which Tissue Forge will evaluate the custom function, as well as to a particular set of simulation information that Tissue Forge will provide to the custom function.
The custom function that performs the set of procedures of an event is called
the invoke method. Aside from the condition that corresponds to a particular built-in
event, the condition of each event can be further customized by also specifying a
predicate method, which is another custom function that, when evaluated, tells
Tissue Forge whether the event is triggered. Both invoke and predicate methods take as
argument an instance of a specialized class of a base class event.Event
(Event
from the event
namespace in C++). In C++, pointers to invoke and
predicate methods can be created using the template EventMethodT
, where the template
parameter is the class of the corresponding event. Invoke methods return
1
if an error occurred during evaluation, and otherwise 0
. Predicate methods
return 1
if the event should occur, 0
if the event should not occur, and a
negative value if an error occurred.
Working with Events
In the most basic (and also most robust) case, Tissue Forge provides a basic
event.Event
class that, when created, is evaluated at every simulation step.
A event.Event
instance has no built-in predicate method, and its invoke method is
evaluated at every simulation step unless a custom predicate method is provided.
As such, the event.Event
class is the standard class for implementing custom
events for a particular model and simulation. An event.Event
instance
can be created with the top-level method event.on_event
(event::onEvent
in C++).
import tissue_forge as tf
...
# Invoke method: destroy the first listed particle in the universe
def destroy_first_invoke(event):
particle = tf.Universe.particles[0]
particle.destroy()
return 0
tf.event.on_event(invoke_method=destroy_first_invoke)
In this example, a particle is destroyed at every simulation step, which could be problematic in the case where no particles exist in the universe. Assigning a predicate method to the event could solve such a problem that describes appropriate conditions for the event to occur,
# Predicate method: first particle is destroyed if there are more than ten particles
def destroy_first_predicate(event):
predicate = len(tf.Universe.particles) > 10
return int(predicate)
tf.event.on_event(invoke_method=destroy_first_invoke,
predicate_method=destroy_first_predicate)
Events that are called repeatedly can also be designated for removal from the event
system using the event.Event
method remove
in
an invoke method.
class SplittingType(tf.ParticleTypeSpec):
pass
splitting_type = SplittingType.get()
# Split once each step until 100 particles are created
def split_to_onehundred(event):
particles = splitting_type.items()
num_particles = len(particles)
if num_particles == 0:
splitting_type()
elif num_particles >= 100:
event.remove()
else:
particles[0].split()
return 0
tf.event.on_event(invoke_method=split_to_onehundred)
Note
In Python, unhandled exceptions that occur in invoke and predicate methods only stop execution of the method, and might not produce any notification of an error. Basic exception handling is strongly encouraged to detect and report when errors occur in custom functions.
Timed Events
The built-in event event.TimeEvent
(event::TimeEvent
in C++)
repeatedly occurs with a prescribed period. By default, the period of evaluation is
approximately implemented as the first simulation time at which at an amount
of time at least as great as the period has elapsed since the last evaluation
of the event. event.TimeEvent
instances can be created with the top-level
method event.on_time
(event::onTimeEvent
in C++).
def split_regular(event):
splitting_type()
return 0
tf.event.on_time(invoke_method=split_regular, period=10.0)
The period of evaluation can also be implemented stochastically using the
optional keyword argument distribution
, which names a built-in distribution
by which Tissue Forge will generate the next time of evaluation from the event
period. Currently, Tissue Forge supports the Poisson distribution, which has
the name “exponential”.
def split_random(event):
splitting_type()
return 0
tf.event.on_time(invoke_method=split_random, period=10.0, distribution="exponential")
event.TimeEvent
instances can also be generated for only a particular period
in simulation. The optional keyword argument start_time
(default 0.0)
defines the first time in simulation when the event can occur, and the optional
keyword argument end_time
(default forever) defines the last time in
simulation when the event can occur.
def destroy_for_a_while(event):
particles = splitting_type.items()
if len(particles) > 0:
particles[0].destroy()
return 0
tf.event.on_time(invoke_method=destroy_for_a_while, period=10.0,
start_time=20.0, end_time=30.0)
Events with Particles
Tissue Forge provides built-in events that operate on individual particles on
the basis of particle type. In addition to working with a custom invoke
method and optional predicate method, particle events select a particle
from a prescribed particle type. These event instances have the attributes
targetType
and targetParticle
that are set to the particle
type and particle that correspond to an event.
The event.ParticleEvent
(event::ParticleEvent
) is a particle event
that functions much the same as event.Event
. A event.ParticleEvent
instance has an invoke method and optional predicate method, and is
evaluated at every simulation step. However, a event.ParticleEvent
instance also has an associated particle type and, on evaluation, an
associated particle. event.ParticleEvent
instances can be created with
the top-level method on_particle
(event::onParticleEvent
in C++).
def split_selected(event):
selected_particle = event.targetParticle
selected_particle.split()
return 0
tf.event.on_particle(splitting_type, invoke_method=split_selected)
By default, a particle is randomly selected during the evaluation of a
particle event according to a uniform distribution. The largest particle
(i.e., the cluster with the most constituent particles) can also be selected
using the optional keyword argument selector
and passing "largest"
.
def invoke_destroy_largest(event):
event.targetParticle.destroy()
return 0
tf.event.on_particle(splitting_type, invoke_method=invoke_destroy_largest,
selector="largest")
The particle event event.ParticleTimeEvent
(event::ParticleTimeEvent
in C++)
functions is a combination of event.TimeEvent
and
event.ParticleEvent
, and can be created with the top-level method
event.on_particletime
(event::onParticleTimeEvent
in C++) with all
of the combined corresponding arguments.
def split_selected_later(event):
event.targetParticle.split()
return 0
tf.event.on_particletime(splitting_type, period=10.0,
invoke_method=split_selected_later, start_time=20.0)
Input-Driven Events
Tissue Forge provides an event event.KeyEvent
(event::KeyEvent
in C++) that
occurs each time a key on the keyboard is pressed. event.KeyEvent
instances
do not support a custom predicate method. The name of the key that triggered
the event is available as the event.KeyEvent
string attribute
key_name
, as are key modifier flags
(Alt
: key_alt
,
Ctrl
: key_ctrl
,
Shift
: key_shift
).
One event.KeyEvent
instance in Python can be
created with the top-level method event.on_keypress
. In C++, an arbitrary number of
invoke methods can be assigned as keyboard callbacks using the static method
event::KeyEvent::addHandler
.
# key "d" destroys a particle; key "c" creates a particle
def do_key_actions(event):
if event.key_name == "d":
particles = splitting_type.items()
if len(particles) > 0:
particles[0].destroy()
elif event.key_name == "c":
splitting_type()
return 0
tf.on_keypress(do_key_actions)