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)