casadi_call.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 "casadi_call.hpp"
27 #include "function_internal.hpp"
28 #include "casadi_misc.hpp"
29 #include "serializing_stream.hpp"
30 
31 #define CASADI_THROW_ERROR(FNAME, WHAT) \
32 throw CasadiException("Error in Call::" FNAME " for '" + fcn_.name() + "' "\
33  "[" + fcn_.class_name() + "] at " + CASADI_WHERE + ":\n" + std::string(WHAT));
34 
35 namespace casadi {
36 
37  MX Call::projectArg(const MX& x, const Sparsity& sp, casadi_int i) {
38  if (x.size()==sp.size()) {
39  // Insert sparsity projection nodes if needed
40  return project(x, sp);
41  } else {
42  // Different dimensions
43  if (x.is_empty() || sp.is_empty()) { // NOTE: To permissive?
44  // Replace nulls with zeros of the right dimension
45  return MX::zeros(sp);
46  } else if (x.is_scalar()) {
47  // Scalar argument means set all
48  return MX(sp, x);
49  } else if (x.size1()==sp.size2() && x.size2()==sp.size1() && sp.is_vector()) {
50  // Transposed vector
51  return projectArg(x.T(), sp, i);
52  } else {
53  // Mismatching dimensions
54  casadi_error("Cannot create function call node: Dimension mismatch for argument "
55  + str(i) + ". Argument has shape " + str(x.size())
56  + " but function input has shape " + str(sp.size()));
57  }
58  }
59  }
60 
61  MX Call::get_output(casadi_int oind) const {
62  MX this_ = shared_from_this<MX>();
63  // No need for an OutputNode if sparsity is fully sparse
64  if (this_->sparsity(oind).nnz()==0) return MX(this_->sparsity(oind));
65  MX ret;
66  if (!cache_.incache(oind, ret)) {
67  ret = MX::create(new OutputNode(this_, oind));
68  cache_.tocache_if_missing(oind, ret);
69  }
70  return ret;
71  }
72 
73  Call::Call(const Function& fcn, const std::vector<MX>& arg) : fcn_(fcn) {
74 
75  // Number inputs and outputs
76  casadi_int num_in = fcn.n_in();
77  casadi_assert(arg.size()==num_in, "Argument list length (" + str(arg.size())
78  + ") does not match number of inputs (" + str(num_in)
79  + ") for function " + fcn.name());
80 
81  // Create arguments of the right dimensions and sparsity
82  std::vector<MX> arg1(num_in);
83  for (casadi_int i=0; i<num_in; ++i) {
84  arg1[i] = projectArg(arg[i], fcn_.sparsity_in(i), i);
85  }
86  set_dep(arg1);
88  }
89 
90  std::string Call::disp(const std::vector<std::string>& arg) const {
91  std::stringstream ss;
92  ss << fcn_.name() << "(";
93  for (casadi_int i=0; i<n_dep(); ++i) {
94  if (i!=0) ss << ", ";
95  ss << arg.at(i);
96  }
97  ss << ")";
98  return ss.str();
99  }
100 
101  int Call::eval(const double** arg, double** res, casadi_int* iw, double* w) const {
102  return fcn_(arg, res, iw, w);
103  }
104 
105  casadi_int Call::nout() const {
106  return fcn_.n_out();
107  }
108 
109  const Sparsity& Call::sparsity(casadi_int oind) const {
110  return fcn_.sparsity_out(oind);
111  }
112 
113  int Call::eval_sx(const SXElem** arg, SXElem** res, casadi_int* iw, SXElem* w) const {
114  return fcn_(arg, res, iw, w);
115  }
116 
117  void Call::eval_mx(const std::vector<MX>& arg, std::vector<MX>& res,
118  const std::vector<bool>& unique) const {
119  res = create(fcn_, arg);
120  }
121 
122  void Call::ad_forward(const std::vector<std::vector<MX>>& fseed,
123  std::vector<std::vector<MX>>& fsens) const {
124  try {
125  // Nondifferentiated inputs and outputs
126  std::vector<MX> arg(n_dep());
127  for (casadi_int i=0; i<arg.size(); ++i) arg[i] = dep(i);
128  std::vector<MX> res(nout());
129  for (casadi_int i=0; i<res.size(); ++i) res[i] = get_output(i);
130 
131  // Call the cached functions
132  fcn_->call_forward(arg, res, fseed, fsens, false, false);
133  } catch (std::exception& e) {
134  CASADI_THROW_ERROR("ad_forward", e.what());
135  }
136  }
137 
138  void Call::ad_reverse(const std::vector<std::vector<MX>>& aseed,
139  std::vector<std::vector<MX>>& asens) const {
140  try {
141  // Find a common conditional argument among the seeds, if any
142  MX cond = common_cond(aseed);
143  // Nondifferentiated inputs and outputs
144  std::vector<MX> arg(n_dep());
145  for (casadi_int i=0; i<arg.size(); ++i) arg[i] = dep(i);
146  std::vector<MX> res(nout());
147  for (casadi_int i=0; i<res.size(); ++i) res[i] = get_output(i);
148  // Call the cached functions
149  std::vector<std::vector<MX>> v;
150  fcn_->call_reverse(arg, res, aseed, v, false, false);
151  for (casadi_int i=0; i<v.size(); ++i) {
152  for (casadi_int j=0; j<v[i].size(); ++j) {
153  // Skip structurally zero contributions (necessary?)
154  if (v[i][j].is_empty()) continue;
155  // Prevent propagation of NaNs through if/else
156  if (!cond.is_empty()) v[i][j] = if_else(cond, v[i][j], 0);
157  // Add seeds
158  asens[i][j] += v[i][j];
159  }
160  }
161  } catch (std::exception& e) {
162  CASADI_THROW_ERROR("ad_reverse", e.what());
163  }
164  }
165 
166  int Call::sp_forward(const bvec_t** arg, bvec_t** res, casadi_int* iw, bvec_t* w) const {
167  return fcn_(arg, res, iw, w);
168  }
169 
170  int Call::eval_activity(const bvec_t** arg, bvec_t** res, casadi_int* iw, bvec_t* w) const {
171  return fcn_.eval_activity(arg, res, iw, w);
172  }
173 
174  int Call::sp_reverse(bvec_t** arg, bvec_t** res, casadi_int* iw, bvec_t* w) const {
175  return fcn_.rev(arg, res, iw, w);
176  }
177 
179  g.add_dependency(fcn_);
180  }
181 
183  const std::vector<casadi_int>& arg,
184  const std::vector<casadi_int>& res,
185  const std::vector<bool>& arg_is_ref,
186  std::vector<bool>& res_is_ref) const {
187  // Collect input arguments
188  g.local("arg1", "const casadi_real", "**");
189  for (casadi_int i=0; i<arg.size(); ++i) {
190  g << "arg1[" << i << "]=" << g.work(arg[i], fcn_.nnz_in(i), arg_is_ref[i]) << ";\n";
191  }
192 
193  // Collect output arguments
194  g.local("res1", "casadi_real", "**");
195  for (casadi_int i=0; i<res.size(); ++i) {
196  g << "res1[" << i << "]=" << g.work(res[i], fcn_.nnz_out(i), false) << ";\n";
197  }
198 
199  // Call function
200  std::string flag = g(fcn_, "arg1", "res1", "iw", "w");
201  g << "if (" << flag << ") return 1;\n";
202  }
203 
204  size_t Call::sz_arg() const {
205  return fcn_.sz_arg();
206  }
207 
208  size_t Call::sz_res() const {
209  return fcn_.sz_res();
210  }
211 
212  size_t Call::sz_iw() const {
213  return fcn_.sz_iw();
214  }
215 
216  size_t Call::sz_w() const {
217  return fcn_.sz_w();
218  }
219 
220  std::vector<MX> Call::create(const Function& fcn, const std::vector<MX>& arg) {
221  // issue #3019: if every output is invariably zero given the (partly zero) inputs,
222  // drop the call entirely and emit structural zeros. Only whole-call removal is done:
223  // redirecting *some* outputs of a surviving call to structural zeros injects zeros
224  // that inlining re-propagates asymmetrically, breaking build/inline idempotency
225  // (ad.py test_MX). Gate on a zero input first (else no annihilation is possible).
226  bool any_zero_in = false;
227  for (const MX& a : arg) if (a.is_zero()) { any_zero_in = true; break; }
228  if (any_zero_in) {
229  std::vector<bool> mask;
230  mask.reserve(fcn.nnz_in());
231  for (casadi_int i=0; i<fcn.n_in(); ++i)
232  mask.insert(mask.end(), fcn.nnz_in(i), !arg[i].is_zero());
233  std::vector<bool> onz = fcn.activity(mask);
234  bool all_out_zero = true;
235  for (bool active : onz) if (active) { all_out_zero = false; break; }
236  if (all_out_zero) {
237  std::vector<MX> ret(fcn.n_out());
238  for (casadi_int i=0; i<fcn.n_out(); ++i) ret[i] = MX(fcn.size1_out(i), fcn.size2_out(i));
239  return ret;
240  }
241  }
242  return MX::createMultipleOutput(new Call(fcn, arg));
243  }
244 
245  MX Call::create_call(const Function& fcn, const std::vector<MX>& arg) {
246  return MX::create(new Call(fcn, arg));
247  }
248 
251  s.pack("Call::fcn", fcn_);
252  }
253 
255  s.unpack("Call::fcn", fcn_);
256  }
257 
258  MX Call::common_cond(const std::vector<std::vector<MX> >& seed) {
259  // Check if all seeds are conditional with the same seed
260  MX c;
261  for (const std::vector<MX>& seed_dir : seed) {
262  for (const MX& s : seed_dir) {
263  // Skip zero seeds
264  if (s.is_zero()) continue;
265  // If not a conditional, no common condition
266  if (!s.is_op(OP_IF_ELSE_ZERO)) return MX();
267  // Get conditional
268  MX c1 = s.dep(0);
269  // Has c already been set
270  if (c.is_empty(true)) {
271  // First time encountered
272  c = c1;
273  } else if (!MX::is_equal(c, c1)) {
274  // Different conditionals
275  return MX();
276  }
277  }
278  }
279  return c;
280  }
281 
282 } // namespace casadi
static std::vector< MX > create(const Function &fcn, const std::vector< MX > &arg)
Create function call node.
Call(const Function &fcn, const std::vector< MX > &arg)
Constructor (should not be used directly)
Definition: casadi_call.cpp:73
WeakCache< casadi_int, MX > cache_
Output node cache.
int sp_reverse(bvec_t **arg, bvec_t **res, casadi_int *iw, bvec_t *w) const override
Propagate sparsity backwards.
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
size_t sz_w() const override
Get required length of w field.
MX get_output(casadi_int oind) const override
Get an output.
Definition: casadi_call.cpp:61
int eval(const double **arg, double **res, casadi_int *iw, double *w) const override
Evaluate the function numerically.
Function fcn_
void generate(CodeGenerator &g, const std::vector< casadi_int > &arg, const std::vector< casadi_int > &res, const std::vector< bool > &arg_is_ref, std::vector< bool > &res_is_ref) const override
Generate code for the operation.
int eval_activity(const bvec_t **arg, bvec_t **res, casadi_int *iw, bvec_t *w) const override
Propagate signal activity forward (recurses into the callee)
size_t sz_res() const override
Get required length of res field.
static MX create_call(const Function &fcn, const std::vector< MX > &arg)
Create function call node.
void ad_forward(const std::vector< std::vector< MX > > &fseed, std::vector< std::vector< MX > > &fsens) const override
Calculate forward mode directional derivatives.
size_t sz_iw() const override
Get required length of iw field.
int sp_forward(const bvec_t **arg, bvec_t **res, casadi_int *iw, bvec_t *w) const override
Propagate sparsity forward.
size_t sz_arg() const override
Get required length of arg field.
void eval_mx(const std::vector< MX > &arg, std::vector< MX > &res, const std::vector< bool > &unique={}) const override
Evaluate symbolically (MX)
casadi_int nout() const override
Number of outputs.
void add_dependency(CodeGenerator &g) const override
Add a dependent function.
void ad_reverse(const std::vector< std::vector< MX > > &aseed, std::vector< std::vector< MX > > &asens) const override
Calculate reverse mode directional derivatives.
static MX projectArg(const MX &x, const Sparsity &sp, casadi_int i)
Project a function input to a particular sparsity.
Definition: casadi_call.cpp:37
std::string disp(const std::vector< std::string > &arg) const override
Print expression.
Definition: casadi_call.cpp:90
static MX common_cond(const std::vector< std::vector< MX >> &seed)
Find a common conditional argument for all seeds.
int eval_sx(const SXElem **arg, SXElem **res, casadi_int *iw, SXElem *w) const override
Evaluate the function symbolically (SX)
Helper class for C code generation.
std::string add_dependency(const Function &f)
Add a function dependency.
std::string work(casadi_int n, casadi_int sz, bool is_ref) const
void local(const std::string &name, const std::string &type, const std::string &ref="")
Declare a local variable.
Helper class for Serialization.
void unpack(Sparsity &e)
Reconstruct an object from the input stream.
virtual void call_forward(const std::vector< MX > &arg, const std::vector< MX > &res, const std::vector< std::vector< MX > > &fseed, std::vector< std::vector< MX > > &fsens, bool always_inline, bool never_inline) const
Forward mode AD, virtual functions overloaded in derived classes.
virtual void call_reverse(const std::vector< MX > &arg, const std::vector< MX > &res, const std::vector< std::vector< MX > > &aseed, std::vector< std::vector< MX > > &asens, bool always_inline, bool never_inline) const
Reverse mode, virtual functions overloaded in derived classes.
Function object.
Definition: function.hpp:60
casadi_int nnz_out() const
Get number of output nonzeros.
Definition: function.cpp:1007
size_t sz_res() const
Get required length of res field.
Definition: function.cpp:1237
casadi_int size2_out(casadi_int ind) const
Get output dimension.
Definition: function.cpp:991
const Sparsity & sparsity_out(casadi_int ind) const
Get sparsity of a given output.
Definition: function.cpp:1183
const std::string & name() const
Name of the function.
Definition: function.cpp:1504
int rev(bvec_t **arg, bvec_t **res, casadi_int *iw, bvec_t *w, int mem=0) const
Propagate sparsity backward.
Definition: function.cpp:1252
const Sparsity & sparsity_in(casadi_int ind) const
Get sparsity of a given input.
Definition: function.cpp:1167
size_t sz_iw() const
Get required length of iw field.
Definition: function.cpp:1239
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
size_t sz_w() const
Get required length of w field.
Definition: function.cpp:1241
size_t sz_arg() const
Get required length of arg field.
Definition: function.cpp:1235
casadi_int nnz_in() const
Get number of input nonzeros.
Definition: function.cpp:1003
casadi_int size1_out(casadi_int ind) const
Get output dimension.
Definition: function.cpp:987
int eval_activity(const bvec_t **arg, bvec_t **res, casadi_int *iw, bvec_t *w, int mem=0) const
Propagate signal activity forward (bit set = active (possibly nonzero))
Definition: function.cpp:1260
std::vector< bool > activity(const std::vector< bool > &arg) const
Output signal activity induced by a given input activity.
Definition: function.cpp:1269
bool is_empty(bool both=false) const
Check if the sparsity is empty, i.e. if one of the dimensions is zero.
std::pair< casadi_int, casadi_int > size() const
Get the shape.
casadi_int size2() const
Get the second dimension (i.e. number of columns)
casadi_int size1() const
Get the first dimension (i.e. number of rows)
static MX zeros(casadi_int nrow=1, casadi_int ncol=1)
Create a dense matrix or a matrix with specified sparsity with all entries zero.
bool is_scalar(bool scalar_and_dense=false) const
Check if the matrix expression is scalar.
friend class MX
Definition: mx_node.hpp:52
const Sparsity & sparsity() const
Get the sparsity.
Definition: mx_node.hpp:410
const MX & dep(casadi_int ind=0) const
dependencies - functions that have to be evaluated before this one
Definition: mx_node.hpp:392
casadi_int n_dep() const
Number of dependencies.
Definition: mx_node.cpp:208
virtual void serialize_body(SerializingStream &s) const
Serialize an object without type information.
Definition: mx_node.cpp:530
void set_sparsity(const Sparsity &sparsity)
Set the sparsity.
Definition: mx_node.cpp:224
void set_dep(const MX &dep)
Set unary dependency.
Definition: mx_node.cpp:228
MX - Matrix expression.
Definition: mx.hpp:92
static MX create(MXNode *node)
Create from node.
Definition: mx.cpp:69
static bool is_equal(const MX &x, const MX &y, casadi_int depth=0)
Definition: mx.cpp:867
MX T() const
Transpose the matrix.
Definition: mx.cpp:1095
static std::vector< MX > createMultipleOutput(MXNode *node)
Create from node (multiple-outputs)
Definition: mx.cpp:130
MX dep(casadi_int ch=0) const
Get the nth dependency as MX.
Definition: mx.cpp:783
The basic scalar symbolic class of CasADi.
Definition: sx_elem.hpp:75
Helper class for Serialization.
void pack(const Sparsity &e)
Serializes an object to the output stream.
General sparsity class.
Definition: sparsity.hpp:106
bool is_vector() const
Check if the pattern is a row or column vector.
Definition: sparsity.cpp:289
casadi_int size1() const
Get the number of rows.
Definition: sparsity.cpp:124
casadi_int nnz() const
Get the number of (structural) non-zeros.
Definition: sparsity.cpp:148
casadi_int size2() const
Get the number of columns.
Definition: sparsity.cpp:128
std::pair< casadi_int, casadi_int > size() const
Get the shape.
Definition: sparsity.cpp:152
static Sparsity scalar(bool dense_scalar=true)
Create a scalar sparsity pattern *.
Definition: sparsity.hpp:153
bool is_empty(bool both=false) const
Check if the sparsity is empty.
Definition: sparsity.cpp:144
The casadi namespace.
Definition: archiver.cpp:28
unsigned long long bvec_t
double if_else(double x, double y, double z)
Definition: calculus.hpp:296
std::string str(const T &v)
String representation, any type.
@ OP_IF_ELSE_ZERO
Definition: calculus.hpp:71