nlp_tools.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 "nlp_tools.hpp"
27 
28 #include "casadi_misc.hpp"
29 
30 namespace casadi {
31 
32 template <class T>
33 void detect_simple_bounds_gen(const T& x, const T& p,
34  const T& g, const T& lbg, const T& ubg,
35  std::vector<casadi_int>& gi,
36  T& lbx, T& ubx,
37  Function& lam_forward,
38  Function& lam_backward) {
39 
40  // Read dimensions
41  casadi_int ng = g.size1();
42  casadi_int nx = x.size1();
43 
44  casadi_assert(lbg.size1()==ng, "Dimension mismatch");
45  casadi_assert(ubg.size1()==ng, "Dimension mismatch");
46  casadi_assert(g.is_column(), "Dimension mismatch");
47  casadi_assert(lbg.is_column(), "Dimension mismatch");
48  casadi_assert(ubg.is_column(), "Dimension mismatch");
49  casadi_assert(x.is_column(), "Dimension mismatch");
50 
51  // Get constraint Jacobian sparsity
52  Function temp("temp_detect_simple_bounds_gen", {x, p}, {g});
53  Sparsity sp = temp.jac_sparsity().at(0);
54  Sparsity spT = sp.T();
55 
56  // Reset result vector
57  std::vector<bool> is_simple(ng, true);
58 
59  // Check nonlinearity
60  std::vector<bool> is_nonlin = which_depends(g, x, 2, true);
61 
62  const casadi_int* row = spT.colind();
63  for (casadi_int i=0;i<ng;++i) {
64  // Check if each row of jac_g_x only depends on one column
65  bool single_dependency = row[i+1]-row[i]==1;
66  is_simple[i] = single_dependency && !is_nonlin[i];
67  }
68 
69  // Full-indices of all simple constraints
70  std::vector<casadi_int> sgi = boolvec_to_index(is_simple);
71  gi = boolvec_to_index(boolvec_not(is_simple));
72  T g_bounds = g(sgi);
73  std::vector<casadi_int> sgi_to_sgsi = lookupvector(sgi, ng);
74 
75  // Detect f2(p)x+f1(p)==0
76  Function gf = Function("gf", std::vector<T>{x, p},
77  std::vector<T>{g_bounds, jtimes(g_bounds, x, T::ones(nx, 1))});
78 
79  std::vector<T> res;
80  gf.call(std::vector<T>{0, p}, res, true, false);
81  T f1 = res[0];
82  T f2 = res[1];
83 
84  T f2_inv = 1/f2;
85 
86  T lb_pre = (lbg(sgi)-f1)*f2_inv;
87  T ub_pre = (ubg(sgi)-f1)*f2_inv;
88 
89  T pos_f2 = f2>=0;
90  T neg_f2 = !pos_f2;
91 
92  T lb = lb_pre*pos_f2+ub_pre*neg_f2;
93  T ub = ub_pre*pos_f2+lb_pre*neg_f2;
94 
95 
96 
97  // Start without simple bounds
98  lbx = -T::inf(nx);
99  ubx = T::inf(nx);
100 
101  const casadi_int* xi = spT.row();
102 
103 
104  /* We will process in groups
105  * (=collection of bounds that have been re-defined a certain number of times)
106  * instead of element-wise to keep the MX graph size minimal.
107  */
108 
109  std::vector< std::vector<casadi_int> > sgsi_groups, sgi_groups, sxi_groups;
110  casadi_int n_groups = 0;
111 
112  // How often has a certain variable been encountered?
113  std::vector<casadi_int> group_count(nx);
114 
115  // Loop over all constraints
116  for (casadi_int i=0;i<ng;++i) {
117  // Only treat simple ones
118  if (!is_simple[i]) continue;
119 
120  casadi_int j = xi[row[i]];
121  casadi_int& k = group_count[j];
122  // Grow as needed
123  if (k==n_groups) {
124  sgsi_groups.emplace_back();
125  sgi_groups.emplace_back();
126  sxi_groups.emplace_back();
127  n_groups++;
128  }
129  sxi_groups[k].push_back(j);
130  sgi_groups[k].push_back(i);
131  sgsi_groups[k].push_back(sgi_to_sgsi[i]);
132  k++;
133  }
134 
135  casadi_assert(n_groups>=1, "mismatch");
136  // Take min/max group-wise to determine simple bounds
137  for (casadi_int i=0;i<n_groups;++i) {
138  lbx(sxi_groups[i]) = fmax(lbx(sxi_groups[i]), lb(sgsi_groups[i]));
139  ubx(sxi_groups[i]) = fmin(ubx(sxi_groups[i]), ub(sgsi_groups[i]));
140  }
141 
142 
143  // Choose the first group as reference
144  const std::vector<casadi_int>& sxi_ref = sxi_groups[0];
145  // Indices into the first group that reproduce the contents of other groups
146  std::vector< std::vector<casadi_int> > xsub(n_groups);
147  // Identity map for first entry of xsub
148  xsub[0] = range(sxi_ref.size());
149 
150  // Computations for other xsub entries
151  std::vector<casadi_int> lookup = lookupvector(sxi_ref, nx);
152  for (casadi_int i=1;i<n_groups;++i) {
153  const std::vector<casadi_int>& sxi = sxi_groups[i];
154  for (casadi_int j=0;j<sxi.size();++j) {
155  xsub[i].push_back(lookup[sxi[j]]);
156  }
157  }
158 
159  // Determine multiplier maps
160 
161  // Symbols
162  T lam_g_forward = T::sym("lam_g", ng);
163  T lam_sg_backward = T::sym("lam_g", gi.size());
164  T lam_x_backward = T::sym("lam_x", nx);
165 
166 
167  T lam_sg_forward = lam_g_forward(sgi); // NOLINT(cppcoreguidelines-slicing)
168  T lam_x_forward = T::zeros(nx, 1);
169  T lam_g_backward = T::zeros(ng, 1);
170  lam_g_backward(gi) = lam_sg_backward;
171 
172  // Comparison expression per group
173  std::vector<T> lcomp, ucomp;
174  for (casadi_int i=0;i<n_groups;++i) {
175  lcomp.push_back(lbx(sxi_groups[i])==lb(sgsi_groups[i]));
176  ucomp.push_back(ubx(sxi_groups[i])==ub(sgsi_groups[i]));
177  }
178 
179  // How many lb/ub are active?
180  T count_lb = T::zeros(nx);
181  T count_ub = T::zeros(nx);
182  for (casadi_int i=0;i<n_groups;++i) {
183  count_lb(sxi_groups[i]) += lcomp[i];
184  count_ub(sxi_groups[i]) += ucomp[i];
185  }
186 
187  // Compute lam_x from lam_g
188  for (casadi_int i=0;i<n_groups;++i) {
189  T l = lam_sg_forward(sgsi_groups[i]); // NOLINT(cppcoreguidelines-slicing)
190  T lt = (l<0);
191  T gt = !lt;
192  lam_x_forward(sxi_groups[i]) += l*(lt*lcomp[i]+gt*ucomp[i]);
193  }
194 
195  // Compute lam_g from lam_x
196  T l = lam_x_backward(sxi_ref); // NOLINT(cppcoreguidelines-slicing)
197  T lt = (l<0);
198  T gt = !lt;
199  T lam_xl = l*lt/count_lb(sxi_ref);
200  T lam_xu = l*gt/count_ub(sxi_ref);
201 
202  for (casadi_int i=0;i<n_groups;++i) {
203  lam_g_backward(sgi_groups[i]) = lam_xl(xsub[i])*lcomp[i]+lam_xu(xsub[i])*ucomp[i];
204  }
205 
206  // Construct Functions for mappings
207  lam_forward = Function("lam_forward",
208  {lam_g_forward, p}, {lam_g_forward(gi), lam_x_forward},// NOLINT(cppcoreguidelines-slicing)
209  {"lam_g", "p"}, {"lam_sg", "lam_x"});
210  casadi_assert_dev(!lam_forward.has_free());
211  lam_backward = Function("lam_backward",
212  {lam_sg_backward, lam_x_backward, p}, {lam_g_backward},
213  {"lam_sg", "lam_x", "p"}, {"lam_g"});
214  casadi_assert_dev(!lam_backward.has_free());
215  }
216 
217 void detect_simple_bounds(const SX& x, const SX& p,
218  const SX& g, const SX& lbg, const SX& ubg,
219  std::vector<casadi_int>& gi,
220  SX& lbx, SX& ubx,
221  Function& lam_forward,
222  Function& lam_backward) {
223  detect_simple_bounds_gen(x, p, g, lbg, ubg,
224  gi, lbx, ubx, lam_forward, lam_backward);
225 }
226 
227 void detect_simple_bounds(const MX& x, const MX& p,
228  const MX& g, const MX& lbg, const MX& ubg,
229  std::vector<casadi_int>& gi,
230  MX& lbx, MX& ubx,
231  Function& lam_forward,
232  Function& lam_backward) {
233  detect_simple_bounds_gen(x, p, g, lbg, ubg,
234  gi, lbx, ubx, lam_forward, lam_backward);
235 }
236 
237 } // namespace casadi
Function object.
Definition: function.hpp:60
bool has_free() const
Does the function have free variables.
Definition: function.cpp:1697
void call(const std::vector< DM > &arg, std::vector< DM > &res, bool always_inline=false, bool never_inline=false) const
Evaluate the function symbolically or numerically.
Definition: function.cpp:357
MX - Matrix expression.
Definition: mx.hpp:92
Sparse matrix class. SX and DM are specializations.
Definition: matrix_decl.hpp:99
General sparsity class.
Definition: sparsity.hpp:106
Sparsity T() const
Transpose the matrix.
Definition: sparsity.cpp:394
const casadi_int * row() const
Get a reference to row-vector,.
Definition: sparsity.cpp:164
const casadi_int * colind() const
Get a reference to the colindex of all column element (see class description)
Definition: sparsity.cpp:168
The casadi namespace.
Definition: archiver.cpp:28
std::vector< casadi_int > range(casadi_int start, casadi_int stop, casadi_int step, casadi_int len)
Range function.
std::vector< casadi_int > lookupvector(const std::vector< casadi_int > &v, casadi_int size)
Returns a vector for quickly looking up entries of supplied list.
std::vector< bool > boolvec_not(const std::vector< bool > &v)
Invert all entries.
void detect_simple_bounds(const SX &x, const SX &p, const SX &g, const SX &lbg, const SX &ubg, std::vector< casadi_int > &gi, SX &lbx, SX &ubx, Function &lam_forward, Function &lam_backward)
Detect simple bounds from general constraints.
Definition: nlp_tools.cpp:217
void detect_simple_bounds_gen(const T &x, const T &p, const T &g, const T &lbg, const T &ubg, std::vector< casadi_int > &gi, T &lbx, T &ubx, Function &lam_forward, Function &lam_backward)
Definition: nlp_tools.cpp:33
std::vector< casadi_int > boolvec_to_index(const std::vector< bool > &v)