graph_builder.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 #include "graph_builder_internal.hpp"
26 #include "onnx_function_impl.hpp"
27 #include <fstream>
28 
29 namespace casadi {
30 
31  // ---------- Node ----------
32 
33  void Node::disp(std::ostream& stream, bool more) const {
34  (void)more;
35  stream << io << " " << name << ": " << dtype << "[";
36  for (size_t k = 0; k < dimension.size(); ++k) {
37  if (k) stream << "x";
38  if (dimension[k] >= 0) stream << dimension[k];
39  else
40  stream << (dim_params[k].empty() ? "?" : dim_params[k]);
41  }
42  stream << "]";
43  if (baked) stream << " (baked)";
44  }
45 
46  std::string Node::get_str(bool more) const {
47  std::stringstream ss;
48  disp(ss, more);
49  return ss.str();
50  }
51 
52  // Infer the model format from a file suffix
53  static std::string format_from_path(const std::string& path) {
54  auto dot = path.find_last_of('.');
55  std::string suffix = dot == std::string::npos ? "" : path.substr(dot + 1);
56  if (suffix == "onnx") return "onnx";
57  casadi_error("GraphBuilder: cannot infer format from '" + path + "'");
58  }
59 
60  // ---------- public GraphBuilder ----------
61 
63  }
64 
65  GraphBuilder::GraphBuilder(const std::string& model_path, const Dict& opts) {
66  std::ifstream file(model_path, std::ios::binary | std::ios::ate);
67  casadi_assert(file.is_open(), "Cannot open model file: " + model_path);
68  std::streamsize size = file.tellg();
69  file.seekg(0, std::ios::beg);
70  std::vector<uint8_t> data(static_cast<size_t>(size));
71  casadi_assert(file.read(reinterpret_cast<char*>(data.data()), size),
72  "Cannot read model file: " + model_path);
73  own(new GraphBuilderInternal(model_path, data, format_from_path(model_path), opts));
74  }
75 
76  GraphBuilder::GraphBuilder(const Function& f, const Dict& opts) {
77  own(new GraphBuilderInternal(f.name(), f, opts));
78  }
79 
80  GraphBuilder::GraphBuilder(const std::string& name, const std::vector<uint8_t>& model_data,
81  const std::string& format, const Dict& opts) {
82  own(new GraphBuilderInternal(name, model_data, format, opts));
83  }
84 
86  return static_cast<GraphBuilderInternal*>(SharedObject::operator->());
87  }
89  return static_cast<const GraphBuilderInternal*>(SharedObject::operator->());
90  }
92  return static_cast<GraphBuilderInternal*>(SharedObject::get());
93  }
94 
95  const std::string& GraphBuilder::name() const {
96  static std::string null = "null";
97  return is_null() ? null : (*this)->name_;
98  }
99 
100  casadi_int GraphBuilder::n_in() const { return (*this)->n_in(); }
101  casadi_int GraphBuilder::n_out() const { return (*this)->n_out(); }
102  std::vector<std::string> GraphBuilder::name_in() const { return (*this)->name_in(); }
103  std::vector<std::string> GraphBuilder::name_out() const { return (*this)->name_out(); }
104  std::vector<casadi_int> GraphBuilder::dimension(const std::string& name) const {
105  return (*this)->node(name).dimension;
106  }
107  std::string GraphBuilder::dtype(const std::string& name) const {
108  return (*this)->node(name).dtype;
109  }
110  std::vector<std::string> GraphBuilder::dimension_param(const std::string& name) const {
111  return (*this)->node(name).dim_params;
112  }
113  std::vector<std::string> GraphBuilder::dynamic_params() const {
114  return (*this)->dynamic_params();
115  }
116 
117  void GraphBuilder::bind_dim(const std::string& param, casadi_int value) {
118  (*this)->bind_dim(param, value);
119  }
120  void GraphBuilder::bind_shape(const std::string& input_name,
121  const std::vector<casadi_int>& shape) {
122  (*this)->bind_shape(input_name, shape);
123  }
124  void GraphBuilder::set(const std::string& input_name, const std::vector<double>& value) {
125  (*this)->set_value(input_name, value);
126  }
127  void GraphBuilder::set(const std::string& input_name, double value) {
128  (*this)->set_value(input_name, std::vector<double>(1, value));
129  }
130 
131  Function GraphBuilder::create(const std::string& name,
132  const std::vector<std::string>& name_in,
133  const std::vector<std::string>& name_out,
134  const Dict& opts) const {
135  return (*this)->create_function(name, name_in, name_out, opts);
136  }
137  Function GraphBuilder::create(const std::string& name, const Dict& opts) const {
138  return (*this)->create_function(name, {}, {}, opts);
139  }
140  void GraphBuilder::export_onnx(const std::string& filename, const Dict& opts) {
141  (*this)->export_onnx(filename, opts);
142  }
143 
144  // ---------- GraphBuilderInternal ----------
145 
147  const std::vector<uint8_t>& model_data,
148  const std::string& format, const Dict& opts)
149  : name_(name), format_(format), model_data_(model_data) {
150  model_ = GraphModel(format, model_data, opts);
151  model_.fill_metadata(*this);
152  }
153 
154  GraphBuilderInternal::GraphBuilderInternal(const std::string& name, const Function& f,
155  const Dict& opts)
156  : name_(name), format_("onnx"), fun_(f) {
157  populate_from_function();
158  }
159 
161  }
162 
163  void GraphBuilderInternal::populate_from_function() {
164  nodes_.clear();
165  for (casadi_int i = 0; i < fun_.n_in(); ++i) {
166  Node n;
167  n.name = fun_.name_in(i);
168  n.io = "input";
169  n.dimension = {fun_.size1_in(i), fun_.size2_in(i)};
170  n.dim_params = {"", ""};
171  nodes_.push_back(n);
172  }
173  for (casadi_int i = 0; i < fun_.n_out(); ++i) {
174  Node n;
175  n.name = fun_.name_out(i);
176  n.io = "output";
177  n.dimension = {fun_.size1_out(i), fun_.size2_out(i)};
178  n.dim_params = {"", ""};
179  nodes_.push_back(n);
180  }
181  }
182 
183  std::vector<casadi_int> GraphBuilderInternal::resolved_shape(const Node& n) const {
184  if (n.io == "input") {
185  auto ov = input_shapes_.find(n.name);
186  if (ov != input_shapes_.end()) return ov->second; // explicit override
187  }
188  std::vector<casadi_int> shape;
189  for (size_t k = 0; k < n.dimension.size(); ++k) {
190  casadi_int d = n.dimension[k];
191  if (d < 0) { // dynamic: bound name else default 1
192  auto it = dim_bindings_.find(n.dim_params[k]);
193  d = (it != dim_bindings_.end()) ? it->second : 1;
194  }
195  shape.push_back(d);
196  }
197  return shape;
198  }
199 
200  const Node& GraphBuilderInternal::find(const std::string& name, const std::string& io) const {
201  for (const Node& n : nodes_) if (n.io == io && n.name == name) return n;
202  casadi_error("Graph tensor '" + name + "' (" + io + ") not found in model '" + name_ + "'");
203  }
204 
205  casadi_int GraphBuilderInternal::n_in() const {
206  casadi_int c = 0;
207  for (const Node& n : nodes_) if (n.io == "input") ++c;
208  return c;
209  }
210  casadi_int GraphBuilderInternal::n_out() const {
211  casadi_int c = 0;
212  for (const Node& n : nodes_) if (n.io == "output") ++c;
213  return c;
214  }
215  std::vector<std::string> GraphBuilderInternal::name_in() const {
216  std::vector<std::string> r;
217  for (const Node& n : nodes_) if (n.io == "input") r.push_back(n.name);
218  return r;
219  }
220  std::vector<std::string> GraphBuilderInternal::name_out() const {
221  std::vector<std::string> r;
222  for (const Node& n : nodes_) if (n.io == "output") r.push_back(n.name);
223  return r;
224  }
225  std::vector<casadi_int> GraphBuilderInternal::input_shape(const std::string& name) const {
226  return find(name, "input").dimension;
227  }
228  std::vector<casadi_int> GraphBuilderInternal::output_shape(const std::string& name) const {
229  return find(name, "output").dimension;
230  }
231 
232  std::vector<std::string> GraphBuilderInternal::dynamic_params() const {
233  std::vector<std::string> r;
234  for (const Node& n : nodes_) {
235  for (size_t k = 0; k < n.dimension.size(); ++k) {
236  if (n.dimension[k] < 0 && !n.dim_params[k].empty() &&
237  std::find(r.begin(), r.end(), n.dim_params[k]) == r.end()) {
238  r.push_back(n.dim_params[k]);
239  }
240  }
241  }
242  return r;
243  }
244 
245  Node GraphBuilderInternal::node(const std::string& name) const {
246  for (const Node& n : nodes_) if (n.name == name) return n;
247  casadi_error("Graph tensor '" + name + "' not found in model '" + name_ + "'");
248  }
249 
250  void GraphBuilderInternal::set_value(const std::string& input_name,
251  const std::vector<double>& value) {
252  find(input_name, "input"); // validate the name
253  input_values_[input_name] = value;
254  for (Node& n : nodes_) if (n.io == "input" && n.name == input_name) {
255  n.value = value;
256  n.baked = true;
257  }
258  }
259 
260  void GraphBuilderInternal::bind_shape(const std::string& input_name,
261  const std::vector<casadi_int>& shape) {
262  const Node& t = find(input_name, "input");
263  casadi_assert(shape.size() == t.dimension.size(),
264  "bind_shape: rank mismatch for '" + input_name + "'");
265  input_shapes_[input_name] = shape;
266  // Pinning a named dynamic axis also binds that dim everywhere it appears (e.g. outputs)
267  for (size_t k = 0; k < t.dimension.size(); ++k) {
268  if (t.dimension[k] < 0 && !t.dim_params[k].empty()) dim_bindings_[t.dim_params[k]] = shape[k];
269  }
270  }
271 
273  const std::vector<std::string>& inputs,
274  const std::vector<std::string>& outputs,
275  const Dict& opts) const {
276  bool symbolic = false;
277  std::string backend = "ort";
278  Dict o;
279  for (auto&& op : opts) {
280  if (op.first == "symbolic") symbolic = op.second;
281  else if (op.first == "backend") backend = op.second.to_string();
282  else
283  o[op.first] = op.second;
284  }
285 
286  if (symbolic) {
287  casadi_assert(!model_.is_null(),
288  "GraphBuilder: symbolic create requires a parsed model (build from a file)");
289  return model_.import_symbolic(*this, name);
290  }
291 
292  // Numeric path: OnnxFunction freezes a snapshot directly from this builder's config
293  casadi_assert(!model_data_.empty(), "GraphBuilder: numeric create requires model bytes");
294  return OnnxFunction::create(backend, name, this, inputs, outputs, o);
295  }
296 
297  void GraphBuilderInternal::export_onnx(const std::string& filename, const Dict& opts) {
298  std::vector<uint8_t> bytes;
299  if (!fun_.is_null()) {
300  GraphModel gm(format_);
301  bytes = gm.export_symbolic(fun_, opts);
302  } else {
303  casadi_assert(!model_data_.empty(), "GraphBuilder: nothing to export");
304  bytes = model_data_;
305  }
306  std::ofstream out(filename, std::ios::binary);
307  casadi_assert(out.good(), "Cannot open output file: " + filename);
308  out.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
309  }
310 
311  void GraphBuilderInternal::disp(std::ostream& stream, bool more) const {
312  stream << "GraphBuilder '" << name_ << "': " << n_in() << " input(s), "
313  << n_out() << " output(s)";
314  if (!more) return;
315  stream << "\nInputs:";
316  for (const Node& n : nodes_) if (n.io == "input") stream << "\n " << n.get_str();
317  stream << "\nOutputs:";
318  for (const Node& n : nodes_) if (n.io == "output") stream << "\n " << n.get_str();
319  std::vector<std::string> dp = dynamic_params();
320  if (!dp.empty()) {
321  stream << "\nDynamic dimensions:";
322  for (const std::string& p : dp) stream << " " << p;
323  }
324  }
325 
326 } // namespace casadi
Function object.
Definition: function.hpp:60
casadi_int size2_out(casadi_int ind) const
Get output dimension.
Definition: function.cpp:991
casadi_int size1_in(casadi_int ind) const
Get input dimension.
Definition: function.cpp:979
const std::vector< std::string > & name_in() const
Get input scheme.
Definition: function.cpp:1113
const std::string & name() const
Name of the function.
Definition: function.cpp:1504
casadi_int n_out() const
Get the number of function outputs.
Definition: function.cpp:975
casadi_int n_in() const
Get the number of function inputs.
Definition: function.cpp:971
casadi_int size1_out(casadi_int ind) const
Get output dimension.
Definition: function.cpp:987
casadi_int size2_in(casadi_int ind) const
Get input dimension.
Definition: function.cpp:983
const std::vector< std::string > & name_out() const
Get output scheme.
Definition: function.cpp:1117
SharedObjectInternal * get() const
Get a const pointer to the node.
SharedObjectInternal * operator->() const
Access a member function or object.
Internal class for GraphBuilder.
void export_onnx(const std::string &filename, const Dict &opts)
void bind_shape(const std::string &input_name, const std::vector< casadi_int > &shape)
std::map< std::string, std::vector< double > > input_values_
std::vector< Node > nodes_
Tensor metadata (inputs followed by outputs)
Function fun_
Source Function (export lifecycle); null when built from a model.
const Node & find(const std::string &name, const std::string &io) const
Locate a node by name in a given I/O role (throws if absent)
Node node(const std::string &name) const
std::vector< casadi_int > resolved_shape(const Node &n) const
std::vector< std::string > name_out() const
void set_value(const std::string &input_name, const std::vector< double > &value)
std::map< std::string, casadi_int > dim_bindings_
Pending configuration carried into create()
std::map< std::string, std::vector< casadi_int > > input_shapes_
GraphModel model_
Parsed model backend (import lifecycle); null when built from a Function.
GraphBuilderInternal(const std::string &name, const std::vector< uint8_t > &model_data, const std::string &format, const Dict &opts)
Construct from parsed model bytes of a given format.
std::vector< casadi_int > input_shape(const std::string &name) const
std::vector< casadi_int > output_shape(const std::string &name) const
std::vector< std::string > dynamic_params() const
std::vector< std::string > name_in() const
void disp(std::ostream &stream, bool more) const override
Print a description of the object.
Function create_function(const std::string &name, const std::vector< std::string > &name_in, const std::vector< std::string > &name_out, const Dict &opts) const
GraphBuilder()
Default constructor.
std::vector< std::string > dynamic_params() const
Names of the symbolic/dynamic dimensions in the model.
std::string dtype(const std::string &name) const
Element type name of a tensor (input or output) by name (FLOAT, INT64, ...)
GraphBuilderInternal * operator->()
void bind_dim(const std::string &param, casadi_int value)
Bind a symbolic/dynamic dimension to a concrete size.
casadi_int n_in() const
Declared shape of a tensor (input or output) by name; -1 for dynamic dimensions.
std::vector< std::string > name_in() const
Declared shape of a tensor (input or output) by name; -1 for dynamic dimensions.
Function create() const
Freeze into an evaluable Function, default naming.
std::vector< casadi_int > dimension(const std::string &name) const
Declared shape of a tensor (input or output) by name; -1 for dynamic dimensions.
void set(const std::string &input_name, const std::vector< double > &value)
Bake a fixed value into an input; it is fed at create() and not exposed as a Function input.
void bind_shape(const std::string &input_name, const std::vector< casadi_int > &shape)
Pin the full shape of an input.
GraphBuilderInternal * get() const
const std::string & name() const
Name of the model.
casadi_int n_out() const
Declared shape of a tensor (input or output) by name; -1 for dynamic dimensions.
void export_onnx(const std::string &filename, const Dict &opts=Dict())
Export to an ONNX model file.
std::vector< std::string > name_out() const
Declared shape of a tensor (input or output) by name; -1 for dynamic dimensions.
std::vector< std::string > dimension_param(const std::string &name) const
Per-axis symbolic dimension name of a tensor by name ("" for static axes)
Format-agnostic handle to a parsed computational-graph model.
std::vector< uint8_t > export_symbolic(const Function &f, const Dict &opts=Dict())
Symbolic export: serialize a CasADi Function to model bytes.
Definition: graph_model.cpp:62
void fill_metadata(GraphBuilderInternal &gb) const
Populate a builder's Node metadata from the parsed model.
Definition: graph_model.cpp:53
Function import_symbolic(const GraphBuilderInternal &gb, const std::string &name) const
Symbolic import: rebuild the graph as a CasADi Function.
Definition: graph_model.cpp:56
static Function create(const std::string &solver, const std::string &name, const GraphBuilderInternal *gb, const std::vector< std::string > &inputs, const std::vector< std::string > &outputs, const Dict &opts)
Plugin factory.
The casadi namespace.
Definition: archiver.cpp:28
static std::string format_from_path(const std::string &path)
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
T dot(const std::vector< T > &a, const std::vector< T > &b)
std::vector< casadi_int > path(const std::vector< casadi_int > &map, casadi_int i_start)
std::string filename(const std::string &path)
Definition: ghc.cpp:55
Metadata for one graph tensor (graph input or output)
void disp(std::ostream &stream, bool more=false) const
std::string get_str(bool more=false) const
std::vector< casadi_int > dimension
Declared shape, -1 for dynamic dimensions.
std::string name
Tensor name.
std::string io
"input" or "output"
std::string dtype
Element type name (FLOAT, INT64, ...)
std::vector< std::string > dim_params
Symbolic name per axis ("" if static)
bool baked
True if a fixed value was set() for this input.