1 /* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2 
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include <minizinc/solvers/nl/nl_solreader.hh>
8 
9 using namespace std;
10 
11 namespace MiniZinc {
12 
13 // *** *** *** NLSol *** *** ***
14 
15 // Parse a string into a NLSol object
parseSolution(istream & in)16 NLSol NLSol::parseSolution(istream& in) {
17   string buffer;
18   string msg;
19   vector<double> vec;
20   NL_Solver_Status st;
21 
22   try {
23     // Read the message
24     while (getline(in, buffer) && !buffer.empty()) {
25       msg += buffer + '\n';
26     }
27 
28     if (in.bad()) {
29       return NLSol("Error reading the solver message", NL_Solver_Status::PARSE_ERROR, {});
30     }
31 
32     // Check if we have 'Options', and skip them
33     if (getline(in, buffer)) {
34       if (buffer == "Options") {
35         if (getline(in, buffer)) {
36           int nb_options = stoi(buffer);
37           while (nb_options > 0) {
38             getline(in, buffer);
39             nb_options--;
40           }
41         }
42       }
43     }
44 
45     if (in.bad()) {
46       return NLSol("Error reading the solver Options", NL_Solver_Status::PARSE_ERROR, {});
47     }
48 
49     // Check the dual: we ignore the first, read the second. If non zero, some lines are to be
50     // skipped
51     (getline(in, buffer) && getline(in, buffer));
52     if (in.bad()) {
53       return NLSol("Error reading the number of dual", NL_Solver_Status::PARSE_ERROR, {});
54     }
55     int nb_duals = stoi(buffer);
56 
57     // Check the primal: we ignore the first, read the second
58     getline(in, buffer) && getline(in, buffer);
59     if (in.bad()) {
60       return NLSol("Error reading the number of primal", NL_Solver_Status::PARSE_ERROR, {});
61     }
62     int nb_vars = stoi(buffer);
63 
64     // Skip the duals
65     while (nb_duals > 0 && getline(in, buffer)) {
66       nb_duals--;
67     }
68 
69     if (in.bad()) {
70       return NLSol("Error reading the dual values", NL_Solver_Status::PARSE_ERROR, {});
71     }
72 
73     // Read the vars
74     while (nb_vars > 0 && getline(in, buffer)) {
75       double d = stod(buffer);
76       vec.push_back(d);
77       nb_vars--;
78     }
79 
80     if (in.bad()) {
81       return NLSol("Error reading the primal values", NL_Solver_Status::PARSE_ERROR, {});
82     }
83 
84     // Reading status code
85     // objno 0 EXIT
86     // ........
87     // 8 char
88     getline(in, buffer);
89     string sub = buffer.substr(8, buffer.length() - 8);
90     int resultCode = stoi(sub);
91     st = NL_Solver_Status::UNKNOWN;
92     // Not this case, this one is our own: case -2: st = NL_Solver_Status::PARSE_ERROR;
93     if (resultCode == -1) {
94       st = NL_Solver_Status::UNKNOWN;
95     } else if (resultCode >= 0 && resultCode < 100) {
96       st = NL_Solver_Status::SOLVED;
97     } else if (resultCode >= 100 && resultCode < 200) {
98       st = NL_Solver_Status::UNCERTAIN;
99     } else if (resultCode >= 200 && resultCode < 300) {
100       st = NL_Solver_Status::INFEASIBLE;
101     } else if (resultCode >= 300 && resultCode < 400) {
102       st = NL_Solver_Status::UNBOUNDED;
103     } else if (resultCode >= 400 && resultCode < 500) {
104       st = NL_Solver_Status::LIMIT;
105     } else if (resultCode >= 500 && resultCode < 600) {
106       st = NL_Solver_Status::FAILURE;
107     } else if (resultCode == 600) {
108       st = NL_Solver_Status::INTERRUPTED;
109     }
110 
111   } catch (...) {
112     return NLSol("Parsing error (probably a bad number)", NL_Solver_Status::PARSE_ERROR, vec);
113   }
114 
115   return NLSol(msg, st, vec);
116 }
117 
118 // *** *** *** NLSolns2Out *** *** ***
119 
120 /** Our "feedrawdatachunk" directly gets the solver's output, which is not the result.
121  *  The result is written in the .sol file.
122  *  Get the solver output and add a comment % in front of the lines
123  *  We may be in a middle of a line!
124  */
feedRawDataChunk(const char * data)125 bool NLSolns2Out::feedRawDataChunk(const char* data) {
126   if (data != nullptr) {
127     std::stringstream ss(data);
128     string to;
129 
130     while (getline(ss, to)) {
131       if (ss.eof()) {
132         if (_inLine) {  // Must complete a line, and the line is not over yet
133           getLog() << to << endl;
134         } else {  // Start an incomple line
135           getLog() << "% " << to;
136           _inLine = true;
137         }
138       } else {
139         if (_inLine) {  // Must complete a line, and the line is over.
140           getLog() << to << endl;
141           _inLine = false;
142         } else {  // Full line
143           getLog() << "% " << to << endl;
144         }
145       }
146     }
147   }
148   return true;
149 }
150 
parseSolution(const string & filename)151 void NLSolns2Out::parseSolution(const string& filename) {
152   ifstream f(FILE_PATH(filename));
153   NLSol sol = NLSol::parseSolution(f);
154 
155   switch (sol.status) {
156     case NL_Solver_Status::PARSE_ERROR: {
157       DEBUG_MSG("NL_Solver_Status: PARSE ERROR" << endl);
158       _out->feedRawDataChunk(_out->opt.errorMsgDef);
159       break;
160     }
161 
162     case NL_Solver_Status::UNKNOWN: {
163       DEBUG_MSG("NL_Solver_Status: UNKNOWN" << endl);
164       _out->feedRawDataChunk(_out->opt.unknownMsgDef);
165       break;
166     }
167 
168     case NL_Solver_Status::SOLVED: {
169       DEBUG_MSG("NL_Solver_Status: SOLVED" << endl);
170 
171       stringstream sb;
172       // sb << std::hexfloat;  // Use hexadecimal format for FP
173       sb << std::showpoint;  // Always shows the decimal point, so we have e.g. '256.0' when 256 is
174                              // the answer for a fp value.
175       sb.precision(numeric_limits<double>::digits10 + 2);
176 
177       for (int i = 0; i < _nlFile.variables.size(); ++i) {
178         string n = _nlFile.vnames[i];
179         NLVar v = _nlFile.variables[n];
180         if (v.toReport) {
181           sb << v.name << " = ";
182           if (v.isInteger) {
183             long value = sol.values[i];
184             sb << value;
185           } else {
186             double value = sol.values[i];
187             sb << value;
188           }
189           sb << ";\n";
190         }
191       }
192 
193       // Output the arrays
194       for (auto& a : _nlFile.outputArrays) {
195         sb << a.name << " = array" << a.dimensions.size() << "d( ";
196         for (const string& s : a.dimensions) {
197           sb << s << ", ";
198         }
199         sb << "[";
200         for (int j = 0; j < a.items.size(); ++j) {
201           const NLArray::Item& item = a.items.at(j);
202 
203           // Case of the literals
204           if (item.variable.empty()) {
205             if (a.isInteger) {
206               sb << static_cast<long long int>(item.value);
207             } else {
208               sb << item.value;
209             }
210           } else {
211             int index = _nlFile.variableIndexes.at(item.variable);
212             if (a.isInteger) {
213               long value = sol.values[index];
214               sb << value;
215             } else {
216               double value = sol.values[index];
217               sb << value;
218             }
219           }
220 
221           if (j < a.items.size() - 1) {
222             sb << ", ";
223           }
224         }
225 
226         sb << "]);\n";
227       }
228 
229       string s = sb.str();
230       _out->feedRawDataChunk(s.c_str());
231       _out->feedRawDataChunk(_out->opt.solutionSeparatorDef);
232       if (_nlFile.objective.isOptimisation()) {
233         _out->feedRawDataChunk("\n");
234         _out->feedRawDataChunk(_out->opt.searchCompleteMsgDef);
235       }
236 
237       break;
238     }
239 
240     case NL_Solver_Status::UNCERTAIN: {
241       DEBUG_MSG("NL_Solver_Status: UNCERTAIN" << endl);
242       _out->feedRawDataChunk(_out->opt.unknownMsgDef);
243       break;
244     }
245 
246     case NL_Solver_Status::INFEASIBLE: {
247       DEBUG_MSG("NL_Solver_Status: INFEASIBLE" << endl);
248       _out->feedRawDataChunk(_out->opt.unsatisfiableMsgDef);
249       break;
250     }
251 
252     case NL_Solver_Status::UNBOUNDED: {
253       DEBUG_MSG("NL_Solver_Status: UNBOUNDED" << endl);
254       _out->feedRawDataChunk(_out->opt.unboundedMsgDef);
255       break;
256     }
257 
258     case NL_Solver_Status::LIMIT: {
259       DEBUG_MSG("NL_Solver_Status: LIMIT" << endl);
260       _out->feedRawDataChunk(_out->opt.unknownMsgDef);
261       break;
262     }
263 
264     case NL_Solver_Status::INTERRUPTED: {
265       DEBUG_MSG("NL_Solver_Status: INTERRUPTED" << endl);
266       _out->feedRawDataChunk(_out->opt.unknownMsgDef);
267       break;
268     }
269 
270     default:
271       should_not_happen("parseSolution: switch on status with unknown code: " << sol.status);
272   }
273 
274   // "Finish" the feed
275   _out->feedRawDataChunk("\n");
276 }
277 
getLog()278 ostream& NLSolns2Out::getLog() { return _verbose ? _out->getLog() : _dummyOfstream; }
279 
280 }  // namespace MiniZinc
281