Hello Plasms!

Ok, so you made your first cell in Hello Ecto!. Now lets make a graph, what ecto is especially good at.

c++

Lets make two more cells

Download: srcs/Increment.cpp

#include <ecto/ecto.hpp>

using ecto::tendrils;
namespace tutorial
{
  struct Increment
  {
    static void
    declare_params(tendrils& params)
    {
      params.declare<int>("start", "The starting value.", 0);
    }

    static void
    declare_io(const tendrils& params, tendrils& /*in*/, tendrils& out)
    {
      out.declare<int>("output", "An increasing integer", params.get<int>("start"));
    }

    int
    process(const tendrils& in, const tendrils& out)
    {
      //the get function of the tendrils returns a mutable reference.
      out.get<int>("output") += 1;
      return ecto::OK;
    }
  };
}

ECTO_CELL(tutorial, tutorial::Increment, "Increment", "Outputs a monotonically increasing integer.");

Download: srcs/Add.cpp

#include <ecto/ecto.hpp>

using ecto::tendrils;
namespace tutorial
{
  struct Add
  {
    static void
    declare_io(const tendrils& /*params*/, tendrils& in, tendrils& out)
    {
      in.declare<int>("a", "1st operand.").required(true);
      in.declare<int>("b", "2nd operand.").required(true);
      out.declare<int>("output", "Result of a + b.");
    }

    int
    process(const tendrils& in, const tendrils& out)
    {
      out.get<int>("output") = in.get<int>("a") + in.get<int>("b");
      return ecto::OK;
    }
  };
}

ECTO_CELL(tutorial, tutorial::Add, "Add", "Adds two integers together.");

python

The python counter part to hello might look like:

Download: srcs/plasms.py

#!/usr/bin/env python
import ecto
from ecto_tutorial.tutorial import Increment, Add

# create three Cell objects
i1 = Increment('Inc 1', start=18)
i2 = Increment('Inc 2', start=20)
add = Add('Add')

# create an empty plasm: a graph
plasm = ecto.Plasm()
# create connections inside that graph: the output named 'output'
# of cell 'i1' is connected to the inut named 'a' of cell 'add'
# and reciprocally with cell 'i2'.
plasm.connect(i1['output'] >> add['a'],
              i2['output'] >> add['b'],
              )

# create a scheduler that will run that plasm
sched = ecto.Scheduler(plasm)
# execute the plasm
sched.execute(niter=2)

# display the output name 'output' in the outputs of cell 'add'
print add.outputs.output

Lets run it:

No module named PySide.QtCore
42

Parameters

Look at the allocation of our cells in the script.

# create three Cell objects
i1 = Increment('Inc 1', start=18)
i2 = Increment('Inc 2', start=20)
add = Add('Add')

The parameters that were declared in:

    static void
    declare_io(const tendrils& /*params*/, tendrils& in, tendrils& out)
    {
      in.declare<int>("a", "1st operand.").required(true);
      in.declare<int>("b", "2nd operand.").required(true);
      out.declare<int>("output", "Result of a + b.");
    }

Become the keyword arguments for the cell. The first non keyword argument is the cell’s instance name, which is relevant for pretty looking graphs.

Connections

The syntax for connecting ecto cells together in the plasm looks like this.

# create an empty plasm: a graph
plasm = ecto.Plasm()
# create connections inside that graph: the output named 'output'
# of cell 'i1' is connected to the inut named 'a' of cell 'add'
# and reciprocally with cell 'i2'.
plasm.connect(i1['output'] >> add['a'],
              i2['output'] >> add['b'],
              )

Each connection is made by using the >> operator, where the key value in the [] operator refers to an output tendril on the left hand side, or an input tendril on the right hand side. See Tendril Connections for more details on how this operator works.

Graphs

The graph for this will look like:

digraph G {
graph [rankdir=TB, ranksep=1]
edge [labelfontsize=8]
node [shape=plaintext]
0[label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">  <TR> <TD ROWSPAN="1" COLSPAN="1" BGCOLOR="khaki">Inc 1</TD> <TD PORT="p_start" BGCOLOR="lightblue">start</TD>
 </TR>  <TR>
<TD PORT="o_output" BGCOLOR="indianred1">output</TD>
</TR> </TABLE>>];
1[label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> <TR>
<TD PORT="i_a" BGCOLOR="springgreen">a</TD>
<TD PORT="i_b" BGCOLOR="springgreen">b</TD>
</TR> <TR> <TD ROWSPAN="1" COLSPAN="2" BGCOLOR="khaki">Add</TD>  </TR>  <TR>
<TD PORT="o_output" BGCOLOR="indianred1">output</TD>
</TR> </TABLE>>];
2[label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">  <TR> <TD ROWSPAN="1" COLSPAN="1" BGCOLOR="khaki">Inc 2</TD> <TD PORT="p_start" BGCOLOR="lightblue">start</TD>
 </TR>  <TR>
<TD PORT="o_output" BGCOLOR="indianred1">output</TD>
</TR> </TABLE>>];
0->1 [headport="i_a" tailport="o_output"];
2->1 [headport="i_b" tailport="o_output"];
}

Plasms, graphs, in ecto are Directed Acyclic Graphs , see plasm. The DAG is important for scheduling and determining execution order. It is used to describe the happens before relationships in cells. Because of the DAG construct, implicit feedback loops are not allowed, and can only be achieved through special purpose cells, see Entanglement for more information.

Execution

Once a plasm is constructed, it may be used with an ecto scheduler.

# create a scheduler that will run that plasm
sched = ecto.Scheduler(plasm)
# execute the plasm
sched.execute(niter=2)

The ecto.Scheduler scheduler essentially does a topological-sort on the graph, and then executes each cell in order. This repeats for the specified number of iterations, or indefinitely if niter is 0.

For parallel or threaded execution of plasms, please read the schedulers documentation for a more detailed reference. Note that support for parallel execution is presently restricted to running multiple graphs in parallel. Although one of the project’s initial design goals, it does not at this time introduce parallelism to the internal execution of a graph (this is a very complex thing to manage).

Affecting the Execution of the Graph

There are various tricks that can be used to affect/interrupt/halt the direction of execution of the graph.

Various cells can manipulate the graph by grouping or providing some conditional logic that affects the processing of cells that have been combined internally within themselves. See for example, the BlackBox and If cells.

The return value from a cell’s process() function will also instruct the scheduler on how it should continue processing cells in the remainder of the graph. The possible return values are listed below with an explanation for quick reference:

  • ecto::OK : directed execution proceeds normally to the next cell in the graph.
  • ecto::DO_OVER : repeats processing for that cell.
  • ecto::BREAK : abort the current iteration of the graph and return to its starting point.
  • ecto::QUIT : completely terminate the execution.

Mutexes/barriers or similar intraprocess mechanisms inside a cell that is present in multiple parallelised graphs will also halt execution of the graph at a particular point.