Loading...
Searching...
No Matches
transportEngine.cpp
Go to the documentation of this file.
1/*
2 *
3 * GAMS - General Algebraic Modeling System C++ API
4 *
5 * Copyright (c) 2017-2022 GAMS Software GmbH <support@gams.com>
6 * Copyright (c) 2017-2022 GAMS Development Corp. <support@gams.com>
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in all
16 * copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE.
25 */
26#include "gams.h"
27#include "gamsengineconfiguration.h"
28#include "gamsvariablerecord.h"
29
30#include <cmath>
31#include <iostream>
32#include <fstream>
33#include <filesystem>
34#include <exception>
35#include <gamspath.h>
36
37#ifdef _WIN32
38#include <stdlib.h>
39#endif
40
41using namespace gams;
42using namespace std;
43using namespace std::string_literals;
44
45static string getDataText()
46{
47 string data {
48 R"(
49Sets
50 i canning plants / seattle, san-diego /
51 j markets / new-york, chicago, topeka / ;
52
53Parameters
54
55 a(i) capacity of plant i in cases
56 / seattle 350
57 san-diego 600 /
58
59 b(j) demand at market j in cases
60 / new-york 325
61 chicago 300
62 topeka 275 / ;
63
64Table d(i,j) distance in thousands of miles
65 new-york chicago topeka
66 seattle 2.5 1.7 1.8
67 san-diego 2.5 1.8 1.4 ;
68
69Scalar f freight in dollars per case per thousand miles / 90 /
70 bmult demand multiplier / 1 /;
71 )"s
72 };
73 return data;
74}
75
76static string getModelText()
77{
78 string model {
79 R"(
80Sets
81 i canning plants
82 j markets;
83
84Parameters
85 a(i) capacity of plant i in cases
86 b(j) demand at market j in cases
87 d(i,j) distance in thousands of miles
88Scalar f freight in dollars per case per thousand miles
89 bmult demand multiplier;
90
91$if not set gdxincname $abort 'no include file name for data file provided'
92$gdxLoad %gdxincname% i j a b d f bmult
93
94$echo test > test.txt
95
96Parameter c(i,j) transport cost in thousands of dollars per case ;
97 c(i,j) = f * d(i,j) / 1000 ;
98
99Variables
100 x(i,j) shipment quantities in cases
101 z total transportation costs in thousands of dollars ;
102
103Positive Variable x ;
104
105Equations
106 cost define objective function
107 supply(i) observe supply limit at plant i
108 demand(j) satisfy demand at market j ;
109
110cost .. z =e= sum((i,j), c(i,j)*x(i,j)) ;
111supply(i) .. sum(j, x(i,j)) =l= a(i) ;
112demand(j) .. sum(i, x(i,j)) =g= bmult*b(j) ;
113
114Model transport /all/ ;
115
116Solve transport using lp minimizing z ;
117
118Scalar ms 'model status', ss 'solve status';
119
120Display x.l, x.m ;
121 )"s
122 };
123 return model;
124}
125
135int main(int argc, char* argv[])
136{
137 cout << "---------- Transport Engine --------------" << endl;
138
139 GAMSWorkspaceInfo wsInfo;
140 if (argc > 1)
141 wsInfo.setSystemDirectory(argv[1]);
142 GAMSWorkspace ws(wsInfo);
143
144 filesystem::remove(ws.workingDirectory().append("/test.txt"));
145
146 string envValues[4] = {};
147
148 string envPrefix("ENGINE_");
149 int index = 0;
150 for (const string &id : {"URL", "USER", "PASSWORD", "NAMESPACE"}) {
151
152#ifdef _WIN32
153 char *buffer;
154 size_t len;
155 errno_t err = _dupenv_s( &buffer, &len, (envPrefix + id).c_str() );
156 if (err == 0 && buffer) {
157 envValues[index++] = string(buffer);
158 free(buffer);
159 } else {
160 free(buffer);
161 cerr << "No ENGINE_" << id << " set" << endl;
162 return -1;
163 }
164#else
165 const char* value = getenv((envPrefix + id).c_str());
166 if(!value) {
167 cerr << "No ENGINE_" << id << " set" << endl;
168 return -1;
169 } else {
170 envValues[index++] = value;
171 }
172#endif
173 }
174
175 GAMSEngineConfiguration engineConf(envValues[0], envValues[1],
176 envValues[2], envValues[3]);
177
178 // run with data from a string with GAMS syntax with explicit export to GDX file
179 GAMSJob jobData = ws.addJobFromString(getDataText());
180 GAMSOptions defaultOptions = ws.addOptions();
181
182 cout << "Run 1" << endl;
183 jobData.runEngine(engineConf, &defaultOptions, nullptr, &cout);
184 jobData.outDB().doExport(filesystem::absolute(ws.workingDirectory().append("/tdata.gdx")).string());
185
186 map<string, double> expectedLevels = { { "seattle.new-york", 0.0 },
187 { "seattle.chicago", 300.0 },
188 { "seattle.topeka", 0.0 },
189 { "san-diego.new-york", 325.0 },
190 { "san-diego.chicago", 0.0 },
191 { "san-diego.topeka", 275.0 }
192 };
193
194
195 GAMSJob jobModel = ws.addJobFromString(getModelText());
196
197 GAMSOptions opt = ws.addOptions();
198 opt.setDefine("gdxincname", "tdata.gdx");
199 opt.setAllModelTypes("xpress");
200
201 // run a job using an instance of GAMSOptions that defines the data include file
202 cout << "Run 2" << endl;
203 jobModel.runEngine(engineConf, &opt, nullptr, &cout, {}, { "tdata.gdx" },
204 {
205 { "inex_string", "{\"type\": \"include\", \"files\": [\"*.gdx\"]}" }
206 });
207
208 for (GAMSVariableRecord &rec : jobModel.outDB().getVariable("x")) {
209 cout << "x(" << rec.key(0) << "," + rec.key(1) << "): level=" << rec.level()
210 << "marginal=" << rec.marginal() << "\n";
211
212 if (expectedLevels.at(rec.key(0).append(".").append(rec.key(1))) != rec.level()) {
213 cout << "Unexpected result, expected level: "
214 << expectedLevels.at(rec.key(0).append(".").append(rec.key(1)))
215 << endl;
216 return 1;
217 }
218 }
219
220 if (filesystem::exists(filesystem::path(ws.workingDirectory()).append("test.txt"))) {
221 cout << "Test.txt should not have sent back - inex_string failed" << endl;
222 return 1;
223 }
224
225 // same but with implicit database communication
226 GAMSCheckpoint cp = ws.addCheckpoint();
227 {
228 GAMSOptions opt = ws.addOptions();
229 GAMSJob jobA = ws.addJobFromString(getDataText());
230 GAMSJob jobB = ws.addJobFromString(getModelText());
231
232 cout << "Run 3" << endl;
233 jobA.runEngine(engineConf, &defaultOptions, nullptr, &cout);
234
235 opt.setDefine("gdxincname", jobA.outDB().name());
236 opt.setAllModelTypes("xpress");
237
238 cout << "Run 4" << endl;
239 jobB.runEngine(engineConf, &opt, &cp, &cout, vector<GAMSDatabase>{jobA.outDB()});
240
241 for (GAMSVariableRecord &rec : jobB.outDB().getVariable("x")) {
242 cout << "x(" << rec.key(0) << "," + rec.key(1) << "): level=" << rec.level()
243 << "marginal=" << rec.marginal() << "\n";
244
245 if (expectedLevels.at(rec.key(0).append(".").append(rec.key(1))) != rec.level()) {
246 cout << "Unexpected result, expected level: "
247 << expectedLevels.at(rec.key(0).append(".").append(rec.key(1)))
248 << endl;
249 return 1;
250 }
251 }
252 vector<map<string, double>> bmultExpected {
253 {{ "bmult", 0.9 }, { "ms", 1 }, { "ss", 1 }, { "obj", 138.31 } },
254 {{ "bmult", 1.2 }, { "ms", 4 }, { "ss", 1 }, { "obj", 184.41 } }
255 };
256
257 for (map<string, double> &m : bmultExpected) {
258 GAMSJob tEbmult = ws.addJobFromString("bmult=" + to_string(m["bmult"])
259 + "; solve transport min z use lp; ms=transport.modelstat; "
260 "ss=transport.solvestat;", cp);
261 try {
262 cout << "Run 5" << endl;
263 tEbmult.runEngine(engineConf, &defaultOptions, nullptr, &cout);
264 cout << "Scenario bmult=" << to_string(m["bmult"]) << ":" << endl;
265 cout << " Modelstatus: " << tEbmult.outDB().getParameter("ms").firstRecord().value() << endl;
266 cout << " Solvestatus: " << tEbmult.outDB().getParameter("ss").firstRecord().value() << endl;
267 cout << " Obj : " << tEbmult.outDB().getVariable("z").firstRecord().level() << endl;
268 if (tEbmult.outDB().getParameter("bmult").firstRecord().value() != m["bmult"]) {
269 cout << "Unexpected input, expected bmult: " + to_string(m["bmult"]) << endl;
270 return 1;
271 }
272 if (tEbmult.outDB().getParameter("ms").firstRecord().value() != m["ms"]) {
273 cout << "Unexpected result, expected ms: " + to_string(m["ms"]);
274 return 1;
275 }
276 if (tEbmult.outDB().getParameter("ss").firstRecord().value() != m["ss"]) {
277 cout << "Unexpected result, expected ss: " + to_string(m["ss"]);
278 return 1;
279 }
280 if (fabs(tEbmult.outDB().getVariable("z").firstRecord().level() - m["obj"]) > 0.01) {
281 cout << "Unexpected result, expected obj: " + to_string(m["obj"]);
282 return 1;
283 }
284
285 }
286 catch (exception ex) {
287 cout << "Exception caught:" << ex.what() << endl;
288 return 1;
289 }
290 }
291 }
292
293 // Example how to interrupt Engine Job
294 GAMSJob jc = ws.addJobFromGamsLib("clad");
295
296 // Define an option file for the solver to be used
297 string optFile1Path = filesystem::path(ws.workingDirectory().append("/cplex.opt")).string();
298 {
299 ofstream optFile1(optFile1Path);
300
301 // Set relative stopping tolerance to 0 initially
302 optFile1 << "epgap 0" << endl;
303 // Activate interactive option setting on interrupt
304 optFile1 << "interactive 1" << endl;
305 // Define new option file to read on interrupt
306 optFile1 << "iafile cplex.op2" << endl;
307 }
308
309 // Write new Cplex option file
310 string optFile2Path = filesystem::path(ws.workingDirectory().append("/cplex.op2")).string();
311 {
312 ofstream optFile2(filesystem::path(ws.workingDirectory().append("/cplex.op2")));
313 optFile2.open(optFile2Path);
314 optFile2 << "epgap 0.1" << endl;
315 optFile2.close();
316 }
317
318 opt = ws.addOptions();
319
320 opt.setMIP("cplex");
321 opt.setOptFile(1);
323
324 cout << "Run 6" << endl;
325
326 string logPath = filesystem::path(ws.workingDirectory().append("/ej.log")).string();
327 {
328 ofstream logFile(logPath);
329 thread optThread(&GAMSJob::runEngine, &jc, engineConf, &opt, nullptr, &logFile,
330 vector<GAMSDatabase>(), set<string> {optFile1Path, optFile2Path, "claddat.gdx"},
331 unordered_map<string, string>(), true, true);
332
333 while (filesystem::exists(logPath) && filesystem::is_empty(logPath))
334 this_thread::sleep_for(500ms);
335
336 jc.interrupt();
337 cout << "Interrupted Cplex to continue with new option" << endl;
338
339 if (optThread.joinable())
340 optThread.join();
341 }
342
343 {
344 string line;
345 ifstream inLog(logPath);
346 while (getline(inLog, line)) {
347 if (line.find("Interrupted") != string::npos) {
348 cout << "Interrupted succesfully" << endl;
349 return 0;
350 }
351 }
352 cout << "Expected the solver to be interrupted at least once." << endl;
353 return 1;
354 }
355
356 cout << "Interrupted succesfully" << endl;
357
358 return 0;
359}
void doExport(const std::string &filePath="")
GAMSParameter getParameter(const std::string &name)
std::string name()
GAMSVariable getVariable(const std::string &name)
void runEngine(const GAMSEngineConfiguration &engineConfiguration, GAMSOptions *gamsOptions, gams::GAMSCheckpoint *checkpoint, std::ostream *output=nullptr, const std::vector< gams::GAMSDatabase > &databases={}, const std::set< std::string > &extraModelFiles={}, const std::unordered_map< std::string, std::string > &engineOptions={}, bool createOutDB=true, bool removeResults=true)
bool interrupt()
GAMSDatabase outDB()
void setAllModelTypes(const std::string &solver)
void setMIP(const std::string &value)
void setDefine(const std::string &key, const std::string &value)
void setSolveLink(const GAMSOptions::ESolveLink::ESolveLinkEnum value)
void setOptFile(const int value)
GAMSParameterRecord firstRecord(const std::vector< std::string > &slice)
GAMSVariableRecord firstRecord(const std::vector< std::string > &slice)
void setSystemDirectory(std::string systemDir)