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 
8 //-----------------------------------------------------------------------------
9 ///
10 /// file: flow_graph.cpp
11 ///
12 //-----------------------------------------------------------------------------
13 
14 #include "flow_graph.hpp"
15 
16 // standard lib includes
17 #include <iostream>
18 #include <string.h>
19 #include <limits.h>
20 #include <cstdlib>
21 
22 //-----------------------------------------------------------------------------
23 // thirdparty includes
24 //-----------------------------------------------------------------------------
25 
26 // conduit includes
27 #include <conduit_relay.hpp>
28 
29 
30 //-----------------------------------------------------------------------------
31 // flow includes
32 //-----------------------------------------------------------------------------
33 #include <flow_workspace.hpp>
34 
35 
36 using namespace conduit;
37 using namespace std;
38 
39 //-----------------------------------------------------------------------------
40 // -- begin flow:: --
41 //-----------------------------------------------------------------------------
42 namespace flow
43 {
44 
45 //-----------------------------------------------------------------------------
46 //-----------------------------------------------------------------------------
47 
48 
49 //-----------------------------------------------------------------------------
Graph(Workspace * w)50 Graph::Graph(Workspace *w)
51 :m_workspace(w),
52  m_filter_count(0)
53 {
54     init();
55 }
56 
57 //-----------------------------------------------------------------------------
~Graph()58 Graph::~Graph()
59 {
60     reset();
61 }
62 
63 
64 //-----------------------------------------------------------------------------
65 Workspace &
workspace()66 Graph::workspace()
67 {
68     return *m_workspace;
69 }
70 
71 //-----------------------------------------------------------------------------
72 void
reset()73 Graph::reset()
74 {
75     // delete all filters
76 
77     std::map<std::string,Filter*>::iterator itr;
78     for(itr = m_filters.begin(); itr != m_filters.end(); itr++)
79     {
80         delete itr->second;
81     }
82 
83     m_filters.clear();
84     m_edges.reset();
85     init();
86 
87 }
88 
89 //-----------------------------------------------------------------------------
90 void
init()91 Graph::init()
92 {
93     // init edges data
94     m_edges["in"];
95     m_edges["out"];
96 
97 }
98 
99 
100 //-----------------------------------------------------------------------------
101 Filter *
add_filter(const std::string & filter_type,const std::string & filter_name)102 Graph::add_filter(const std::string &filter_type,
103                   const std::string &filter_name)
104 {
105     Node filter_params;
106     return add_filter(filter_type, filter_name, filter_params);
107 }
108 
109 
110 //-----------------------------------------------------------------------------
111 Filter *
add_filter(const std::string & filter_type,const std::string & filter_name,const Node & filter_params)112 Graph::add_filter(const std::string &filter_type,
113                   const std::string &filter_name,
114                   const Node &filter_params)
115 {
116     if(has_filter(filter_name))
117     {
118         CONDUIT_WARN("Cannot create filter, filter named: " << filter_name
119                      << " already exists in Graph");
120         return NULL;
121     }
122 
123     Filter *f = Workspace::create_filter(filter_type);
124 
125     f->init(this,
126             filter_name,
127             filter_params);
128 
129     Node v_info;
130     if(!f->verify_params(filter_params,v_info))
131     {
132         std::string f_name = f->detailed_name();
133         // cleanup f ...
134         delete f;
135         CONDUIT_ERROR("Cannot create filter " << f_name
136                       << " because verify_params failed." << std::endl
137                       << "Details:" << std::endl
138                       << v_info.to_json());
139         return NULL;
140     }
141 
142 
143     m_filters[filter_name] = f;
144 
145     NodeConstIterator ports_itr = f->port_names().children();
146 
147     while(ports_itr.has_next())
148     {
149         std::string port_name = ports_itr.next().as_string();
150         m_edges["in"][filter_name][port_name] = DataType::empty();
151     }
152 
153     if(f->output_port())
154     {
155         m_edges["out"][filter_name] = DataType::list();
156     }
157 
158     m_filter_count++;
159 
160     return f;
161 }
162 
163 //-----------------------------------------------------------------------------
164 Filter *
add_filter(const std::string & filter_type)165 Graph::add_filter(const std::string &filter_type)
166 {
167     ostringstream oss;
168     oss << "f_" << m_filter_count;
169     Node filter_params;
170     return add_filter(filter_type, oss.str(), filter_params);
171 }
172 
173 //-----------------------------------------------------------------------------
174 Filter *
add_filter(const std::string & filter_type,const Node & filter_params)175 Graph::add_filter(const std::string &filter_type,
176                   const Node &filter_params)
177 {
178     ostringstream oss;
179     oss << "f_" << m_filter_count;
180     return add_filter(filter_type, oss.str(), filter_params);
181 }
182 
183 
184 
185 //-----------------------------------------------------------------------------
186 void
connect(const std::string & src_name,const std::string & des_name,const std::string & port_name)187 Graph::connect(const std::string &src_name,
188                const std::string &des_name,
189                const std::string &port_name)
190 {
191     // make sure we have a filter with the given name
192 
193     if(!has_filter(src_name))
194     {
195         CONDUIT_WARN("source filter named: " << src_name
196                     << " does not exist in Filter Graph");
197         return;
198     }
199 
200     if(!has_filter(des_name))
201     {
202         CONDUIT_WARN("destination filter named: " << des_name
203                     << " does not exist in Filter Graph");
204         return;
205     }
206 
207 
208     Filter *des_filter = m_filters[des_name];
209 
210     // make sure it has an input port with the given name
211     if(!des_filter->has_port(port_name))
212     {
213         CONDUIT_WARN("destination filter: "
214                      << des_filter->detailed_name()
215                      << " does not have input port named:"
216                      << port_name);
217         return;
218     }
219 
220     m_edges["in"][des_name][port_name] = src_name;
221     m_edges["out"][src_name].append().set(des_name);
222 }
223 
224 //-----------------------------------------------------------------------------
225 void
connect(const std::string & src_name,const std::string & des_name,int port_idx)226 Graph::connect(const std::string &src_name,
227                const std::string &des_name,
228                int port_idx)
229 {
230     if(!has_filter(des_name))
231     {
232         CONDUIT_WARN("destination filter named: " << des_name
233                     << " does not exist in Filter Graph ");
234         return;
235     }
236 
237     Filter *des_filter = m_filters[des_name];
238     std::string port_name = des_filter->port_index_to_name(port_idx);
239 
240 
241     connect(src_name,des_name,port_name);
242 }
243 
244 
245 
246 
247 //-----------------------------------------------------------------------------
248 bool
has_filter(const std::string & name)249 Graph::has_filter(const std::string &name)
250 {
251     std::map<std::string,Filter*>::iterator itr = m_filters.find(name);
252     return itr != m_filters.end();
253 }
254 
255 //-----------------------------------------------------------------------------
256 void
remove_filter(const std::string & name)257 Graph::remove_filter(const std::string &name)
258 {
259     if(!has_filter(name))
260     {
261         CONDUIT_WARN("filter named: " << name
262                      << " does not exist in Filter Graph");
263         return;
264     }
265 
266     // remove from m_filters, and prune edges
267     std::map<std::string,Filter*>::iterator itr = m_filters.find(name);
268 
269     delete itr->second;
270 
271     m_filters.erase(itr);
272 
273     m_edges["in"].remove(name);
274     m_edges["out"].remove(name);
275 }
276 
277 //-----------------------------------------------------------------------------
278 const Node &
edges() const279 Graph::edges() const
280 {
281     return m_edges;
282 }
283 
284 //-----------------------------------------------------------------------------
285 const Node &
edges_out(const std::string & f_name) const286 Graph::edges_out(const std::string &f_name) const
287 {
288     return edges()["out"][f_name];
289 }
290 
291 //-----------------------------------------------------------------------------
292 const Node &
edges_in(const std::string & f_name) const293 Graph::edges_in(const std::string &f_name) const
294 {
295     return edges()["in"][f_name];
296 }
297 
298 
299 //-----------------------------------------------------------------------------
300 std::map<std::string,Filter*>  &
filters()301 Graph::filters()
302 {
303     return m_filters;
304 }
305 
306 
307 //-----------------------------------------------------------------------------
308 void
filters(Node & out) const309 Graph::filters(Node &out) const
310 {
311     out.reset();
312     std::map<std::string,Filter*>::const_iterator itr;
313     for(itr = m_filters.begin(); itr != m_filters.end(); itr++)
314     {
315         Filter *f_ptr = itr->second;
316         Node &f_info = out[itr->first];
317         f_info["type_name"] = f_ptr->type_name();
318 
319         if(f_ptr->params().number_of_children() > 0)
320         {
321             f_info["params"] = f_ptr->params();
322         }
323     }
324 }
325 
326 
327 //-----------------------------------------------------------------------------
328 void
connections(Node & out) const329 Graph::connections(Node &out) const
330 {
331     out.reset();
332     NodeConstIterator edges_itr = edges()["in"].children();
333     while(edges_itr.has_next())
334     {
335         const Node &edge = edges_itr.next();
336         std::string dest_name = edges_itr.name();
337         NodeConstIterator ports_itr = edge.children();
338         while(ports_itr.has_next())
339         {
340             const Node &port = ports_itr.next();
341             if(port.dtype().is_string())
342             {
343                 std::string port_name = ports_itr.name();
344                 std::string src_name  = port.as_string();
345                 Node &edge = out.append();
346                 edge["src"]  = src_name;
347                 edge["dest"] = dest_name;
348                 edge["port"] = port_name;
349             }
350         }
351     }
352 }
353 
354 
355 
356 //-----------------------------------------------------------------------------
357 void
add_filters(const Node & filters)358 Graph::add_filters(const Node &filters)
359 {
360     CONDUIT_INFO(filters.to_json());
361 
362     NodeConstIterator filters_itr = filters.children();
363 
364     // first make sure we have only supported filters.
365     bool ok = true;
366     ostringstream oss;
367 
368     while(filters_itr.has_next())
369     {
370         const Node &f_info = filters_itr.next();
371         std::string f_name = filters_itr.name();
372 
373         if(!f_info.has_child("type_name") ||
374            !f_info["type_name"].dtype().is_string())
375         {
376             oss << "Filter '"
377                 << f_name
378                 << "' is missing required 'type_name' entry"
379                 << std::endl;
380             ok = false;
381         }
382         else
383         {
384             std::string f_type = f_info["type_name"].as_string();
385             if(!Workspace::supports_filter_type(f_type))
386             {
387 
388                 oss << "Workspace does not support filter type "
389                     << "'" << f_type << "' "
390                     << "(filter name: '" << f_name << "')"
391                     << std::endl;
392                 ok = false;
393             }
394         }
395     }
396 
397     // provide one error message with all issues discovered
398     if(!ok)
399     {
400         CONDUIT_ERROR(oss.str());
401         return;
402     }
403 
404     filters_itr.to_front();
405 
406     while(filters_itr.has_next())
407     {
408         const Node &f_info = filters_itr.next();
409         std::string f_name = filters_itr.name();
410         std::string f_type = f_info["type_name"].as_string();
411 
412         if(f_info.has_child("params"))
413         {
414             add_filter(f_type,f_name,f_info["params"]);
415         }
416         else
417         {
418             add_filter(f_type,f_name);
419         }
420     }
421 }
422 
423 //-----------------------------------------------------------------------------
424 void
add_connections(const Node & conns)425 Graph::add_connections(const Node &conns)
426 {
427     CONDUIT_INFO(conns.to_json());
428 
429     NodeConstIterator conns_itr = conns.children();
430     while(conns_itr.has_next())
431     {
432         const Node &edge = conns_itr.next();
433 
434         bool ok = true;
435         ostringstream oss;
436         if(!edge.has_child("src") ||
437            !edge["src"].dtype().is_string())
438         {
439             oss << "Connection is missing required 'src' entry" << std::endl;
440             ok = false;
441         }
442 
443         if(!edge.has_child("dest") ||
444            !edge["dest"].dtype().is_string())
445         {
446             oss << "Connection is missing required 'dest' entry" << std::endl;
447             ok = false;
448         }
449 
450         if(!ok)
451         {
452             CONDUIT_ERROR(oss.str());
453             return;
454         }
455 
456 
457         if(edge.has_child("port"))
458         {
459             connect(edge["src"].as_string(),
460                     edge["dest"].as_string(),
461                     edge["port"].as_string());
462         }
463         else
464         {
465             connect(edge["src"].as_string(),
466                     edge["dest"].as_string(),
467                     0);
468         }
469     }
470 }
471 
472 
473 //-----------------------------------------------------------------------------
474 void
add_graph(const Graph & g)475 Graph::add_graph(const Graph &g)
476 {
477     Node n;
478     g.info(n);
479     add_graph(n);
480 }
481 
482 //-----------------------------------------------------------------------------
483 void
add_graph(const Node & g)484 Graph::add_graph(const Node &g)
485 {
486     if(g.has_child("filters"))
487     {
488         add_filters(g["filters"]);
489     }
490 
491     if(g.has_child("connections"))
492     {
493         add_connections(g["connections"]);
494     }
495 }
496 
497 
498 
499 //-----------------------------------------------------------------------------
500 void
save(const std::string & path,const std::string & protocol)501 Graph::save(const std::string &path,const std::string &protocol)
502 {
503     Node out;
504     save(out);
505     conduit::relay::io::save(out,path,protocol);
506 }
507 
508 //-----------------------------------------------------------------------------
509 void
save(Node & out)510 Graph::save(Node &out)
511 {
512     out.reset();
513     info(out);
514 }
515 
516 
517 //-----------------------------------------------------------------------------
518 void
load(const std::string & path,const std::string & protocol)519 Graph::load(const std::string &path, const std::string &protocol)
520 {
521     Node n;
522     conduit::relay::io::load(path,protocol,n);
523     load(n);
524 }
525 
526 
527 //-----------------------------------------------------------------------------
528 void
load(const Node & n)529 Graph::load(const Node &n)
530 {
531     reset();
532     add_graph(n);
533 }
534 
535 
536 
537 
538 //-----------------------------------------------------------------------------
539 void
info(Node & out) const540 Graph::info(Node &out) const
541 {
542     out.reset();
543     filters(out["filters"]);
544     connections(out["connections"]);
545 }
546 
547 
548 //-----------------------------------------------------------------------------
549 std::string
to_json() const550 Graph::to_json() const
551 {
552     Node out;
553     info(out);
554     ostringstream oss;
555     out.to_json_stream(oss);
556     return oss.str();
557 }
558 
559 //-----------------------------------------------------------------------------
560 std::string
to_yaml() const561 Graph::to_yaml() const
562 {
563     Node out;
564     info(out);
565     ostringstream oss;
566     out.to_yaml_stream(oss);
567     return oss.str();
568 }
569 
570 //-----------------------------------------------------------------------------
571 std::string
to_dot() const572 Graph::to_dot() const
573 {
574     ostringstream oss;
575     to_dot(oss);
576     return oss.str();
577 }
578 
579 //-----------------------------------------------------------------------------
580 std::string
to_dot_html() const581 Graph::to_dot_html() const
582 {
583     ostringstream oss;
584     to_dot_html(oss);
585     return oss.str();
586 }
587 
588 //-----------------------------------------------------------------------------
589 void
to_dot(std::ostream & os,const std::string & eol) const590 Graph::to_dot(std::ostream &os,
591               const std::string &eol) const
592 {
593     Node out;
594     info(out);
595 
596     // traverse conns to create a dot graph;
597     os << "digraph {" << eol;
598 
599 
600     NodeConstIterator itr = out["filters"].children();
601     while(itr.has_next())
602     {
603         const Node &f= itr.next();
604         std::string f_name = itr.name();
605         os << "  \""
606            << f_name
607            << "\" [label=\"" << f_name
608            << "(" << f["type_name"].as_string() << ")"
609            << "\"];" << eol;
610     }
611 
612     itr = out["connections"].children();
613 
614     while(itr.has_next())
615     {
616         const Node &c= itr.next();
617         os << "  \""
618            << c["src"].as_string()
619            << "\" -> \""
620            << c["dest"].as_string()
621            << "\"[ label=\"" << c["port"].as_string() << "\" ]"
622            << ";"
623            << eol;
624     }
625 
626     os << "}" << eol;
627 }
628 
629 //-----------------------------------------------------------------------------
630 void
to_dot_html(std::ostream & os) const631 Graph::to_dot_html(std::ostream &os) const
632 {
633 
634     // TODO: Path that bundles these js deps w/ ascent?
635     os << "<script src=\"https://d3js.org/d3.v4.min.js\"></script>\n"
636           "<script src=\"https://unpkg.com/viz.js@1.8.0/viz.js\" type=\"javascript/worker\"></script>\n"
637           "<script src=\"https://unpkg.com/d3-graphviz@1.3.1/build/d3-graphviz.min.js\"></script>\n"
638           "<div id=\"graph\" style=\"text-align: center;\"></div>\n"
639           "<script>\n"
640           "\n"
641           "d3.select(\"#graph\")\n"
642           "  .graphviz()\n"
643           "    .renderDot('";
644 
645     // gen dot def, with proper js escaping
646     // we are injected as inline js literal -- new lines need to be escaped.
647     // Add \ to the end of each line in our dot output.
648     to_dot(os," \\\n");
649 
650     os << "');\n"
651           "\n"
652           "</script>\n";
653 }
654 
655 //-----------------------------------------------------------------------------
656 void
save_dot(const std::string & ofile) const657 Graph::save_dot(const std::string &ofile) const
658 {
659     std::ofstream ofs;
660     ofs.open(ofile.c_str());
661     if(!ofs.is_open())
662     {
663         CONDUIT_ERROR("Failed to open "
664                       << ofile
665                       << " to save dot txt result.");
666     }
667 
668     to_dot(ofs);
669 
670     ofs.close();
671 }
672 
673 //-----------------------------------------------------------------------------
674 void
save_dot_html(const std::string & ofile) const675 Graph::save_dot_html(const std::string &ofile) const
676 {
677     std::ofstream ofs;
678     ofs.open(ofile.c_str());
679     if(!ofs.is_open())
680     {
681         CONDUIT_ERROR("Failed to open "
682                        << ofile
683                        << " to save dot html result.");
684     }
685 
686     // add html header to create full well formed html doc
687     ofs << "<!DOCTYPE html>\n"
688            "<meta charset=\"utf-8\">\n"
689            "<body>\n";
690 
691     to_dot_html(ofs);
692 
693     // add html footer to finish well formed html doc
694     ofs << "</body>\n"
695            "</html>\n";
696 
697     ofs.close();
698 }
699 
700 
701 //-----------------------------------------------------------------------------
702 void
print() const703 Graph::print() const
704 {
705     CONDUIT_INFO(to_json());
706 }
707 
708 
709 //-----------------------------------------------------------------------------
710 };
711 //-----------------------------------------------------------------------------
712 // -- end flow:: --
713 //-----------------------------------------------------------------------------
714 
715 
716 
717 
718