Adapting visualization with Triggers

Triggers allow the user to specify a set of actions that are triggered by the result of a boolean expression. They provide flexibility to adapt what analysis and visualization actions are taken in situ. Triggers leverage Ascent’s Query and Expression infrastructure. See Ascent’s Triggers docs for deeper details on Triggers.

Using triggers to render when events occur

C++

#include <iostream>
#include <sstream>

#include "ascent.hpp"
#include "conduit_blueprint.hpp"

#include "ascent_tutorial_cpp_utils.hpp"

using namespace ascent;
using namespace conduit;

int main(int argc, char **argv)
{
    // Use triggers to render when conditions occur
    Ascent a;

    // open ascent
    a.open();

    // setup actions
    Node actions;

    // declare a question to ask 
    Node &add_queries = actions.append();
    add_queries["action"] = "add_queries";

    // add our entropy query (q1)
    Node &queries = add_queries["queries"];
    queries["q1/params/expression"] = "entropy(histogram(field('gyre'), num_bins=128))";
    queries["q1/params/name"] = "entropy";

    // declare triggers 
    Node &add_triggers = actions.append();
    add_triggers["action"] = "add_triggers";
    Node &triggers = add_triggers["triggers"];

    // add a simple trigger (t1_ that fires at cycle 500
    triggers["t1/params/condition"] = "cycle() == 500";
    triggers["t1/params/actions_file"] = "cycle_trigger_actions.yaml";

    // add trigger (t2) that fires when the change in entroy exceeds 0.5

    // the history function allows you to access query results of previous
    // cycles. relative_index indicates how far back in history to look.

    // Looking at the plot of gyre entropy in the previous notebook, we see a jump
    // in entropy at cycle 200, so we expect the trigger to fire at cycle 200
    triggers["t2/params/condition"] = "entropy - history(entropy, relative_index = 1) > 0.5";
    triggers["t2/params/actions_file"] = "entropy_trigger_actions.yaml";

    // print our full actions tree
    std::cout << actions.to_yaml() << std::endl;

    // gyre time varying params
    int nsteps = 10;
    float time_value = 0.0;
    float delta_time = 0.5;
    
    Node mesh;

    for( int step =0; step < nsteps; step++)
    {
        // call helper that generates a gyre time varying example mesh.
        // gyre ref :https://shaddenlab.berkeley.edu/uploads/LCS-tutorial/examples.html
        tutorial_gyre_example(time_value, mesh);

        // update the example cycle
        int cycle = 100 + step * 100;
        mesh["state/cycle"] = cycle;
        std::cout << "time: " << time_value << " cycle: " << cycle << std::endl;

        // publish mesh to ascent
        a.publish(mesh);

        // execute the actions
        a.execute(actions);

        // update time
        time_value = time_value + delta_time;
    }
    // retrieve the info node that contains the query results
    Node info;
    a.info(info);

    // close ascent
    a.close();

    // this will render:
    //   cycle_trigger_out_500.png
    //   entropy_trigger_out_200.png
    //
    //
    // We can also examine when the triggers executed by looking at the expressions
    // results in the output info
    //

    std::cout << info["expressions"].to_yaml() << std::endl;
}

Python


import conduit
import conduit.blueprint
import ascent
import numpy as np

from ascent_tutorial_py_utils import tutorial_gyre_example

# Use triggers to render when conditions occur
a = ascent.Ascent()
a.open()

# setup actions
actions = conduit.Node()

# declare a question to ask 
add_queries = actions.append()
add_queries["action"] = "add_queries"

# add our entropy query (q1)
queries = add_queries["queries"] 
queries["q1/params/expression"] = "entropy(histogram(field('gyre'), num_bins=128))"
queries["q1/params/name"] = "entropy"

# declare triggers 
add_triggers = actions.append()
add_triggers["action"] = "add_triggers"
triggers = add_triggers["triggers"] 

# add a simple trigger (t1_ that fires at cycle 500
triggers["t1/params/condition"] = "cycle() == 500"
triggers["t1/params/actions_file"] = "cycle_trigger_actions.yaml"

