lpsolver.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2007-2012 by Johan De Taeye, frePPLe bvba *
4  * *
5  * This library is free software; you can redistribute it and/or modify it *
6  * under the terms of the GNU Affero General Public License as published *
7  * by the Free Software Foundation; either version 3 of the License, or *
8  * (at your option) any later version. *
9  * *
10  * This library is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU Affero General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU Affero General Public *
16  * License along with this program. *
17  * If not, see <http://www.gnu.org/licenses/>. *
18  * *
19  ***************************************************************************/
20 
21 #include "lpsolver.h"
22 
23 namespace module_lp_solver
24 {
25 
27 
28 const Keyword tag_datafile("datafile");
29 const Keyword tag_modelfile("modelfile");
30 const Keyword tag_solutionfile("solutionfile");
31 const Keyword tag_objective("objective");
32 
33 
35 {
36  // Initialize only once
37  static bool init = false;
38  static const char* name = "lpsolver";
39  if (init)
40  {
41  logger << "Warning: Initializing module lpsolver more than once." << endl;
42  return name;
43  }
44  init = true;
45 
46  // Register the Python extension
47  PyGILState_STATE state = PyGILState_Ensure();
48  try
49  {
50  // Register new Python data types
52  throw RuntimeException("Error registering Python solver_lp extension");
53  PyGILState_Release(state);
54  }
55  catch (const exception &e)
56  {
57  PyGILState_Release(state);
58  logger << "Error: " << e.what() << endl;
59  }
60  catch (...)
61  {
62  PyGILState_Release(state);
63  logger << "Error: unknown exception" << endl;
64  }
65 
66  // Return the name of the module
67  return name;
68 }
69 
70 
72 {
73  // Initialize the metadata.
74  metadata = new MetaClass("solver", "solver_lp",
75  Object::createString<LPSolver>);
76 
77  // Initialize the Python class
79 }
80 
81 
82 void LPSolver::solveObjective(const string& colname)
83 {
84  // Set the objective coefficient
85  if (colname.empty()) throw DataException("Empty objective name");
86  int col = glp_find_col(lp, colname.c_str());
87  if (!col)
88  throw DataException("Unknown objective name '" + string(colname) + "'");
89  lpx_set_obj_coef(lp, col, 1.0);
90 
91  // Message
92  if (getLogLevel()>0)
93  logger << "Solving for " << colname << "..." << endl;
94 
95  // Solve
96  int result = glp_simplex(lp, &parameters);
97 
98  // Echo the result
99  double val = lpx_get_obj_val(lp);
100  if (getLogLevel()>0)
101  {
102  if (result)
103  logger << " Error " << result << endl;
104  else
105  logger << " Optimum " << val << " found at " << Date::now() << endl;
106  }
107 
108  // Freeze the column bounds
109  lpx_set_col_bnds(lp, col, LPX_DB,
110  val>=ROUNDING_ERROR ? val-ROUNDING_ERROR : 0.0,
111  val>=-ROUNDING_ERROR ? val+ROUNDING_ERROR : 0.0);
112 
113  // Remove from the objective
114  lpx_set_obj_coef(lp, col, 0.0);
115 
116  // No more presolving required after 1 objective
117  if (parameters.presolve) parameters.presolve = 0;
118 }
119 
120 
121 void LPSolver::solve(void *v)
122 {
123  if (getLogLevel()>0)
124  logger << "Start running the solver at " << Date::now() << endl;
125 
126  // Capture all terminal output of the solver
127  glp_term_hook(solveroutputredirect,NULL);
128 
129  // Configure verbosity of the output
130  glp_init_smcp(&parameters);
131  if (getLogLevel() == 0)
132  parameters.msg_lev = GLP_MSG_OFF;
133  else if (getLogLevel() == 1)
134  parameters.msg_lev = GLP_MSG_ERR;
135  else if (getLogLevel() == 2)
136  parameters.msg_lev = GLP_MSG_ON;
137  else
138  parameters.msg_lev = GLP_MSG_ALL;
139 
140  // Read the problem from a file in the GNU MathProg language.
141  if (modelfilename.empty())
142  throw DataException("No model file specified");
143  if (datafilename.empty())
144  lp = lpx_read_model(modelfilename.c_str(), NULL, NULL);
145  else
146  lp = lpx_read_model(modelfilename.c_str(), datafilename.c_str(), NULL);
147  if (lp == NULL)
148  throw RuntimeException("Cannot read model file '" + modelfilename + "'");
149 
150  // Optinally, write the model in MPS format. This format can be read
151  // directly by other Linear Programming packages.
152  if (getLogLevel()>2)
153  {
154  string c = modelfilename + ".mps";
155  lpx_write_mps(lp,c.c_str());
156  }
157 
158  // Scale the problem data
159  lpx_scale_prob(lp);
160 
161  // Enable pre-solving
162  // After the first objective, the presolving is switched off.
163  parameters.presolve = 1;
164 
165  // Minimize the goal
166  glp_set_obj_dir(lp, minimum ? GLP_MIN : GLP_MAX);
167 
168  // Create an index for quick searching on names
169  glp_create_index(lp);
170 
171  if (getLogLevel()>0)
172  logger << "Finished solver initialisation at " << Date::now() << endl;
173 
174  // Solving...
175  if (objectives.empty())
176  throw DataException("No solver objectives are specified");
177  for (list<string>::const_iterator i = objectives.begin();
178  i != objectives.end(); ++i)
179  solveObjective(*i);
180 
181  // Write solution
182  if (!solutionfilename.empty())
183  lpx_print_sol(lp,solutionfilename.c_str());
184 
185  // Cleanup
186  lpx_delete_prob(lp);
187  glp_term_hook(NULL,NULL);
188 
189  if (getLogLevel()>0)
190  logger << "Finished running the solver at " << Date::now() << endl;
191 }
192 
193 
194 string LPSolver::replaceSpaces(const string& input)
195 {
196  string x = input;
197  for (string::iterator i = x.begin(); i != x.end(); ++i)
198  if (*i == ' ') *i = '_';
199  return x;
200 }
201 
202 
203 void LPSolver::writeElement(XMLOutput *o, const Keyword& tag, mode m) const
204 {
205  // Writing a reference
206  if (m == REFERENCE)
207  {
208  o->writeElement(tag, Tags::tag_name, getName());
209  return;
210  }
211 
212  // Write the complete object
213  if (m != NOHEADER) o->BeginObject
215 
216  // Fields
217  if (getMinimum())
219  else
221  o->writeElement(tag_modelfile, getModelFile());
222  o->writeElement(tag_datafile, getDataFile());
223  o->writeElement(tag_solutionfile, getSolutionFile());
224  for (list<string>::const_iterator i = objectives.begin();
225  i != objectives.end(); ++i)
226  o->writeElement(tag_objective, *i);
228 }
229 
230 
231 void LPSolver::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
232 {
233  if (pAttr.isA(Tags::tag_minimum))
234  setMinimum(pElement.getBool());
235  else if (pAttr.isA(Tags::tag_maximum))
236  setMinimum(!pElement.getBool());
237  else if (pAttr.isA(tag_datafile))
238  setDataFile(pElement.getString());
239  else if (pAttr.isA(tag_modelfile))
240  setModelFile(pElement.getString());
241  else if (pAttr.isA(tag_solutionfile))
242  setSolutionFile(pElement.getString());
243  else if (pAttr.isA(tag_objective))
244  addObjective(pElement.getString());
245  else
246  // The standard fields of a solver...
247  Solver::endElement(pIn, pAttr, pElement);
248 }
249 
250 
251 PyObject* LPSolver::getattro(const Attribute& attr)
252 {
253  if (attr.isA(Tags::tag_minimum))
254  return PythonObject(getMinimum());
255  else if (attr.isA(Tags::tag_maximum))
256  return PythonObject(!(getMinimum()));
257  else if (attr.isA(tag_datafile))
258  return PythonObject(getDataFile());
259  else if (attr.isA(tag_modelfile))
260  return PythonObject(getModelFile());
261  else if (attr.isA(tag_solutionfile))
262  return PythonObject(getSolutionFile());
263  else if (attr.isA(tag_objective))
264  {
265  // The list of objectives is returned as a list of strings
266  PyObject* result = PyList_New(getObjectives().size());
267  int count = 0;
268  for (list<string>::const_iterator i = getObjectives().begin();
269  i != getObjectives().end(); ++i)
270  PyList_SetItem(result, count++, PythonObject(*i));
271  return result;
272  }
273  return Solver::getattro(attr);
274 }
275 
276 
277 int LPSolver::setattro(const Attribute& attr, const PythonObject& field)
278 {
279  if (attr.isA(Tags::tag_minimum))
280  setMinimum(field.getBool());
281  else if (attr.isA(Tags::tag_maximum))
282  setMinimum(!field.getBool());
283  else if (attr.isA(tag_datafile))
284  setDataFile(field.getString());
285  else if (attr.isA(tag_modelfile))
286  setModelFile(field.getString());
287  else if (attr.isA(tag_solutionfile))
288  setSolutionFile(field.getString());
289  else if (attr.isA(tag_objective))
290  {
291  // The objective argument is a list of strings
292  PyObject* seq = PySequence_Fast(static_cast<PyObject*>(field), "expected a list");
293  if (!PyList_Check(seq))
294  {
295  PyErr_SetString(PythonDataException, "expected a list");
296  return -1; // Error
297  }
298  int len = PySequence_Size(static_cast<PyObject*>(field));
299  PythonObject item;
300  for (int i = 0; i < len; i++)
301  {
302  item = PyList_GET_ITEM(seq, i);
303  addObjective(item.getString());
304  }
305  }
306  else
307  return Solver::setattro(attr, field);
308  return 0; // OK
309 }
310 
311 } // End namespace