ampl_interface.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 "ampl_interface.hpp"
27 #include "casadi/core/casadi_misc.hpp"
28 #include <cstdio>
29 #include <cstdlib>
30 #include <fstream>
31 namespace casadi {
32 
33  extern "C"
34  int CASADI_NLPSOL_AMPL_EXPORT
35  casadi_register_nlpsol_ampl(Nlpsol::Plugin* plugin) {
36  plugin->creator = AmplInterface::creator;
37  plugin->name = "ampl";
38  plugin->doc = AmplInterface::meta_doc.c_str();
39  plugin->version = CASADI_VERSION;
40  plugin->options = &AmplInterface::options_;
41  return 0;
42  }
43 
44  extern "C"
45  void CASADI_NLPSOL_AMPL_EXPORT casadi_load_nlpsol_ampl() {
47  }
48 
49  AmplInterface::AmplInterface(const std::string& name, const Function& nlp)
50  : Nlpsol(name, nlp) {
51  }
52 
53 
55  clear_mem();
56  }
57 
59  = {{&Nlpsol::options_},
60  {{"solver",
61  {OT_STRING,
62  "AMPL solver binary"}}
63  }
64  };
65 
66  void AmplInterface::init(const Dict& opts) {
67  // Call the init method of the base class
68  Nlpsol::init(opts);
69 
70  // Set default options
71  solver_ = "ipopt";
72 
73  // Read user options
74  for (auto&& op : opts) {
75  if (op.first=="solver") {
76  solver_ = op.second.to_string();
77  }
78  }
79 
80  // Extract the expressions
81  casadi_assert(oracle().is_a("SXFunction"),
82  "Only SX supported currently.");
83  std::vector<SX> xp = oracle().sx_in();
84  std::vector<SX> fg = oracle()(xp);
85 
86  // Get x, p, f and g
87  SX x = xp.at(NL_X);
88  SX p = xp.at(NL_P);
89  SX f = fg.at(NL_F);
90  SX g = fg.at(NL_G);
91  casadi_assert(p.is_empty(), "'p' currently not supported");
92 
93  // Names of the variables, constraints
94  std::vector<std::string> x_name, g_name;
95  for (casadi_int i=0; i<nx_; ++i) x_name.push_back("x[" + str(i) + "]");
96  for (casadi_int i=0; i<ng_; ++i) g_name.push_back("g[" + str(i) + "]");
97  casadi_int max_x_name = x_name.back().size();
98  casadi_int max_g_name = g_name.empty() ? 0 : g_name.back().size();
99 
100  // Calculate the Jacobian, gradient
101  Sparsity jac_g = SX::jacobian(g, x).sparsity();
102  Sparsity jac_f = SX::jacobian(f, x).sparsity();
103 
104  // Extract the shared subexpressions
105  std::vector<SX> ex = {f, g}, v, vdef;
106  shared(ex, v, vdef);
107  f = ex[0];
108  g = ex[1];
109 
110  // Header
111  nl_init_ << "g3 1 1 0\n";
112  // Type of constraints
113  nl_init_ << nx_ << " " // number of variables
114  << ng_ << " " // number of constraints
115  << 1 << " " // number of objectives
116  << 0 << " " // number of ranges
117  << 0 << " " // ?
118  << 0 << "\n"; // number of logical constraints
119 
120  // Nonlinearity - assume all nonlinear for now TODO: Detect
121  nl_init_ << ng_ << " " // nonlinear constraints
122  << 1 << "\n"; // nonlinear objectives
123 
124  // Network constraints
125  nl_init_ << 0 << " " // nonlinear
126  << 0 << "\n"; // linear
127 
128  // Nonlinear variables
129  nl_init_ << nx_ << " " // in constraints
130  << nx_ << " " // in objectives
131  << nx_ << "\n"; // in both
132 
133  // Linear network ..
134  nl_init_ << 0 << " " // .. variables ..
135  << 0 << " " // .. arith ..
136  << 0 << " " // .. functions ..
137  << 0 << "\n"; // .. flags
138 
139  // Discrete variables
140  nl_init_ << 0 << " " // binary
141  << 0 << " " // integer
142  << 0 << " " // nonlinear in both
143  << 0 << " " // nonlinear in constraints
144  << 0 << "\n"; // nonlinear in objective
145 
146  // Nonzeros in the Jacobian, gradients
147  nl_init_ << jac_g.nnz() << " " // nnz in Jacobian
148  << jac_f.nnz() << "\n"; // nnz in gradients
149 
150  // Maximum name length
151  nl_init_ << max_x_name << " " // constraints
152  << max_g_name << "\n"; // variables
153 
154  // Shared subexpressions
155  nl_init_ << v.size() << " " // both
156  << 0 << " " // constraints
157  << 0 << " " // objective
158  << 0 << " " // c1 - constaint, but linear?
159  << 0 << "\n"; // o1 - objective, but linear?
160 
161  // Create a function which evaluates f and g
162  Function F("F", {vertcat(v), x}, {vertcat(vdef), f, g},
163  {"v", "x"}, {"vdef", "f", "g"});
164 
165  // Iterate over the algoritm
166  std::vector<std::string> work(F.sz_w());
167 
168  // Loop over the algorithm
169  for (casadi_int k=0; k<F.n_instructions(); ++k) {
170  // Get the atomic operation
171  casadi_int op = F.instruction_id(k);
172  // Get the operation indices
173  std::vector<casadi_int> o = F.instruction_output(k);
174  casadi_int o0=-1, o1=-1, i0=-1, i1=-1;
175  if (!o.empty()) o0 = o[0];
176  if (o.size()>1) o1 = o[1];
177  std::vector<casadi_int> i = F.instruction_input(k);
178  if (!i.empty()) i0 = i[0];
179  if (i.size()>1) i1 = i[1];
180  switch (op) {
181  case OP_CONST:
182  work[o0] = "n" + str(F.instruction_constant(k)) + "\n";
183  break;
184  case OP_INPUT:
185  work[o0] = "v" + str(i0*v.size() + i1) + "\n";
186  break;
187  case OP_OUTPUT:
188  if (o0==0) {
189  // Common subexpression
190  nl_init_ << "V" << (x.nnz()+o1) << " 0 0\n" << work[i0];
191  } else if (o0==1) {
192  // Nonlinear objective term
193  nl_init_ << "O" << o1 << " 0\n" << work[i0];
194  } else {
195  // Nonlinear constraint term
196  nl_init_ << "C" << o1 << "\n" << work[i0];
197  }
198  break;
199  case OP_ADD: work[o0] = "o0\n" + work[i0] + work[i1]; break;
200  case OP_SUB: work[o0] = "o1\n" + work[i0] + work[i1]; break;
201  case OP_MUL: work[o0] = "o2\n" + work[i0] + work[i1]; break;
202  case OP_DIV: work[o0] = "o3\n" + work[i0] + work[i1]; break;
203  case OP_SQ: work[o0] = "o5\n" + work[i0] + "n2\n"; break;
204  case OP_POW: work[o0] = "o5\n" + work[i0] + work[i1]; break;
205  case OP_FLOOR: work[o0] = "o13\n" + work[i0]; break;
206  case OP_CEIL: work[o0] = "o14\n" + work[i0]; break;
207  case OP_FABS: work[o0] = "o15\n" + work[i0]; break;
208  case OP_NEG: work[o0] = "o16\n" + work[i0]; break;
209  case OP_TANH: work[o0] = "o37\n" + work[i0]; break;
210  case OP_TAN: work[o0] = "o38\n" + work[i0]; break;
211  case OP_SQRT: work[o0] = "o39\n" + work[i0]; break;
212  case OP_SINH: work[o0] = "o40\n" + work[i0]; break;
213  case OP_SIN: work[o0] = "o41\n" + work[i0]; break;
214  case OP_LOG: work[o0] = "o43\n" + work[i0]; break;
215  case OP_EXP: work[o0] = "o44\n" + work[i0]; break;
216  case OP_COSH: work[o0] = "o45\n" + work[i0]; break;
217  case OP_COS: work[o0] = "o46\n" + work[i0]; break;
218  case OP_ATANH: work[o0] = "o47\n" + work[i0]; break;
219  case OP_ATAN2: work[o0] = "o48\n" + work[i0] + work[i1]; break;
220  case OP_ATAN: work[o0] = "o49\n" + work[i0]; break;
221  case OP_ASINH: work[o0] = "o50\n" + work[i0]; break;
222  case OP_ASIN: work[o0] = "o51\n" + work[i0]; break;
223  case OP_ACOSH: work[o0] = "o52\n" + work[i0]; break;
224  case OP_ACOS: work[o0] = "o53\n" + work[i0]; break;
225  default:
226  if (casadi_math<double>::ndeps(op)==1) {
227  casadi_error(casadi_math<double>::print(op, "x") + " not supported");
228  } else {
229  casadi_error(casadi_math<double>::print(op, "x", "y") + " not supported");
230  }
231  }
232  }
233 
234  // k segments, cumulative column count in jac_g
235  const casadi_int *colind = jac_g.colind(), *row = jac_g.row();
236  nl_init_ << "k" << (nx_-1) << "\n";
237  for (casadi_int i=1; i<nx_; ++i) nl_init_ << colind[i] << "\n";
238 
239  // J segments, rows in jac_g
240  Sparsity sp = jac_g.T();
241  colind = sp.colind(), row = sp.row();
242  for (casadi_int i=0; i<ng_; ++i) {
243  nl_init_ << "J" << i << " " << (colind[i+1]-colind[i]) << "\n";
244  for (casadi_int k=colind[i]; k<colind[i+1]; ++k) {
245  casadi_int r=row[k];
246  nl_init_ << r << " " << 0 << "\n"; // no linear term
247  }
248  }
249 
250  // G segments, rows in jac_f
251  sp = jac_f.T();
252  colind = sp.colind(), row = sp.row();
253  nl_init_ << "G" << 0 << " " << (colind[0+1]-colind[0]) << "\n";
254  for (casadi_int k=colind[0]; k<colind[0+1]; ++k) {
255  casadi_int r=row[k];
256  nl_init_ << r << " " << 0 << "\n"; // no linear term
257  }
258  }
259 
260  int AmplInterface::init_mem(void* mem) const {
261  if (Nlpsol::init_mem(mem)) return 1;
262  //auto m = static_cast<AmplInterfaceMemory*>(mem);
263 
264  return 0;
265  }
266 
267  void AmplInterface::set_work(void* mem, const double**& arg, double**& res,
268  casadi_int*& iw, double*& w) const {
269  //auto m = static_cast<AmplInterfaceMemory*>(mem);
270 
271  // Set work in base classes
272  Nlpsol::set_work(mem, arg, res, iw, w);
273 
274  }
275 
276  int AmplInterface::solve(void* mem) const {
277  auto m = static_cast<AmplInterfaceMemory*>(mem);
278  auto d_nlp = &m->d_nlp;
279 
280  // Create .nl file and add preamble
281  std::string nlname = temporary_file("casadi_ampl_tmp", ".nl");
282  std::ofstream nl(nlname, std::ofstream::out);
283  if (verbose_) casadi_message("Opened " + nlname);
284  nl << nl_init_.str();
285 
286  // Primal intial guess
287  nl << "x" << nx_ << "\n";
288  for (casadi_int i=0; i<nx_; ++i) {
289  nl << i << " " << d_nlp->z[i] << "\n";
290  }
291 
292 
293  // Add constraint bounds
294  nl << "r\n";
295  for (casadi_int i=0; i<ng_; ++i) {
296  double lbg = d_nlp->lbz[i+nx_];
297  double ubg = d_nlp->ubz[i+nx_];
298  if (isinf(lbg)) {
299  if (isinf(ubg)) { // no constraint
300  nl << "3\n";
301  } else { // only upper
302  nl << "1 " << ubg << "\n";
303  }
304  } else {
305  if (isinf(ubg)) { // only lower
306  nl << "2 " << lbg << "\n";
307  } else if (ubg==lbg) { // equality
308  nl << "4 " << lbg << "\n";
309  } else { // range
310  nl << "0 " << lbg << " " << ubg << "\n";
311  }
312  }
313  }
314 
315  // Add variable bounds
316  nl << "b\n";
317  for (casadi_int i=0; i<nx_; ++i) {
318  double lbx = d_nlp->lbz[i];
319  double ubx = d_nlp->ubz[i];
320  if (isinf(lbx)) {
321  if (isinf(ubx)) { // no constraint
322  nl << "3\n";
323  } else { // only upper
324  nl << "1 " << ubx << "\n";
325  }
326  } else {
327  if (isinf(ubx)) { // only lower
328  nl << "2 " << lbx << "\n";
329  } else if (ubx==lbx) { // equality
330  nl << "4 " << lbx << "\n";
331  } else { // range
332  nl << "0 " << lbx << " " << ubx << "\n";
333  }
334  }
335  }
336 
337  // Close file
338  nl.close();
339 
340  // Temporary name for the .sol file
341  std::string solname = temporary_file("casadi_ampl_tmp", ".sol");
342 
343  // Temporary name for the solver output
344  std::string outname = temporary_file("casadi_ampl_tmp", ".out");
345  // Call executable
346  std::string system_cmd = solver_ + " -o" + solname + " " + nlname + " > " + outname;
347  int ret = system(system_cmd.c_str());
348  casadi_assert_dev(ret==0);
349 
350  // Delete the nl file
351  if (remove(nlname.c_str())!=0) {
352  casadi_warning("Failed to remove " + nlname);
353  }
354  if (verbose_) casadi_message("Removed " + nlname);
355 
356  // Open .out file and dump to screen
357  std::ifstream out(outname, std::ifstream::in);
358  casadi_assert(out.is_open(), "Failed to open " + outname);
359  std::string line;
360  while (!out.eof()) {
361  getline(out, line);
362  uout() << line << "\n";
363  }
364 
365  // Close and delete .out file
366  out.close();
367  if (remove(outname.c_str())!=0) {
368  casadi_warning("Failed to remove " + outname);
369  }
370  if (verbose_) casadi_message("Removed " + outname);
371 
372  // Open .sol file
373  std::ifstream sol(solname, std::ifstream::in);
374  casadi_assert(sol.is_open(), "Failed to open " + solname);
375  if (verbose_) casadi_message("Opened " + solname);
376 
377  // Get all the lines
378  std::vector<std::string> sol_lines;
379  while (!sol.eof()) {
380  getline(sol, line);
381  sol_lines.push_back(line);
382  }
383 
384  // Get the primal solution
385  for (casadi_int i=0; i<nx_; ++i) {
386  std::istringstream s(sol_lines.at(sol_lines.size() - nx_ + i - 1));
387  s >> d_nlp->z[i];
388  }
389 
390  // Get the dual solution (x)
391  for (casadi_int i=0; i<nx_; ++i) {
392  d_nlp->lam[i] = casadi::nan; // not implemented
393  }
394 
395  // Get the dual solution (g)
396  for (casadi_int i=0; i<ng_; ++i) {
397  std::istringstream s(sol_lines.at(sol_lines.size() - ng_ - nx_ + i - 1));
398  s >> d_nlp->lam[i + nx_];
399  d_nlp->lam[i + nx_] *= -1;
400  }
401 
402  // Close and delete .sol file
403  sol.close();
404  if (remove(solname.c_str())!=0) {
405  casadi_warning("Failed to remove " + solname);
406  }
407  if (verbose_) casadi_message("Removed " + solname);
408 
409  return 0;
410  }
411 
412 
413 } // namespace casadi
AmplInterface(const std::string &name, const Function &nlp)
static const std::string meta_doc
A documentation string.
int solve(void *mem) const override
static const Options options_
Options.
static Nlpsol * creator(const std::string &name, const Function &nlp)
Create a new NLP Solver.
int init_mem(void *mem) const override
Initalize memory block.
void set_work(void *mem, const double **&arg, double **&res, casadi_int *&iw, double *&w) const override
Set the (persistent) work vectors.
std::stringstream nl_init_
void init(const Dict &opts) override
Initialize.
Function object.
Definition: function.hpp:60
const SX sx_in(casadi_int iind) const
Get symbolic primitives equivalent to the input expressions.
Definition: function.cpp:1552
bool is_empty(bool both=false) const
Check if the sparsity is empty, i.e. if one of the dimensions is zero.
casadi_int nnz() const
Get the number of (structural) non-zero elements.
Sparse matrix class. SX and DM are specializations.
Definition: matrix_decl.hpp:99
static Matrix< Scalar > jacobian(const Matrix< Scalar > &f, const Matrix< Scalar > &x, const Dict &opts=Dict())
NLP solver storage class.
Definition: nlpsol_impl.hpp:59
static const Options options_
Options.
void init(const Dict &opts) override
Initialize.
Definition: nlpsol.cpp:420
casadi_int ng_
Number of constraints.
Definition: nlpsol_impl.hpp:69
int init_mem(void *mem) const override
Initalize memory block.
Definition: nlpsol.cpp:603
casadi_int nx_
Number of variables.
Definition: nlpsol_impl.hpp:66
void set_work(void *mem, const double **&arg, double **&res, casadi_int *&iw, double *&w) const override
Set the (persistent) work vectors.
Definition: nlpsol.cpp:795
bool is_a(const std::string &type, bool recursive) const override
Check if the function is of a particular type.
Definition: nlpsol.cpp:290
const Function & oracle() const override
Get oracle.
static void registerPlugin(const Plugin &plugin, bool needs_lock=true)
Register an integrator in the factory.
bool verbose_
Verbose printout.
void clear_mem()
Clear all memory (called from destructor)
General sparsity class.
Definition: sparsity.hpp:106
Sparsity T() const
Transpose the matrix.
Definition: sparsity.cpp:394
casadi_int nnz() const
Get the number of (structural) non-zeros.
Definition: sparsity.cpp:148
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
int CASADI_NLPSOL_AMPL_EXPORT casadi_register_nlpsol_ampl(Nlpsol::Plugin *plugin)
@ NL_X
Decision variable.
Definition: nlpsol.hpp:170
@ NL_P
Fixed parameter.
Definition: nlpsol.hpp:172
@ NL_F
Objective function.
Definition: nlpsol.hpp:183
@ NL_G
Constraint function.
Definition: nlpsol.hpp:185
std::string temporary_file(const std::string &prefix, const std::string &suffix)
void CASADI_NLPSOL_AMPL_EXPORT casadi_load_nlpsol_ampl()
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 nan
Not a number.
Definition: calculus.hpp:53
bool remove(const std::string &path)
Definition: ghc.cpp:47
std::ostream & uout()
@ OP_COS
Definition: calculus.hpp:68
@ OP_SINH
Definition: calculus.hpp:74
@ OP_COSH
Definition: calculus.hpp:74
@ OP_ASINH
Definition: calculus.hpp:75
@ OP_ACOS
Definition: calculus.hpp:69
@ OP_ATAN2
Definition: calculus.hpp:76
@ OP_ATAN
Definition: calculus.hpp:69
@ OP_SQRT
Definition: calculus.hpp:67
@ OP_EXP
Definition: calculus.hpp:66
@ OP_OUTPUT
Definition: calculus.hpp:82
@ OP_SIN
Definition: calculus.hpp:68
@ OP_ASIN
Definition: calculus.hpp:69
@ OP_ACOSH
Definition: calculus.hpp:75
@ OP_CEIL
Definition: calculus.hpp:71
@ OP_CONST
Definition: calculus.hpp:79
@ OP_INPUT
Definition: calculus.hpp:82
@ OP_SUB
Definition: calculus.hpp:65
@ OP_ATANH
Definition: calculus.hpp:75
@ OP_POW
Definition: calculus.hpp:66
@ OP_FABS
Definition: calculus.hpp:71
@ OP_LOG
Definition: calculus.hpp:66
@ OP_TANH
Definition: calculus.hpp:74
@ OP_ADD
Definition: calculus.hpp:65
@ OP_DIV
Definition: calculus.hpp:65
@ OP_FLOOR
Definition: calculus.hpp:71
@ OP_NEG
Definition: calculus.hpp:66
@ OP_MUL
Definition: calculus.hpp:65
@ OP_SQ
Definition: calculus.hpp:67
@ OP_TAN
Definition: calculus.hpp:68
casadi_nlpsol_data< double > d_nlp
Definition: nlpsol_impl.hpp:42
Options metadata for a class.
Definition: options.hpp:40
Easy access to all the functions for a particular type.
Definition: calculus.hpp:1125