nlp_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 
26 #include "nlp_builder.hpp"
27 #include "core.hpp"
28 #include "filesystem_impl.hpp"
29 #include <fstream>
30 
31 namespace casadi {
32 
33  void NlpBuilder::import_nl(const std::string& filename, const Dict& opts) {
34  // Redirect to helper class
35  NlImporter(*this, filename, opts);
36  }
37 
38  void NlpBuilder::disp(std::ostream& stream, bool more) const {
39  stream << "#x=" << this->x.size() << ", #g=" << this->g.size();
40  if (more) {
41  stream << std::endl;
42  stream << "x = " << this->x << std::endl;
43  stream << "f = " << this->f << std::endl;
44  stream << "g = " << this->g << std::endl;
45  }
46  }
47 
48  NlImporter::NlImporter(NlpBuilder& nlp, const std::string& filename, const Dict& opts)
49  : nlp_(nlp), s_ptr_(Filesystem::ifstream_ptr(filename)) {
50  // Set default options
51  verbose_=false;
52  std::istream& s = *s_ptr_;
53 
54  // Read user options
55  for (auto&& op : opts) {
56  if (op.first == "verbose") {
57  verbose_ = op.second;
58  } else {
59  std::stringstream ss;
60  ss << "Unknown option \"" << op.first << "\"" << std::endl;
61  throw CasadiException(ss.str());
62  }
63  }
64  // Open file for reading
65  if (verbose_) casadi_message("Reading file \"" + filename + "\"");
66 
67  // Read the header of the NL-file (first 10 lines)
68  const casadi_int header_sz = 10;
69  std::vector<std::string> header(header_sz);
70  for (casadi_int k=0; k<header_sz; ++k) {
71  getline(s, header[k]);
72  }
73 
74  // Assert that the file is not in binary form
75  if (header.at(0).at(0)=='g') {
76  binary_ = false;
77  } else if (header.at(0).at(0)=='b') {
78  binary_ = true;
79  } else {
80  casadi_error("File could not be read");
81  }
82 
83  // Get the number of objectives and constraints
84  std::stringstream ss(header[1]);
85  ss >> n_var_ >> n_con_ >> n_obj_ >> n_eq_ >> n_lcon_;
86  if (verbose_) {
87  casadi_message("n_var=" + str(n_var_) + ", n_con =" + str(n_con_) + ", "
88  "n_obj=" + str(n_obj_) + ", n_eq=" + str(n_eq_) + ", "
89  "n_lcon=" + str(n_lcon_));
90  }
91 
92  // Get the number of nonlinear vars in constraints, objectives, both
93  std::stringstream ss4(header[4]);
94  ss4 >> nlvc_ >> nlvo_ >> nlvb_;
95  if (verbose_) {
96  casadi_message("nlvc=" + str(nlvc_) + ", nlvo=" + str(nlvo_) + ", nlvb=" + str(nlvb_));
97  }
98 
99  // Get the number of discrete variables
100  std::stringstream ss6(header[6]);
101  ss6 >> nbv_ >> niv_ >> nlvbi_ >> nlvci_ >> nlvoi_;
102  if (verbose_) {
103  casadi_message("nbv=" + str(nbv_) + ", niv =" + str(niv_) + ", "
104  "nlvbi=" + str(nlvbi_) + ", nlvci=" + str(nlvci_) + ", "
105  "nlvoi=" + str(nlvoi_));
106  }
107 
108  // Allocate variables
109  nlp_.x = MX::sym("x", 1, 1, n_var_);
110 
111  // Allocate f and c
112  nlp_.f = 0;
113  nlp_.g.resize(n_con_, 0);
114 
115  // Allocate bounds for x and primal initial guess
116  nlp_.x_lb.resize(n_var_, -inf);
117  nlp_.x_ub.resize(n_var_, inf);
118  nlp_.x_init.resize(n_var_, 0);
119 
120  // Allocate bounds for g and dual initial guess
121  nlp_.g_lb.resize(n_con_, -inf);
122  nlp_.g_ub.resize(n_con_, inf);
123  nlp_.lambda_init.resize(n_con_, 0);
124 
125  // Allocate binary variables vector
126  nlp_.discrete.clear();
127 
128  //D. M. Gay and M. Hill, 'Hooking Your Solver to AMPL' October, 1997.
129  // continuous in an objective and in a constraint
130  for (casadi_int j=0; j<nlvb_-nlvbi_; ++j) nlp_.discrete.push_back(false);
131 
132  // integer in an objective and in a constraint
133  for (casadi_int j=0; j<nlvbi_; ++j) nlp_.discrete.push_back(true);
134 
135  // continuous just in constraints
136  for (casadi_int j=0; j<nlvc_ - (nlvb_ + nlvci_); ++j) nlp_.discrete.push_back(false);
137 
138  // integer just in constraints
139  for (casadi_int j=0; j<nlvci_; ++j) nlp_.discrete.push_back(true);
140 
141  // continuous just in objectives
142  for (casadi_int j=0; j<nlvo_ - (nlvc_ + nlvoi_); ++j) nlp_.discrete.push_back(false);
143 
144  // integer just in objectives
145  for (casadi_int j=0; j < nlvoi_; ++j) nlp_.discrete.push_back(true);
146 
147  // linear
148  casadi_int max_nlvc_nlvo = (nlvc_ < nlvo_) ? nlvo_ : nlvc_;
149  for (casadi_int j=0; j<n_var_-(max_nlvc_nlvo+niv_+nbv_); ++j) nlp_.discrete.push_back(false);
150 
151  // binary
152  for (casadi_int j = 0; j<nbv_; ++j) nlp_.discrete.push_back(true);
153 
154  // other integer
155  for (casadi_int j = 0; j<niv_; ++j) nlp_.discrete.push_back(true);
156 
157  casadi_assert(nlp_.discrete.size()==n_var_,
158  "Number of variables in the header don't match");
159 
160  // All variables, including dependent
161  v_ = nlp_.x;
162 
163  if (binary_) {
164  std::streampos offset = s.tellg();
165  s_ptr_.reset();
166  s_ptr_ = Filesystem::ifstream_ptr(filename, std::ifstream::binary);
167  s_ptr_->seekg(offset);
168  }
169 
170  // Read segments
171  parse();
172 
173  // multiple the objective sign
174  nlp_.f = sign_*nlp_.f;
175  }
176 
178  // Close the NL file
179  s_ptr_.reset();
180  }
181 
182  void NlImporter::parse() {
183  // Segment key
184  char key;
185 
186  // Process segments
187  while (true) {
188  // Read segment key
189  key = read_char();
190  if (s_ptr_->eof()) break; // end of file encountered
191  switch (key) {
192  case 'F': F_segment(); break;
193  case 'S': S_segment(); break;
194  case 'V': V_segment(); break;
195  case 'C': C_segment(); break;
196  case 'L': L_segment(); break;
197  case 'O': O_segment(); break;
198  case 'd': d_segment(); break;
199  case 'x': x_segment(); break;
200  case 'r': r_segment(); break;
201  case 'b': b_segment(); break;
202  case 'k': k_segment(); break;
203  case 'J': J_segment(); break;
204  case 'G': G_segment(); break;
205  default: casadi_error("Unknown .nl segment");
206  }
207  }
208  }
209 
210  MX NlImporter::expr() {
211  // Read the instruction
212  char inst = read_char();
213 
214  // Temporaries
215  int i;
216  double d;
217 
218  // Error message
219  std::stringstream msg;
220 
221  // Process instruction
222  switch (inst) {
223 
224  // Symbolic variable
225  case 'v':
226  // Read the variable number
227  i = read_int();
228 
229  // Return the corresponding expression
230  return v_.at(i);
231 
232  // Numeric expression
233  case 'n':
234 
235  // Read the floating point number
236  d = read_double();
237 
238  // Return an expression containing the number
239  return d;
240 
241  // Numeric expression
242  case 's':
243 
244  // Read the short number
245  d = read_short();
246 
247  // Return an expression containing the number
248  return d;
249 
250  // Numeric expression
251  case 'l':
252 
253  // Read the short number
254  d = static_cast<double>(read_long());
255 
256  // Return an expression containing the number
257  return d;
258 
259  // Operation
260  case 'o':
261 
262  // Read the operation
263  i = read_int();
264 
265  // Process
266  switch (i) {
267 
268  // Unary operations, class 1 in Gay2005
269  case 13: case 14: case 15: case 16: case 34: case 37: case 38: case 39: case 40:
270  case 41: case 43: case 42: case 44: case 45: case 46: case 47: case 49: case 50:
271  case 51: case 52: case 53:
272  {
273  // Read dependency
274  MX x = expr();
275 
276  // Perform operation
277  switch (i) {
278  case 13: return floor(x);
279  case 14: return ceil(x);
280  case 15: return abs(x);
281  case 16: return -x;
282  case 34: return logic_not(x);
283  case 37: return tanh(x);
284  case 38: return tan(x);
285  case 39: return sqrt(x);
286  case 40: return sinh(x);
287  case 41: return sin(x);
288  case 42: return log10(x);
289  case 43: return log(x);
290  case 44: return exp(x);
291  case 45: return cosh(x);
292  case 46: return cos(x);
293  // case 47: return atanh(x); FIXME
294  case 49: return atan(x);
295  // case 50: return asinh(x); FIXME
296  case 51: return asin(x);
297  // case 52: return acosh(x); FIXME
298  case 53: return acos(x);
299 
300  default:
301  casadi_error("Unknown unary operation: " + str(i));
302  }
303  break;
304  }
305 
306  // Binary operations, class 2 in Gay2005
307  case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 20: case 21:
308  case 22: case 23: case 24: case 28: case 29: case 30: case 48: case 55: case 56:
309  case 57: case 58: case 73:
310  {
311  // Read dependencies
312  MX x = expr();
313  MX y = expr();
314 
315  // Perform operation
316  switch (i) {
317  case 0: return x + y;
318  case 1: return x - y;
319  case 2: return x * y;
320  case 3: return x / y;
321  // case 4: return rem(x, y); FIXME
322  case 5: return pow(x, y);
323  // case 6: return x < y; // TODO(Joel): Verify this,
324  // what is the difference to 'le' == 23 below?
325  case 20: return logic_or(x, y);
326  case 21: return logic_and(x, y);
327  case 22: return x < y;
328  case 23: return x <= y;
329  case 24: return x == y;
330  case 28: return x >= y;
331  case 29: return x > y;
332  case 30: return x != y;
333  case 48: return atan2(x, y);
334  // case 55: return intdiv(x, y); // FIXME
335  // case 56: return precision(x, y); // FIXME
336  // case 57: return round(x, y); // FIXME
337  // case 58: return trunc(x, y); // FIXME
338  // case 73: return iff(x, y); // FIXME
339 
340  default:
341  casadi_error("Unknown binary operation: " + str(i));
342  }
343  break;
344  }
345 
346  // N-ary operator, classes 2, 6 and 11 in Gay2005
347  case 11: case 12: case 54: case 59: case 60: case 61: case 70: case 71: case 74:
348  {
349  // Number of elements in the sum
350  int n = read_int();
351 
352  // Collect the arguments
353  std::vector<MX> args(n);
354  for (int k=0; k<n; ++k) {
355  args[k] = expr();
356  }
357 
358  // Perform the operation
359  switch (i) {
360  // case 11: return min(args).scalar(); FIXME // rename?
361  // case 12: return max(args).scalar(); FIXME // rename?
362  // case 54: return sum(args).scalar(); FIXME // rename?
363  // case 59: return count(args).scalar(); FIXME // rename?
364  // case 60: return numberof(args).scalar(); FIXME // rename?
365  // case 61: return numberofs(args).scalar(); FIXME // rename?
366  // case 70: return all(args).scalar(); FIXME // and in AMPL // rename?
367  // case 71: return any(args).scalar(); FIXME // or in AMPL // rename?
368  // case 74: return alldiff(args).scalar(); FIXME // rename?
369  case 54:
370  {
371  MX r = 0;
372  for (std::vector<MX>::const_iterator it=args.begin();
373  it!=args.end(); ++it) r += *it;
374  return r;
375  }
376 
377  default:
378  casadi_error("Unknown n-ary operation: " + str(i));
379  }
380  break;
381  }
382 
383  // Piecewise linear terms, class 4 in Gay2005
384  case 64:
385  casadi_error("Piecewise linear terms not supported");
386  break;
387 
388  // If-then-else expressions, class 5 in Gay2005
389  case 35: case 65: case 72:
390  casadi_error("If-then-else expressions not supported");
391  break;
392 
393  default:
394  casadi_error("Unknown operation: " + str(i));
395  }
396  break;
397 
398  default:
399  uout() << s_ptr_->tellg() << std::endl;
400  casadi_error("Unknown instruction: " + str(inst));
401  }
402 
403  // Throw error message
404  casadi_error("Unknown error");
405  }
406 
407  void NlImporter::F_segment() {
408  casadi_error("Imported function description unsupported.");
409  }
410 
411  void NlImporter::S_segment() {
412  casadi_error("Suffix values unsupported");
413  }
414 
415  void NlImporter::V_segment() {
416  // Read header
417  int i = read_int();
418  int j = read_int();
419  read_int();
420 
421  // Make sure that v is long enough
422  if (i >= v_.size()) {
423  v_.resize(i+1);
424  }
425 
426  // Initialize element to zero
427  v_.at(i) = 0;
428 
429  // Add the linear terms
430  for (int jj=0; jj<j; ++jj) {
431  // Linear term
432  int pl = read_int();
433  double cl = read_double();
434 
435  // Add to variable definition (assuming it has already been defined)
436  casadi_assert(!v_.at(pl).is_empty(), "Circular dependencies not supported");
437  v_.at(i) += cl*v_.at(pl);
438  }
439 
440  // Finally, add the nonlinear term
441  v_.at(i) += expr();
442  }
443 
444  int NlImporter::read_int() {
445  std::istream& s = *s_ptr_;
446  int i;
447  if (binary_) {
448  s.read(reinterpret_cast<char *>(&i), sizeof(int));
449  } else {
450  s >> i;
451  }
452  return i;
453  }
454 
455  char NlImporter::read_char() {
456  std::istream& s = *s_ptr_;
457  char c;
458  if (binary_) {
459  s.read(&c, 1);
460  } else {
461  s >> c;
462  }
463  return c;
464  }
465 
466  double NlImporter::read_double() {
467  std::istream& s = *s_ptr_;
468  double d;
469  if (binary_) {
470  s.read(reinterpret_cast<char *>(&d), sizeof(double));
471  } else {
472  s >> d;
473  }
474  return d;
475  }
476 
477  short NlImporter::read_short() {
478  std::istream& s = *s_ptr_;
479  short d;
480  if (binary_) {
481  s.read(reinterpret_cast<char *>(&d), 2);
482  } else {
483  s >> d;
484  }
485  return d;
486  }
487 
488  long NlImporter::read_long() {
489  std::istream& s = *s_ptr_;
490  long d;
491  if (binary_) {
492  s.read(reinterpret_cast<char *>(&d), 4);
493  } else {
494  s >> d;
495  }
496  return d;
497  }
498 
499  void NlImporter::C_segment() {
500  // Get the number
501  int i = read_int();
502 
503  // Parse and save expression
504  nlp_.g.at(i) = expr();
505  }
506 
507  void NlImporter::L_segment() {
508  casadi_error("Logical constraint expression unsupported");
509  }
510 
511  void NlImporter::O_segment() {
512  // Get the number
513  read_int(); // i
514 
515  // Should the objective be maximized
516  int sigma= read_int();
517  sign_ = sigma!=0 ? -1 : 1;
518 
519  // Parse and save expression
520  nlp_.f += expr();
521  }
522 
523  void NlImporter::d_segment() {
524  // Read the number of guesses supplied
525  int m = read_int();
526 
527  // Process initial guess for the fual variables
528  for (int i=0; i<m; ++i) {
529  // Offset and value
530  int offset = read_int();
531  double d = read_double();
532 
533  // Save initial guess
534  nlp_.lambda_init.at(offset) = d;
535  }
536  }
537 
538  void NlImporter::x_segment() {
539  // Read the number of guesses supplied
540  int m = read_int();
541 
542  // Process initial guess
543  for (int i=0; i<m; ++i) {
544  // Offset and value
545  int offset = read_int();
546  double d = read_double();
547 
548  // Save initial guess
549  nlp_.x_init.at(offset) = d;
550  }
551  }
552 
553  void NlImporter::r_segment() {
554  // For all constraints
555  for (int i=0; i<n_con_; ++i) {
556 
557  // Read constraint type
558  char c_type = read_char();
559 
560  // Temporary
561  double c;
562 
563  switch (c_type) {
564  // Upper and lower bounds
565  case '0':
566  c = read_double();
567  nlp_.g_lb.at(i) = c;
568  c = read_double();
569  nlp_.g_ub.at(i) = c;
570  continue;
571 
572  // Only upper bounds
573  case '1':
574  c = read_double();
575  nlp_.g_ub.at(i) = c;
576  continue;
577 
578  // Only lower bounds
579  case '2':
580  c = read_double();
581  nlp_.g_lb.at(i) = c;
582  continue;
583 
584  // No bounds
585  case '3':
586  continue;
587 
588  // Equality constraints
589  case '4':
590  c = read_double();
591  nlp_.g_lb.at(i) = nlp_.g_ub.at(i) = c;
592  continue;
593 
594  // Complementary constraints
595  case '5':
596  {
597  // Read the indices
598  read_int(); // ck
599  read_int(); // ci
600  casadi_error("Complementary constraints unsupported");
601  continue;
602  }
603 
604  default:
605  casadi_error("Illegal constraint type");
606  }
607  }
608  }
609 
610  void NlImporter::b_segment() {
611  // For all variable
612  for (casadi_int i=0; i<n_var_; ++i) {
613 
614  // Read constraint type
615  char c_type = read_char();
616 
617  // Temporary
618  double c;
619 
620  switch (c_type) {
621  // Upper and lower bounds
622  case '0':
623  c = read_double();
624  nlp_.x_lb.at(i) = c;
625  c = read_double();
626  nlp_.x_ub.at(i) = c;
627  continue;
628 
629  // Only upper bounds
630  case '1':
631  c = read_double();
632  nlp_.x_ub.at(i) = c;
633  continue;
634 
635  // Only lower bounds
636  case '2':
637  c = read_double();
638  nlp_.x_lb.at(i) = c;
639  continue;
640 
641  // No bounds
642  case '3':
643  continue;
644 
645  // Equality constraints
646  case '4':
647  c = read_double();
648  nlp_.x_lb.at(i) = nlp_.x_ub.at(i) = c;
649  continue;
650 
651  default:
652  casadi_error("Illegal variable bound type");
653  }
654  }
655  }
656 
657  void NlImporter::k_segment() {
658  // Get row offsets
659  std::vector<casadi_int> rowind(n_var_+1);
660 
661  // Get the number of offsets
662  int k = read_int();
663  casadi_assert_dev(k==n_var_-1);
664 
665  // Get the row offsets
666  rowind[0]=0;
667  for (int i=0; i<k; ++i) {
668  rowind[i+1] = read_int();
669  }
670  }
671 
672  void NlImporter::J_segment() {
673  // Get constraint number and number of terms
674  int i = read_int();
675  int k = read_int();
676 
677  // Get terms
678  for (int kk=0; kk<k; ++kk) {
679  // Get the term
680  int j = read_int();
681  double c = read_double();
682 
683  // Add to constraints
684  nlp_.g.at(i) += c*v_.at(j);
685  }
686  }
687 
688  void NlImporter::G_segment() {
689  // Get objective number and number of terms
690  read_int(); // i
691  int k = read_int();
692 
693  // Get terms
694  for (int kk=0; kk<k; ++kk) {
695  // Get the term
696  int j = read_int();
697  double c = read_double();
698 
699  // Add to objective
700  nlp_.f += c*v_.at(j);
701  }
702  }
703 
704 
705 } // namespace casadi
Casadi exception class.
Definition: exception.hpp:77
Filesystem interface.
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
static MX sym(const std::string &name, casadi_int nrow=1, casadi_int ncol=1)
Create an nrow-by-ncol symbolic primitive.
NlImporter(NlpBuilder &nlp, const std::string &filename, const Dict &opts)
Definition: nlp_builder.cpp:48
A symbolic NLP representation.
Definition: nlp_builder.hpp:41
void disp(std::ostream &stream, bool more=false) const
Print a description of the object.
Definition: nlp_builder.cpp:38
std::vector< double > x_lb
Bounds on x.
Definition: nlp_builder.hpp:59
std::vector< double > g_ub
Variables.
Definition: nlp_builder.hpp:62
std::vector< bool > discrete
Discrete variables.
Definition: nlp_builder.hpp:71
std::vector< double > lambda_init
Dual initial guess.
Definition: nlp_builder.hpp:68
std::vector< double > x_ub
Variables.
Definition: nlp_builder.hpp:59
std::vector< MX > x
Variables.
Definition: nlp_builder.hpp:50
std::vector< double > g_lb
Bounds on g.
Definition: nlp_builder.hpp:62
std::vector< MX > g
Constraints.
Definition: nlp_builder.hpp:56
MX f
Objective.
Definition: nlp_builder.hpp:53
void import_nl(const std::string &filename, const Dict &opts=Dict())
Import an .nl file.
Definition: nlp_builder.cpp:33
std::vector< double > x_init
Primal initial guess.
Definition: nlp_builder.hpp:65
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.
const double inf
infinity
Definition: calculus.hpp:50
std::ostream & uout()
std::string filename(const std::string &path)
Definition: ghc.cpp:55