1.. ############################################################################
2.. # Copyright (c) Lawrence Livermore National Security, LLC and other Ascent
3.. # Project developers. See top-level LICENSE AND COPYRIGHT files for dates and
4.. # other details. No copyright assignment is required to contribute to Ascent.
5.. ############################################################################
6
7.. _flow_filter:
8
9Flow Filter Anatomy
10===================
11Flow filters are the basic unit of execution inside of Ascent, and all functionality
12is implemented as a Flow filter. The full interface to a Flow filter can be found in the
13`Flow filter header file <https://github.com/Alpine-DAV/ascent/blob/develop/src/flow/flow_filter.hpp>`_.
14Here is a summary of the functions relevant to a filter developer:
15
16.. code-block:: c++
17
18  public:
19    Filter();
20    virtual ~Filter();
21
22    // override and fill i with the info about the filter's interface
23    virtual void          declare_interface(conduit::Node &i) = 0;
24
25    // override to imp filter's work
26    virtual void          execute() = 0;
27
28    // optionally override to allow filter to verify custom params
29    // (used as a guard when a filter instance is created in a graph)
30    virtual bool          verify_params(const conduit::Node &params,
31                                        conduit::Node &info);
32
33
34A derived filter must minimally implement the ``declare_interface`` and ``execute``
35methods, but it is highly encouraged that a new filter implement ``verify_params``
36as well. ``verify_params`` alerts users to input errors and unexpected parameters.
37
38.. note::
39
40    Developing a flow filter requires a working knowledge of the Conduit API.
41    In the :ref:`tutorial_intro` section under ``Conduit Examples``, there are several
42    examples of basic Conduit usage. More Conduit tutorial resources can be found in the
43    `Conduit documentation <https://llnl-conduit.readthedocs.io/en/latest/tutorial_cpp.html>`_.
44
45Flow filter implementations are located in the ``src/ascent/runtimes/flow_filters`` directory.
46
47Implementing A New Filter
48-------------------------
49As a convenience, we have created the
50`VTKHNoOp <https://github.com/Alpine-DAV/ascent/blob/develop/src/ascent/runtimes/flow_filters/ascent_runtime_vtkh_filters.cpp>`_
51filter as staring point and reference. Although the NoOp filter demonstrates how to use a
52VTK-h filter, the implementation is relevant to anyone developing flow filters in Ascent
53regardless of whether VTK-h or VTK-m is used.
54
55Interface Declaration
56"""""""""""""""""""""
57.. code-block:: c++
58
59    void
60    VTKHNoOp::declare_interface(conduit::Node &i)
61    {
62        i["type_name"]   = "vtkh_no_op";
63        i["port_names"].append() = "in";
64        i["output_port"] = "true";
65    }
66
67
68* ``type_name``: declares the name of the filter to flow, and the only requirement is that this name be unique.
69* ``port_names``: declares a list of input port names.
70* ``output_port``: declares if this filter has an output of not. Valid values are ``true`` and ``false``.
71
72The ``port_names`` parameter is a list of input port names that can be referenced by name or index
73when creating the filter within the runtime. The typical number of inputs is one, but there is no
74restriction on the input count. To add additional inputs, additional ``append()`` calls will add
75more inputs to the port list, and the input port names must be unique.
76
77.. code-block:: c++
78
79   i["port_names"].append() = "in1";
80   i["port_names"].append() = "in2";
81
82
83For the majority of developers, a transform (i.e., a filter that can be part of a pipeline)
84filter will have one input (e.g., the data set) and one output. If creating an extract,
85the ``output_port`` should be declared ``false`` indicating that this filter is a sink.
86
87Parameter Verification
88""""""""""""""""""""""
89Parameters are passed through Ascent and then to filters. For detailed
90examples of filter in Ascent see the :ref:`pipelines` section.
91
92
93How Are Parameters Passed?
94^^^^^^^^^^^^^^^^^^^^^^^^^^
95The parameters are passed to the Ascent API through Conduit nodes. A simple filter
96interface looks like this in c++:
97
98.. code-block:: c++
99
100    conduit::Node filter;
101    filter["type"] = "filter_name";
102    filter["params/string_param"] = "string";
103    filter["params/double_param"] = 2.0;
104
105
106or equivalently in json:
107
108.. code-block:: json
109
110    {
111      "type"   : "filter_name",
112      "params":
113      {
114        "string_param" : "string",
115        "double_param" : 2.0
116      }
117    }
118
119The Ascent runtime looks for the ``params`` node and passes it to the filter
120upon creation. Parameters are verified when the filter is created during execution.
121
122Filter Parameter Verification
123^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
124The ``verify_params`` method allow the filter creator to verify the expected parameters
125and parameter types before the filter is executed. If the verification fails, error messages
126are shown to the user. The method has two parameters: a Conduit node holding the parameters
127of the filter and a Conduit node that is populated with error information that flow will
128show if the result of the verification is false (error state).
129
130.. code-block:: c++
131    :caption: Example parameter verification
132    :name: verify
133    :linenos:
134
135    bool
136    VTKHNoOp::verify_params(const conduit::Node &params,
137                            conduit::Node &info)
138    {
139        info.reset();
140
141        bool res = check_string("field",params, info, true);
142
143        std::vector<std::string> valid_paths;
144        valid_paths.push_back("field");
145
146        std::string surprises = surprise_check(valid_paths, params);
147
148        if(surprises != "")
149        {
150          res = false;
151          info["errors"].append() = surprises;
152        }
153
154        return res;
155    }
156
157Check Parameters
158++++++++++++++++
159While you can use the Conduit API to check for expected paths and types of values, we
160provide a number of methods to streamline common checks. These
161`parameter checking helpers <https://github.com/Alpine-DAV/ascent/blob/develop/src/ascent/runtimes/flow_filters/ascent_runtime_param_check.hpp>`_
162provide two basic checking mechanisms:
163
164* ``check_string``: checks for the presence of a string parameter
165* ``check_numeric``: checks for the presence of a numeric parameter
166
167Both functions have the same signature:
168
169.. code-block:: c++
170
171    bool check_numeric(const std::string path,
172                       const conduit::Node &params,
173                       conduit::Node &info,
174                       bool required);
175
176* ``path``: the expected path to the parameter in the Conduit node
177* ``params``: the parameters passed into verify
178* ``info``: the info node passed into verify
179* ``required``: indication that the parameter is required or optional
180
181These helper functions return ``false`` if the parameter check fails.
182
183Surprises
184+++++++++
185A common user error is to set a parameter at the wrong path.z
186For example the filter expects a parameter ``field`` but the user
187adds the path ``field_name``, the verification will fail and complain about a
188missing parameter. In order to provide a better error message, we provide
189a surprise parameter checking mechanism that reports unknown paths.
190Lines 9-18 in :ref:`verify` show how to use the surprise_check function to
191declare a set of known parameters and check for the existence of surprises.
192``surpise_check`` also allows you to ignore certain paths, which enables
193hierarchical surprise checking.
194
195Execute
196"""""""
197The `execute()` method does the real work. In our example, we are wrapping the
198``VTKHNoOp`` filter which is a `transform`, i.e., a filter that can be called
199inside of a pipeline. Be default, `transforms` are passed VTK-h data sets and
200`extracts` are called with either Conduit Blueprint data sets (i.e., the data
201published by the simulation) or VTK-h data sets, when the `extract` consumes
202the result of a pipeline. The data type can be checked by the filter and converted
203by one of Ascent's data adapters located in the ``src/ascent/runtimes`` directory.
204
205.. code-block:: c++
206    :caption: An example execute method
207    :linenos:
208
209    void
210    VTKHNoOp::execute()
211    {
212
213        if(!input(0).check_type<vtkh::DataSet>())
214        {
215            ASCENT_ERROR("vtkh_no_op input must be a vtk-h dataset");
216        }
217
218        std::string field_name = params()["field"].as_string();
219
220        vtkh::DataSet *data = input<vtkh::DataSet>(0);
221        vtkh::NoOp noop;
222
223        noop.SetInput(data);
224        noop.SetField(field_name);
225
226        noop.Update();
227
228        vtkh::DataSet *noop_output = noop.GetOutput();
229
230        set_output<vtkh::DataSet>(noop_output);
231    }
232
233
234Filter Inputs
235^^^^^^^^^^^^^
236
237Inputs to filters are always pointers.
238Lines 5-8 demonstrate how to check the type of data to the filter.
239``input(0).check_type<SomeType>()`` returns true if the input pointer
240is of the same type as the template parameter. Alternatively, we could
241reference the input port by its declared interface name:
242``input("in").check_type<SomeType>()``.
243
244.. warning::
245    If you perform input data type conversion, the temporary converted
246    data must be deleted before exiting the execute method.
247
248Once the filter input type is known it is safe to call ``input<KnownType>(0)``
249to retrieve the pointer to the input (line 12).
250
251Flow filters have a member function ``params()`` that returns a reference
252to the Conduit node containing the filter parameters that were previously
253verified. Since we already verified the existence of the string parameter
254``field``, it is safe to grab that parameter without checking the type or
255path.
256
257
258For optional parameters, care should be used when accessing node paths.
259Conduit nodes paths can be checked with ``params().has_path("some_path")``
260Other methods exist to verify or convert their underlying types such as
261``node["path"].is_numeric()``. If you are expecting an integer the semantics
262between these two calls are very different:
263
264* ``node["path"].as_int32()``: I am positive this is an int32 and I alone
265  accept the consequences if it is not
266* ``node["path"].to_int32()``: I am expecting an int32 and please convert if for me
267  assuming whatever type it is can be converted to what I am expecting
268
269Filter Output
270^^^^^^^^^^^^^
271A filter's output is a pointer to a data sets. In the case of `tranforms` this type is
272expected to be a VTK-h data set. Output pointers are reference counted by Flow's registry
273and will be deleted when no downstream filter needs the output of the current filter.
274
275In the case of an `extract`, no output needs to be set.
276
277Registering Filters With Ascent
278"""""""""""""""""""""""""""""""
279Newly created filters need to be registered with the Ascent runtime.
280The file
281`ascent_runtime_filters.cpp <https://github.com/Alpine-DAV/ascent/blob/develop/src/ascent/runtimes/flow_filters/ascent_runtime_filters.cpp>`_
282is where all builtin filter are registered. Following the NoOp example:
283
284.. code-block:: c++
285    :caption: Ascent runtime filter registration
286
287    AscentRuntime::register_filter_type<VTKHNoOp>("transforms","noop");
288
289Filter registration is templated on the filter type and takes two arguments.
290
291* arg1: the type of the fitler. Valid values are ``transforms`` and ``extracts``
292* arg2: the front-facing API name of the filter. This is what a user would declare in an actions file.
293
294Accessing Metadata
295------------------
296We currently populate a limited set of metadata that is accessable to flow filters.
297We place a Conduit node containing the metadata inside the registry which can be
298accessed in the following manner:
299
300.. code-block:: c++
301    :caption: Accessing the regsitry metadata inside a flow filter
302
303    conduit::Node * meta = graph().workspace().registry().fetch<Node>("metadata");
304    int cycle = -1;
305    float time = -1.f;
306    if(meta->has_path("cycle"))
307    {
308      cycle = (*meta)["cycle"].to_int32();
309    }
310    if(meta->has_path("time"))
311    {
312       time = (*meta)["time"].to_int32();
313    }
314
315The above code is conservative, checking to see if the paths exist. The current metadata values
316Ascent populates are:
317
318* cycle: simulation cycle
319* time: simulation time
320* refinement_level: number of times a high-order mesh is refined
321
322If these values are not provided by the simulation, then defaults are used.
323
324Using the Registry (state)
325--------------------------
326Filters are created and destroyed every time the graph is executed. Filters might
327want to keep state associated with a particular execution of the filter. A conduit node
328is a convenient container for arbitrary data, but there is no restriction on the type
329of data that can go inside the registry.
330
331.. code-block:: c++
332    :caption: Accessing the registry metadata inside a flow filter
333
334    conduit::Node *my_state_data = new conduit::Node();
335    // insert some data to the node
336
337    // adding the  node to the registry
338    graph().workspace().registry().add<conduit::Node>("my_state", my_state_data, 1);
339
340    // check for existence and retrieve
341    if(graph().workspace().registry().has_entry("my_state"))
342    {
343      conduit::Node *data = graph().workspace().registry().fetch<conduit::Node>("my_state"))
344      // do more stuff
345    }
346
347Data kept in the registry will be destroyed when Ascent is torn down, but will persist otherwise.
348A problem that arises is how to tell different invocations of the same filter apart, since
349a filter can be called an arbitrary number of times every time ascent is executed. The Ascent
350runtime gives unique names to filters that can be accessed by a filter member function
351``this->detailed_name()``. One possible solution is to use this name to differentiate
352filter invocations. This approach is reasonable if the actions remain the same throughout
353the simulation, but if they might change, all bets are o ff.
354
355.. note::
356    Advanced support of registry and workspace usage is only supported through
357    the Ascent developers platinum support contract, which can be purchased with
358    baby unicorn tears. Alternatively, you are encouraged to look at the flow
359    source code, unit tests, and ask questions.
360
361Using MPI Inside Ascent
362-----------------------
363
364Ascent creates two separate libraries for MPI and non-MPI (i.e., serial).
365In order to maintain the same interface for both versions of the library, ``MPI_Comm`` handles
366are represented by integers and are converted to the MPI implementations underlying representation
367by using the ``MPI_Comm_f2c`` function.
368
369Code containing calls to MPI are protected by the define ``ASCENT_MPI_ENABLED`` and calls to MPI API calls
370must be guarded inside the code. In Ascent, the MPI comm handle is stored in and can be
371retrieved from the ``flow::Workspace`` which is accessible from inside a flow filter.
372
373.. code-block:: c++
374    :caption: Example of code inside a filter that retrieves the MPI comm handle from the workspace
375
376    #ifdef ASCENT_MPI_ENABLED
377      int comm_id = flow::Workspace::default_mpi_comm();
378      MPI_Comm mpi_comm = MPI_Comm_f2c(comm_id);
379      int rank;
380      MPI_Comm_rank(comm, &rank);
381    #endif
382
383
384