# add trigger (t2) that fires when the change in entroy exceeds 0.5

# the history function allows you to access query results of previous
# cycles. relative_index indicates how far back in history to look.

# Looking at the plot of gyre entropy in the previous notebook, we see a jump
# in entropy at cycle 200, so we expect the trigger to fire at cycle 200
triggers["t2/params/condition"] = "entropy - history(entropy, relative_index = 1) > 0.5"
triggers["t2/params/actions_file"] = "entropy_trigger_actions.yaml"

# view our full actions tree
print(actions.to_yaml())

# gyre time varying params
nsteps = 10
time = 0.0
delta_time = 0.5

for step in range(nsteps):
    # call helper that generates a double gyre time varying example mesh.
    # gyre ref :https://shaddenlab.berkeley.edu/uploads/LCS-tutorial/examples.html
    mesh = tutorial_gyre_example(time)
    
    # update the example cycle
    cycle = 100 + step * 100
    mesh["state/cycle"] = cycle
    print("time: {} cycle: {}".format(time,cycle))

    # publish mesh to ascent
    a.publish(mesh)

    # execute the actions
    a.execute(actions)
    
    # update time
    time = time + delta_time

# retrieve the info node that contains the trigger and query results
info = conduit.Node()
a.info(info)

# close ascent
a.close()

# this will render:
#  cycle_trigger_out_500.png
#  entropy_trigger_out_200.png

#
# We can also examine when the triggers executed by looking at the expressions
# results in the output info
#
print(info["expressions"].to_yaml())

Output


- 
  action: "add_queries"
  queries: 
    q1: 
      params: 
        expression: "entropy(histogram(field('gyre'), num_bins=128))"
        name: "entropy"
- 
  action: "add_triggers"
  triggers: 
    t1: 
      params: 
        condition: "cycle() == 500"
        actions_file: "cycle_trigger_actions.yaml"
    t2: 
      params: 
        condition: "entropy - history(entropy, relative_index = 1) > 0.5"
        actions_file: "entropy_trigger_actions.yaml"

time: 0 cycle: 100
time: 0.5 cycle: 200
time: 1 cycle: 300
time: 1.5 cycle: 400
time: 2 cycle: 500
time: 2.5 cycle: 600
time: 3 cycle: 700
time: 3.5 cycle: 800
time: 4 cycle: 900
time: 4.5 cycle: 1000

entropy: 
  100: 
    value: 3.81580590726479
    type: "double"
  200: 
    value: 4.43027379899862
    type: "double"
  300: 
    value: 4.42357515605932
    type: "double"
  400: 
    value: 4.4133821818731
    type: "double"
  500: 
    value: 4.40290017527564
    type: "double"
  600: 
    value: 4.3643209637501
    type: "double"
  700: 
    value: 4.40290017527564
    type: "double"
  800: 
    value: 4.4133821818731
    type: "double"
  900: 
    value: 4.42357515605932
    type: "double"
  1000: 
    value: 4.43027379899862
    type: "double"
cycle() == 500: 
  100: 
    value: 0
    type: "bool"
  200: 
    value: 0
    type: "bool"
  300: 
    value: 0
    type: "bool"
  400: 
    value: 0
    type: "bool"
  500: 
    value: 1
    type: "bool"
  600: 
    value: 0
    type: "bool"
  700: 
    value: 0
    type: "bool"
  800: 
    value: 0
    type: "bool"
  900: 
    value: 0
    type: "bool"
  1000: 
    value: 0
    type: "bool"
entropy - history(entropy, relative_index = 1) > 0.5: 
  100: 
    value: 0
    type: "bool"
  200: 
    value: 1
    type: "bool"
  300: 
    value: 0
    type: "bool"
  400: 
    value: 0
    type: "bool"
  500: 
    value: 0
    type: "bool"
  600: 
    value: 0
    type: "bool"
  700: 
    value: 0
    type: "bool"
  800: 
    value: 0
    type: "bool"
  900: 
    value: 0
    type: "bool"
  1000: 
    value: 0
    type: "bool"