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  // Process instruction
219  switch (inst) {
220 
221  // Symbolic variable
222  case 'v':
223  // Read the variable number
224  i = read_int();
225 
226  // Return the corresponding expression
227  return v_.at(i);
228 
229  // Numeric expression
230  case 'n':
231 
232  // Read the floating point number
233  d = read_double();
234 
235  // Return an expression containing the number
236  return d;
237 
238  // Numeric expression
239  case 's':
240 
241  // Read the short number
242  d = read_short();
243 
244  // Return an expression containing the number
245  return d;
246 
247  // Numeric expression
248  case 'l':
249 
250  // Read the short number
251  d = static_cast<double>(read_long());
252 
253  // Return an expression containing the number
254  return d;
255 
256  // Operation
257  case 'o':
258 
259  // Read the operation
260  i = read_int();
261 
262  // Process
263  switch (i) {
264 
265  // Unary operations, class 1 in Gay2005
266  case 13: case 14: case 15: case 16: case 34: case 37: case 38: case 39: case 40:
267  case 41: case 43: case 42: case 44: case 45: case 46: case 47: case 49: case 50:
268  case 51: case 52: case 53:
269  {
270  // Read dependency
271  MX x = expr();
272 
273  // Perform operation
274  switch (i) {
275  case 13: return floor(x);
276  case 14: return ceil(x);
277  case 15: return abs(x);
278  case 16: return -x;
279  case 34: return logic_not(x);
280  case 37: return tanh(x);
281  case 38: return tan(x);
282  case 39: return sqrt(x);
283  case 40: return sinh(x);
284  case 41: return sin(x);
285  case 42: return log10(x);
286  case 43: return log(x);
287  case 44: return exp(x);
288  case 45: return cosh(x);
289  case 46: return cos(x);
290  // case 47: return atanh(x); FIXME
291  case 49: return atan(x);
292  // case 50: return asinh(x); FIXME
293  case 51: return asin(x);
294  // case 52: return acosh(x); FIXME
295  case 53: return acos(x);
296 
297  default:
298  casadi_error("Unknown unary operation: " + str(i));
299  }
300  break;
301  }
302 
303  // Binary operations, class 2 in Gay2005
304  case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 20: case 21:
305  case 22: case 23: case 24: case 28: case 29: case 30: case 48: case 55: case 56:
306  case 57: case 58: case 73:
307  {
308  // Read dependencies
309  MX x = expr();
310  MX y = expr();
311 
312  // Perform operation
313  switch (i) {
314  case 0: return x + y;
315  case 1: return x - y;
316  case 2: return x * y;
317  case 3: return x / y;
318  // case 4: return rem(x, y); FIXME
319  case 5: return pow(x, y);
320  // case 6: return x < y; // TODO(Joel): Verify this,
321  // what is the difference to 'le' == 23 below?
322  case 20: return logic_or(x, y);
323  case 21: return logic_and(x, y);
324  case 22: return x < y;
325  case 23: return x <= y;
326  case 24: return x == y;
327  case 28: return x >= y;
328  case 29: return x > y;
329  case 30: return x != y;
330  case 48: return atan2(x, y);
331  // case 55: return intdiv(x, y); // FIXME
332  // case 56: return precision(x, y); // FIXME
333  // case 57: return round(x, y); // FIXME
334  // case 58: return trunc(x, y); // FIXME
335  // case 73: return iff(x, y); // FIXME
336 
337  default:
338  casadi_error("Unknown binary operation: " + str(i));
339  }
340  break;
341  }
342 
343  // N-ary operator, classes 2, 6 and 11 in Gay2005
344  case 11: case 12: case 54: case 59: case 60: case 61: case 70: case 71: case 74:
345  {
346  // Number of elements in the sum
347  int n = read_int();
348 
349  // Collect the arguments
350  std::vector<MX> args(n);
351  for (int k=0; k<n; ++k) {
352  args[k] = expr();
353  }
354 
355  // Perform the operation
356  switch (i) {
357  // case 11: return min(args).scalar(); FIXME // rename?
358  // case 12: return max(args).scalar(); FIXME // rename?
359  // case 54: return sum(args).scalar(); FIXME // rename?
360  // case 59: return count(args).scalar(); FIXME // rename?
361  // case 60: return numberof(args).scalar(); FIXME // rename?
362  // case 61: return numberofs(args).scalar(); FIXME // rename?
363  // case 70: return all(args).scalar(); FIXME // and in AMPL // rename?
364  // case 71: return any(args).scalar(); FIXME // or in AMPL // rename?
365  // case 74: return alldiff(args).scalar(); FIXME // rename?
366  case 54:
367  {
368  MX r = 0;
369  for (std::vector<MX>::const_iterator it=args.begin();
370  it!=args.end(); ++it) r += *it;
371  return r;
372  }
373 
374  default:
375  casadi_error("Unknown n-ary operation: " + str(i));
376  }
377  break;
378  }
379 
380  // Piecewise linear terms, class 4 in Gay2005
381  case 64:
382  casadi_error("Piecewise linear terms not supported");
383  break;
384 
385  // If-then-else expressions, class 5 in Gay2005
386  case 35: case 65: case 72:
387  casadi_error("If-then-else expressions not supported");
388  break;
389 
390  default:
391  casadi_error("Unknown operation: " + str(i));
392  }
393  break;
394 
395  default:
396  uout() << s_ptr_->tellg() << std::endl;
397  casadi_error("Unknown instruction: " + str(inst));
398  }
399 
400  // Throw error message
401  casadi_error("Unknown error");
402  }
403 
404  void NlImporter::F_segment() {
405  casadi_error("Imported function description unsupported.");
406  }
407 
408  void NlImporter::S_segment() {
409  casadi_error("Suffix values unsupported");
410  }
411 
412  void NlImporter::V_segment() {
413  // Read header
414  int i = read_int();
415  int j = read_int();
416  read_int();
417 
418  // Make sure that v is long enough
419  if (i >= v_.size()) {
420  v_.resize(i+1);
421  }
422 
423  // Initialize element to zero
424  v_.at(i) = 0;
425 
426  // Add the linear terms
427  for (int jj=0; jj<j; ++jj) {
428  // Linear term
429  int pl = read_int();
430  double cl = read_double();
431 
432  // Add to variable definition (assuming it has already been defined)
433  casadi_assert(!v_.at(pl).is_empty(), "Circular dependencies not supported");
434  v_.at(i) += cl*v_.at(pl);
435  }
436 
437  // Finally, add the nonlinear term
438  v_.at(i) += expr();
439  }
440 
441  int NlImporter::read_int() {
442  std::istream& s = *s_ptr_;
443  int i;
444  if (binary_) {
445  s.read(reinterpret_cast<char *>(&i), sizeof(int));
446  } else {
447  s >> i;
448  }
449  return i;
450  }
451 
452  char NlImporter::read_char() {
453  std::istream& s = *s_ptr_;
454  char c;
455  if (binary_) {
456  s.read(&c, 1);
457  } else {
458  s >> c;
459  }
460  return c;
461  }
462 
463  double NlImporter::read_double() {
464  std::istream& s = *s_ptr_;
465  double d;
466  if (binary_) {
467  s.read(reinterpret_cast<char *>(&d), sizeof(double));
468  } else {
469  s >> d;
470  }
471  return d;
472  }
473 
474  short NlImporter::read_short() {
475  std::istream& s = *s_ptr_;
476  short d;
477  if (binary_) {
478  s.read(reinterpret_cast<char *>(&d), 2);
479  } else {
480  s >> d;
481  }
482  return d;
483  }
484 
485  long NlImporter::read_long() {
486  std::istream& s = *s_ptr_;
487  long d;
488  if (binary_) {
489  s.read(reinterpret_cast<char *>(&d), 4);
490  } else {
491  s >> d;
492  }
493  return d;
494  }
495 
496  void NlImporter::C_segment() {
497  // Get the number
498  int i = read_int();
499 
500  // Parse and save expression
501  nlp_.g.at(i) = expr();
502  }
503 
504  void NlImporter::L_segment() {
505  casadi_error("Logical constraint expression unsupported");
506  }
507 
508  void NlImporter::O_segment() {
509  // Get the number
510  read_int(); // i
511 
512  // Should the objective be maximized
513  int sigma= read_int();
514  sign_ = sigma!=0 ? -1 : 1;
515 
516  // Parse and save expression
517  nlp_.f += expr();
518  }
519 
520  void NlImporter::d_segment() {
521  // Read the number of guesses supplied
522  int m = read_int();
523 
524  // Process initial guess for the fual variables
525  for (int i=0; i<m; ++i) {
526  // Offset and value
527  int offset = read_int();
528  double d = read_double();
529 
530  // Save initial guess
531  nlp_.lambda_init.at(offset) = d;
532  }
533  }
534 
535  void NlImporter::x_segment() {
536  // Read the number of guesses supplied
537  int m = read_int();
538 
539  // Process initial guess
540  for (int i=0; i<m; ++i) {
541  // Offset and value
542  int offset = read_int();
543  double d = read_double();
544 
545  // Save initial guess
546  nlp_.x_init.at(offset) = d;
547  }
548  }
549 
550  void NlImporter::r_segment() {
551  // For all constraints
552  for (int i=0; i<n_con_; ++i) {
553 
554  // Read constraint type
555  char c_type = read_char();
556 
557  // Temporary
558  double c;
559 
560  switch (c_type) {
561  // Upper and lower bounds
562  case '0':
563  c = read_double();
564  nlp_.g_lb.at(i) = c;
565  c = read_double();
566  nlp_.g_ub.at(i) = c;
567  continue;
568 
569  // Only upper bounds
570  case '1':
571  c = read_double();
572  nlp_.g_ub.at(i) = c;
573  continue;
574 
575  // Only lower bounds
576  case '2':
577  c = read_double();
578  nlp_.g_lb.at(i) = c;
579  continue;
580 
581  // No bounds
582  case '3':
583  continue;
584 
585  // Equality constraints
586  case '4':
587  c = read_double();
588  nlp_.g_lb.at(i) = nlp_.g_ub.at(i) = c;
589  continue;
590 
591  // Complementary constraints
592  case '5':
593  {
594  // Read the indices
595  read_int(); // ck
596  read_int(); // ci
597  casadi_error("Complementary constraints unsupported");
598  continue;
599  }
600 
601  default:
602  casadi_error("Illegal constraint type");
603  }
604  }
605  }
606 
607  void NlImporter::b_segment() {
608  // For all variable
609  for (casadi_int i=0; i<n_var_; ++i) {
610 
611  // Read constraint type
612  char c_type = read_char();
613 
614  // Temporary
615  double c;
616 
617  switch (c_type) {
618  // Upper and lower bounds
619  case '0':
620  c = read_double();
621  nlp_.x_lb.at(i) = c;
622  c = read_double();
623  nlp_.x_ub.at(i) = c;
624  continue;
625 
626  // Only upper bounds
627  case '1':
628  c = read_double();
629  nlp_.x_ub.at(i) = c;
630  continue;
631 
632  // Only lower bounds
633  case '2':
634  c = read_double();
635  nlp_.x_lb.at(i) = c;
636  continue;
637 
638  // No bounds
639  case '3':
640  continue;
641 
642  // Equality constraints
643  case '4':
644  c = read_double();
645  nlp_.x_lb.at(i) = nlp_.x_ub.at(i) = c;
646  continue;
647 
648  default:
649  casadi_error("Illegal variable bound type");
650  }
651  }
652  }
653 
654  void NlImporter::k_segment() {
655  // Get row offsets
656  std::vector<casadi_int> rowind(n_var_+1);
657 
658  // Get the number of offsets
659  int k = read_int();
660  casadi_assert_dev(k==n_var_-1);
661 
662  // Get the row offsets
663  rowind[0]=0;
664  for (int i=0; i<k; ++i) {
665  rowind[i+1] = read_int();
666  }
667  }
668 
669  void NlImporter::J_segment() {
670  // Get constraint number and number of terms
671  int i = read_int();
672  int k = read_int();
673 
674  // Get terms
675  for (int kk=0; kk<k; ++kk) {
676  // Get the term
677  int j = read_int();
678  double c = read_double();
679 
680  // Add to constraints
681  nlp_.g.at(i) += c*v_.at(j);
682  }
683  }
684 
685  void NlImporter::G_segment() {
686  // Get objective number and number of terms
687  read_int(); // i
688  int k = read_int();
689 
690  // Get terms
691  for (int kk=0; kk<k; ++kk) {
692  // Get the term
693  int j = read_int();
694  double c = read_double();
695 
696  // Add to objective
697  nlp_.f += c*v_.at(j);
698  }
699  }
700 
701 
702 } // 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