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