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