visualizer.cpp
1 /*
2  * This file is part of CasADi.
3  *
4  * CasADi -- A symbolic framework for dynamic optimization.
5  * Copyright (C) 2010-2023 Joel Andersson, Joris Gillis, Moritz Diehl,
6  * KU Leuven. All rights reserved.
7  * Copyright (C) 2011-2014 Greg Horn
8  *
9  * CasADi is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 3 of the License, or (at your option) any later version.
13  *
14  * CasADi is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with CasADi; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22  *
23  */
24 
25 
26 #include "function.hpp"
27 #include "casadi_meta.hpp"
28 #include "function_internal.hpp"
29 #include "sx_function.hpp"
30 #include "sx.hpp"
31 #include "serializer.hpp"
32 #include "mx_function.hpp"
33 #include "filesystem_impl.hpp"
34 #include <casadi/core/resource_casadi_viz.hpp>
35 
36 #include <algorithm>
37 #include <iomanip>
38 #include <map>
39 #include <sstream>
40 
41 namespace casadi {
42 namespace {
43 
44 // Escape JSON and HTML script delimiters without interpreting expression labels.
45 std::string graph_string(const std::string& value) {
46  std::ostringstream s;
47  s << '"';
48  for (unsigned char c : value) {
49  if (c == '"' || c == '\\') {
50  s << '\\' << c;
51  } else if (c < 32 || c == '<' || c == '>' || c == '&') {
52  s << "\\u" << std::hex << std::setfill('0') << std::setw(4)
53  << static_cast<unsigned int>(c) << std::dec;
54  } else {
55  s << c;
56  }
57  }
58  s << '"';
59  return s.str();
60 }
61 
62 struct GraphNode {
63  casadi_int id, op, io = -1, offset = 0;
64  bool binary, ordered;
65  std::string kind, display, symbol;
66  std::vector<std::string> input_names, output_names, constants;
67  std::vector<Sparsity> inputs, outputs;
68 };
69 struct GraphEdge { casadi_int from, output, to, input; };
70 struct GraphModel {
71  std::vector<GraphNode> nodes;
72  std::vector<GraphEdge> edges;
73 };
74 
75 // Instruction model for standalone DOT; browser exports carry the serialized Function instead.
76 void graph_model(const Function& function, GraphModel& model) {
77  auto sx = dynamic_cast<const SXFunction*>(function.get());
78  auto mx = dynamic_cast<const MXFunction*>(function.get());
79  const auto sx_inputs = sx ? function.sx_in() : std::vector<SX>{};
80  const auto mx_inputs = mx ? function.mx_in() : std::vector<MX>{};
81 
82  // Work slots are reused: resolve dependencies before replacing their producers.
83  std::map<casadi_int, std::pair<casadi_int, casadi_int>> producer;
84  for (casadi_int k = 0; k < function.n_instructions(); ++k) {
85  casadi_int op = function.instruction_id(k);
86  auto arg = function.instruction_input(k), res = function.instruction_output(k);
87  std::string label = casadi_math<double>::name(op);
88  GraphNode node;
89  node.id = k; node.op = op;
90  std::string kind = "operation";
91  if (op == OP_INPUT || op == OP_OUTPUT) {
92  bool input = op == OP_INPUT;
93  casadi_int io = (input ? arg : res).at(0);
94  casadi_int offset = sx ? (input ? arg : res).at(1)
95  : mx->algorithm_.at(k).data->offset();
96  node.io = io; node.offset = offset;
97  if (input) {
98  node.symbol = sx ? str(sx_inputs.at(io).nonzeros().at(offset))
99  : str(mx_inputs.at(io));
100  }
101  label = (input ? function.name_in(io) : function.name_out(io));
102  if ((sx && (input ? function.nnz_in(io) : function.nnz_out(io)) != 1) || offset != 0) {
103  label += "[" + str(offset) + "]";
104  }
105  kind = input ? "input" : "output";
106  } else if (op == OP_PARAMETER) {
107  kind = "symbol";
108  label = sx ? str(sx->free_vars_.at(sx->algorithm_.at(k).i1))
109  : mx->algorithm_.at(k).data.name();
110  } else if (op == OP_CONST) {
111  kind = "constant";
112  } else if (op == OP_CALL) {
113  kind = "call";
114  label = (sx ? sx->call_.el.at(sx->algorithm_.at(k).i1).f.name()
115  : mx->algorithm_.at(k).data.which_function().name());
116  }
117  if (op == OP_INPUT) arg.clear();
118  if (op == OP_OUTPUT) res.clear();
119  std::vector<std::string> input_names, output_names, constants;
120  for (casadi_int i = 0; i < arg.size(); ++i) input_names.push_back("arg" + str(i));
121  for (casadi_int i = 0; i < res.size(); ++i) output_names.push_back("out" + str(i));
122  if (op == OP_CALL) {
123  const Function& f = sx ? sx->call_.el.at(sx->algorithm_.at(k).i1).f
124  : mx->algorithm_.at(k).data.which_function();
125  Dict info = f.info();
126  auto source = info.find("model_path");
127  if (source != info.end()) {
128  std::string path = source->second.to_string();
129  if (!path.empty()) label += " [" + path.substr(path.find_last_of("/\\") + 1) + "]";
130  }
131  input_names.clear(); output_names.clear();
132  for (casadi_int side = 0; side < 2; ++side) {
133  auto& names = side == 0 ? input_names : output_names;
134  for (casadi_int i = 0; i < (side == 0 ? f.n_in() : f.n_out()); ++i) {
135  std::string name = side == 0 ? f.name_in(i) : f.name_out(i);
136  casadi_int count = side == 0 ? f.nnz_in(i) : f.nnz_out(i);
137  if (sx) {
138  for (casadi_int j = 0; j < count; ++j) {
139  names.push_back(name + (count == 1 ? "" : "[" + str(j) + "]"));
140  }
141  } else {
142  names.push_back(name);
143  }
144  }
145  }
146  }
147  std::string display = label;
148  if (kind == "operation") {
149  switch (op) {
150  case OP_ADD: display = "+"; break;
151  case OP_SUB: display = "-"; break;
152  case OP_MUL: display = "*"; break;
153  case OP_DIV: display = "/"; break;
154  case OP_NEG: display = "-"; break;
155  case OP_SQ: display = "(.)^2"; break;
156  case OP_POW: case OP_CONSTPOW: display = "pow"; break;
157  case OP_TWICE: display = "2*(.)"; break;
158  case OP_INV: display = "1/(.)"; break;
159  default: break;
160  }
161  } else if (op == OP_CONST) {
162  std::vector<double> values = sx ? std::vector<double> {sx->algorithm_.at(k).d}
163  : static_cast<DM>(mx->algorithm_.at(k).data).nonzeros();
164  for (double value : values) {
165  std::ostringstream number;
166  number << std::setprecision(17) << value;
167  constants.push_back(number.str());
168  }
169  display = constants.size() == 1 ? constants.front() : "constant";
170  }
171  node.kind = kind; node.display = display;
172  node.binary = casadi_math<double>::is_binary(op);
173  node.ordered = arg.size() > 1 && !operation_checker<CommChecker>(op);
174  node.input_names = input_names; node.output_names = output_names;
175  node.constants = constants;
176  for (casadi_int i = 0; i < arg.size(); ++i) {
177  node.inputs.push_back(sx ? Sparsity::scalar() : mx->algorithm_.at(k).data->dep(i).sparsity());
178  if (arg[i] < 0) continue;
179  auto p = producer.find(arg[i]);
180  casadi_assert(p != producer.end(), "Missing graph producer for instruction " + str(k));
181  model.edges.push_back({p->second.first, p->second.second, k, i});
182  }
183  for (casadi_int i = 0; i < res.size(); ++i) {
184  node.outputs.push_back(sx ? Sparsity::scalar() : mx->algorithm_.at(k).data->sparsity(i));
185  if (res[i] >= 0) producer[res[i]] = {k, i};
186  }
187  model.nodes.push_back(std::move(node));
188  }
189 }
190 
191 // DOT quoted strings and HTML labels have different escaping rules from JSON.
192 std::string dot_quote(const std::string& value) {
193  std::string out = "\"";
194  for (char c : value) {
195  if (c == '\n') {
196  out += "\\n";
197  } else if (c == '\r') {
198  out += "\\r";
199  } else {
200  if (c == '\\' || c == '"') out += '\\';
201  out += c;
202  }
203  }
204  return out + "\"";
205 }
206 std::string dot_html(const std::string& value) {
207  std::string out;
208  for (char c : value) {
209  switch (c) {
210  case '&': out += "&amp;"; break;
211  case '<': out += "&lt;"; break;
212  case '>': out += "&gt;"; break;
213  case '"': out += "&quot;"; break;
214  default: out += c;
215  }
216  }
217  return out;
218 }
219 std::string graph_size(const Sparsity& sp) {
220  return str(sp.size1()) + "-by-" + str(sp.size2());
221 }
222 void dot_matrix(std::ostream& s, const std::string& id, const Sparsity& sp,
223  const std::string& title, const std::vector<std::string>* constants = nullptr,
224  const std::string& color = "#666666", bool show_sizes = true) {
225  s << id << " [shape=plain, fontcolor=\"#666666\", fontsize=10, label=<"
226  << "<TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"3\">";
227  if (show_sizes || !title.empty()) s << "<TR><TD COLSPAN=\""
228  << std::max<casadi_int>(1, std::min<casadi_int>(8, sp.size2()))
229  << "\"><FONT COLOR=\"" << color << "\">"
230  << dot_html(show_sizes ? (title.empty() ? graph_size(sp) : title + " : " + graph_size(sp))
231  : title)
232  << "</FONT></TD></TR>";
233  std::map<std::pair<casadi_int, casadi_int>, casadi_int> lookup;
234  for (casadi_int c = 0; c < std::min<casadi_int>(8, sp.size2()); ++c) {
235  for (casadi_int k = sp.colind()[c]; k < sp.colind()[c+1]; ++k) {
236  if (sp.row()[k] < 8) lookup[{sp.row()[k], c}] = k;
237  }
238  }
239  for (casadi_int r = 0; r < std::min<casadi_int>(8, sp.size1()); ++r) {
240  s << "<TR>";
241  for (casadi_int c = 0; c < std::min<casadi_int>(8, sp.size2()); ++c) {
242  auto nz = lookup.find({r, c});
243  s << "<TD BGCOLOR=\"white\"";
244  if (nz != lookup.end()) s << " PORT=\"nz" << nz->second << "\"";
245  s << ">";
246  if (constants) s << (nz == lookup.end() ? "." : dot_html(constants->at(nz->second)));
247  else
248  s << "<TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"0\">"
249  << "<TR><TD FIXEDSIZE=\"TRUE\" WIDTH=\"9\" HEIGHT=\"9\" BGCOLOR=\""
250  << (nz == lookup.end() ? "white" : "#111111") << "\"></TD></TR></TABLE>";
251  s << "</TD>";
252  }
253  if (!sp.size2()) s << "<TD>empty</TD>";
254  s << "</TR>";
255  }
256  if (!sp.size1()) s << "<TR><TD>empty</TD></TR>";
257  if (sp.size1() > 8 || sp.size2() > 8) s << "<TR><TD>...</TD></TR>";
258  s << "</TABLE>>];\n";
259 }
260 bool dot_record(const GraphNode& n) {
261  return (n.ordered && !n.binary) || n.kind == "call";
262 }
263 bool dot_has_table(const GraphNode& n, const Sparsity& sp, bool show_contents) {
264  if (!show_contents || sp.numel() == 1) return false;
265  if (n.kind == "call" || n.inputs.empty() || n.op == OP_GETNONZEROS
266  || n.op == OP_SETNONZEROS || n.op == OP_ADDNONZEROS) return true;
267  for (const auto& input : n.inputs) if (input != sp) return true;
268  return false;
269 }
270 void dot_ports(std::ostream& s, const GraphNode& n,
271  const std::vector<std::string>& names, const std::string& prefix, bool show_sizes) {
272  if (names.empty()) return;
273  s << "<TR><TD><TABLE BORDER=\"0\" CELLBORDER=\"1\" COLOR=\"#d26666\"><TR>";
274  for (casadi_int i = 0; i < names.size(); ++i) {
275  s << "<TD PORT=\"" << prefix << i << "\"><FONT POINT-SIZE=\"10\">"
276  << dot_html(names[i]);
277  const auto& sp = prefix == "in" ? n.inputs.at(i) : n.outputs.at(i);
278  if (show_sizes && sp.numel() != 1) s << " " << graph_size(sp);
279  s << "</FONT></TD>";
280  }
281  s << "</TR></TABLE></TD></TR>";
282 }
283 void graph_dot(std::ostream& s, const Function& f, const GraphModel& model,
284  const std::string& direction, const std::string& view, bool show_contents, bool show_sizes) {
285  bool function_view = view == "function";
286  std::map<std::pair<casadi_int, casadi_int>, std::string> sources;
287  std::map<casadi_int, std::string> targets;
288  s << "digraph G {\ngraph [rankdir=" << (function_view ? "TB" : direction)
289  << ", bgcolor=\"white\", pad=0.4, nodesep=0.55, ranksep=0.65];\n"
290  << "node [shape=ellipse, style=filled, color=\"#b00000\", fillcolor=\"#b00000\", "
291  << "fontcolor=white, fontname=Helvetica, fontsize=14, margin=\"0.15,0.08\", "
292  << "width=0.5, height=0.5];\nedge [color=\"#34658b\", penwidth=1.5, arrowsize=0.65];\n";
293  for (const auto& n : model.nodes) {
294  if (n.kind == "output" || (function_view && n.kind == "input")) continue;
295  const std::string id = "n" + str(n.id);
296  std::string display = !function_view && n.kind == "input" ? n.symbol : n.display;
297  std::string color = n.kind == "input" || n.kind == "symbol" ? "#34658b"
298  : n.kind == "constant" ? "#38754d" : "#b00000";
299  s << id << " [color=" << dot_quote(color) << ", fillcolor=" << dot_quote(color);
300  if (dot_record(n)) {
301  s << ", shape=plain, label=<<TABLE BORDER=\"0\" CELLBORDER=\"0\" "
302  << "CELLSPACING=\"0\" CELLPADDING=\"6\" BGCOLOR=\"#b00000\">";
303  dot_ports(s, n, n.input_names, "in", show_sizes);
304  s << "<TR><TD>" << dot_html(display) << "</TD></TR>";
305  if (n.kind == "call") dot_ports(s, n, n.output_names, "out", show_sizes);
306  s << "</TABLE>>";
307  } else {
308  const auto& ports = n.outputs.empty() ? n.inputs : n.outputs;
309  bool table = false;
310  for (const auto& sp : n.outputs) table = table || dot_has_table(n, sp, show_contents);
311  if (show_sizes && !table && !ports.empty() && ports.front().numel() != 1) {
312  display += "\n" + graph_size(ports.front());
313  }
314  s << ", label=" << dot_quote(display);
315  }
316  s << "];\n";
317  for (casadi_int i = 0; i < n.outputs.size(); ++i) {
318  const auto& sp = n.outputs[i];
319  if (!dot_has_table(n, sp, show_contents)) continue;
320  std::string value = "v" + str(n.id) + "_" + str(i);
321  dot_matrix(s, value, sp, n.kind == "call" ? n.output_names.at(i) : "",
322  n.kind == "constant" ? &n.constants : nullptr, "#666666", show_sizes);
323  s << id << (n.kind == "call" ? ":out" + str(i) : "") << " -> " << value << ";\n";
324  sources[{n.id, i}] = value;
325  }
326  }
327  for (bool input : {true, false}) {
328  std::vector<std::string> row;
329  for (casadi_int i = 0; i < (input ? f.n_in() : f.n_out()); ++i) {
330  const auto& sp = input ? f.sparsity_in(i) : f.sparsity_out(i);
331  if (!function_view && (input || !show_contents || !f.is_a("SXFunction")
332  || sp.numel() == 1)) continue;
333  std::string kind = input ? "input" : "output", id = "b" + kind + "_" + str(i);
334  std::string name = function_view ? (input ? f.name_in(i) : f.name_out(i)) : "";
335  if (show_contents && sp.numel() != 1) dot_matrix(s, id, sp, name, nullptr,
336  function_view ? (input ? "#34658b" : "#b00000") : "#666666", show_sizes);
337  else
338  s << id << " [label=" << dot_quote(name
339  + (show_sizes && sp.numel() != 1 ? "\n" + graph_size(sp) : "")) << ", color=\""
340  << (input ? "#34658b" : "#b00000") << "\", fillcolor=\""
341  << (input ? "#34658b" : "#b00000") << "\"];\n";
342  for (const auto& n : model.nodes) {
343  if (n.kind != kind || n.io != i) continue;
344  std::string endpoint = id;
345  if (show_contents && f.is_a("SXFunction") && sp.numel() != 1 && n.offset < sp.nnz()) {
346  const auto col = std::upper_bound(sp.colind(), sp.colind()+sp.size2()+1, n.offset)
347  - sp.colind() - 1;
348  if (col < 8 && sp.row()[n.offset] < 8) endpoint += ":nz" + str(n.offset);
349  }
350  if (input) sources[{n.id, 0}] = endpoint;
351  else
352  targets[n.id] = endpoint;
353  }
354  row.push_back(id);
355  }
356  if (function_view && !row.empty()) {
357  s << "{rank=" << (input ? "source" : "sink") << ";";
358  for (const auto& id : row) s << id << ";";
359  s << "}\n";
360  for (casadi_int i = 1; i < row.size(); ++i) {
361  s << row[i-1] << " -> " << row[i] << " [style=invis, weight=100];\n";
362  }
363  }
364  }
365  for (const auto& e : model.edges) {
366  const auto& from = model.nodes.at(e.from);
367  const auto& to = model.nodes.at(e.to);
368  auto source = sources.find({e.from, e.output});
369  auto target = targets.find(e.to);
370  if (to.kind == "output" && target == targets.end()) continue;
371  s << (source != sources.end() ? source->second : "n" + str(e.from)
372  + (from.kind == "call" ? ":out" + str(e.output) : "")) << " -> ";
373  if (target != targets.end()) s << target->second;
374  else
375  s << "n" << e.to << (to.binary ? (e.input == 0 ? ":w" : ":e")
376  : dot_record(to) ? ":in" + str(e.input) : "");
377  s << ";\n";
378  }
379  s << "}\n";
380 }
381 
382 struct GraphOptions {
383  std::string direction = "TB", viz_js, view = "function";
384  std::string viewer_url = "https://unpkg.com/@casadi/casadi-viz@"
385  + std::string(CasadiMeta::version()).substr(
386  0, std::string(CasadiMeta::version()).find_last_of('.'))
387  + "/dist/index.js";
388  bool include_functions = true, show_matrix_contents = true, show_matrix_sizes = true;
389  explicit GraphOptions(const Dict& opts) {
390  for (const auto& opt : opts) {
391  if (opt.first == "direction") {
392  direction = opt.second.to_string();
393  } else if (opt.first == "view") {
394  view = opt.second.to_string();
395  } else if (opt.first == "include_functions") {
396  include_functions = opt.second.to_bool();
397  } else if (opt.first == "show_matrix_contents") {
398  show_matrix_contents = opt.second.to_bool();
399  } else if (opt.first == "show_matrix_sizes") {
400  show_matrix_sizes = opt.second.to_bool();
401  } else if (opt.first == "viewer_url") {
402  viewer_url = opt.second.to_string();
403  } else if (opt.first == "viz_js") {
404  viz_js = opt.second.to_string();
405  } else {
406  casadi_error("Unknown export_graph option: " + opt.first);
407  }
408  }
409  casadi_assert(direction == "LR" || direction == "RL" || direction == "TB"
410  || direction == "BT", "Invalid export_graph direction: " + direction);
411 
412  casadi_assert(view == "function" || view == "expression",
413  "Invalid export_graph view: " + view);
414 
415  }
416 };
417 
418 std::string graph_dot(const Function& f, const GraphOptions& opts) {
419  casadi_assert(f.is_a("SXFunction") || f.is_a("MXFunction"),
420  "DOT export requires an SXFunction or MXFunction; use .html or .casadi_viz for "
421  + f.class_name());
422  GraphModel model;
423  graph_model(f, model);
424  std::ostringstream output;
425  graph_dot(output, f, model, opts.direction, opts.view,
426  opts.show_matrix_contents, opts.show_matrix_sizes);
427  return output.str();
428 }
429 
430 void write_graph(const std::string& fname, const std::string& data,
431  const GraphOptions& options) {
432  if (fname.size() < 5 || fname.substr(fname.size()-5) != ".html") {
433  auto output = Filesystem::ofstream_ptr(fname);
434  *output << data << "\n";
435  output->flush();
436  casadi_assert(output->good(), "Failed to write graph to '" + fname + "'");
437  return;
438  }
439 
440  std::ostringstream config;
441  config << "{\"viewer_url\":" << graph_string(options.viewer_url) << ",\"runtime\":";
442  if (options.viz_js.empty()) {
443  config << "null";
444  } else {
445  auto input = Filesystem::ifstream_ptr(options.viz_js);
446  std::ostringstream contents;
447  contents << input->rdbuf();
448  config << "{\"source\":" << graph_string(contents.str()) << "}";
449  }
450 
451  config << "}";
452 
453  const std::string html = resource_casadi_viz;
454  const std::string graph_marker = "@@GRAPH_DATA@@", viz_marker = "@@VIZ_CONFIG@@";
455  size_t graph_pos = html.find(graph_marker), viz_pos = html.find(viz_marker);
456  casadi_assert_dev(graph_pos != std::string::npos && viz_pos > graph_pos);
457  auto output = Filesystem::ofstream_ptr(fname);
458  *output << html.substr(0, graph_pos) << data
459  << html.substr(graph_pos + graph_marker.size(), viz_pos-graph_pos-graph_marker.size())
460  << config.str() << html.substr(viz_pos + viz_marker.size());
461  output->flush();
462  casadi_assert(output->good(), "Failed to write graph to '" + fname + "'");
463 }
464 
465 std::string graph_extension(const std::string& fname) {
466  const auto dot = fname.find_last_of('.');
467  const std::string extension = dot == std::string::npos ? "" : fname.substr(dot);
468  casadi_assert(extension == ".html" || extension == ".dot" || extension == ".casadi_viz",
469  "Unsupported export_graph extension '" + extension
470  + "'. Supported extensions: .html, .dot, .casadi_viz");
471  return extension;
472 }
473 
474 // Browser bundles embed native serialization; the viewer decodes it with casadi-reader.
475 template<typename T>
476 std::string serialized_bundle(const T& data, const Dict& opts, const std::string& view) {
477  Dict defaults = opts;
478  if (!defaults.count("view")) defaults["view"] = view;
479  const GraphOptions options(defaults);
480  StringSerializer serializer;
481  serializer.pack(data);
482  std::ostringstream s;
483  s << "{\"format\":\"casadi_viz\",\"version\":1,\"source\":"
484  << graph_string(serializer.encode()) << ",\"view\":" << graph_string(options.view)
485  << ",\"direction\":" << graph_string(options.direction)
486  << ",\"casadi_version\":" << graph_string(CasadiMeta::version())
487  << ",\"include_functions\":" << (options.include_functions ? "true" : "false")
488  << ",\"show_matrix_contents\":" << (options.show_matrix_contents ? "true" : "false")
489  << ",\"show_matrix_sizes\":" << (options.show_matrix_sizes ? "true" : "false") << "}";
490  return s.str();
491 }
492 
493 template<typename MatType>
494 void export_expressions(const std::vector<MatType>& expressions, const std::string& fname,
495  const Dict& opts) {
496  if (graph_extension(fname) == ".dot") {
497  Dict defaults = opts;
498  if (!defaults.count("view")) defaults["view"] = "expression";
499  Function("expression", symvar(veccat(expressions)), expressions).export_graph(fname, defaults);
500  } else {
501  write_graph(fname, serialized_bundle(expressions, opts, "expression"), GraphOptions(opts));
502  }
503 }
504 
505 } // namespace
506 
507 std::string Function::export_graph(const Dict& opts) const {
508  return serialized_bundle(*this, opts, "function");
509 }
510 
511 void Function::export_graph(const std::string& fname, const Dict& opts) const {
512  const GraphOptions options(opts);
513  write_graph(fname, graph_extension(fname) == ".dot" ? graph_dot(*this, options)
514  : serialized_bundle(*this, opts, "function"), options);
515 }
516 
517 void export_graph(const SX& expression, const std::string& fname, const Dict& opts) {
518  export_expressions(std::vector<SX>{expression}, fname, opts);
519 }
520 
521 void export_graph(const std::vector<SX>& expressions, const std::string& fname, const Dict& opts) {
522  export_expressions(expressions, fname, opts);
523 }
524 
525 void export_graph(const MX& expression, const std::string& fname, const Dict& opts) {
526  export_expressions(std::vector<MX>{expression}, fname, opts);
527 }
528 
529 void export_graph(const std::vector<MX>& expressions, const std::string& fname, const Dict& opts) {
530  export_expressions(expressions, fname, opts);
531 }
532 
533 std::string export_graph(const std::vector<SX>& expressions, const Dict& opts) {
534  return serialized_bundle(expressions, opts, "expression");
535 }
536 
537 std::string export_graph(const SX& expression, const Dict& opts) {
538  return export_graph(std::vector<SX>{expression}, opts);
539 }
540 
541 std::string export_graph(const std::vector<MX>& expressions, const Dict& opts) {
542  return serialized_bundle(expressions, opts, "expression");
543 }
544 
545 std::string export_graph(const MX& expression, const Dict& opts) {
546  return export_graph(std::vector<MX>{expression}, opts);
547 }
548 
549 } // namespace casadi
static const char * version()
Obtain the version number of CasADi.
Definition: casadi_meta.cpp:30
static std::unique_ptr< std::ostream > ofstream_ptr(const std::string &path, std::ios_base::openmode mode=std::ios_base::out)
Definition: filesystem.cpp:115
static std::unique_ptr< std::istream > ifstream_ptr(const std::string &path, std::ios_base::openmode mode=std::ios_base::in, bool fail=true)
Definition: filesystem.cpp:135
void export_graph(const std::string &fname, const Dict &opts=Dict()) const
Export a Function graph as .html, .dot, or .casadi_viz.
Definition: visualizer.cpp:511
MX - Matrix expression.
Definition: mx.hpp:145
Sparse matrix class. SX and DM are specializations.
static Sparsity scalar(bool dense_scalar=true)
Create a 1-by-1 sparsity pattern.
Definition: sparsity.hpp:454
The casadi namespace.
Definition: archiver.cpp:28
std::string str(const T &v)
String representation, any type.
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
CASADI_EXPORT std::string export_graph(const MX &expression, const Dict &opts=Dict())
Export expressions as .html, .dot, or .casadi_viz, defaulting to expression view.
Definition: visualizer.cpp:545
T dot(const std::vector< T > &a, const std::vector< T > &b)
Matrix< double > DM
Definition: dm_fwd.hpp:33
std::vector< casadi_int > path(const std::vector< casadi_int > &map, casadi_int i_start)
@ OP_INV
Definition: calculus.hpp:73
@ OP_OUTPUT
Definition: calculus.hpp:82
@ OP_SETNONZEROS
Definition: calculus.hpp:163
@ OP_CONST
Definition: calculus.hpp:79
@ OP_TWICE
Definition: calculus.hpp:67
@ OP_INPUT
Definition: calculus.hpp:82
@ OP_SUB
Definition: calculus.hpp:65
@ OP_POW
Definition: calculus.hpp:66
@ OP_ADDNONZEROS
Definition: calculus.hpp:157
@ OP_PARAMETER
Definition: calculus.hpp:85
@ OP_CALL
Definition: calculus.hpp:88
@ OP_ADD
Definition: calculus.hpp:65
@ OP_DIV
Definition: calculus.hpp:65
@ OP_NEG
Definition: calculus.hpp:66
@ OP_CONSTPOW
Definition: calculus.hpp:66
@ OP_MUL
Definition: calculus.hpp:65
@ OP_SQ
Definition: calculus.hpp:67
@ OP_GETNONZEROS
Definition: calculus.hpp:151
static bool is_binary(unsigned char op)
Is binary operation?
Definition: calculus.hpp:1626
static std::string name(unsigned char op)
Definition: calculus.hpp:1679