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 ¶ms, 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 ¶ms, 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 ¶ms, 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