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