domainchecking.cpp
Go to the documentation of this file.
1/*
2  * GAMS - General Algebraic Modeling System C++ API
3  *
4  * Copyright (c) 2017-2022 GAMS Software GmbH <support@gams.com>
5  * Copyright (c) 2017-2022 GAMS Development Corp. <support@gams.com>
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a copy
8  * of this software and associated documentation files (the "Software"), to deal
9  * in the Software without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11  * copies of the Software, and to permit persons to whom the Software is
12  * furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in all
15  * copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23  * SOFTWARE.
24  */
25 #include "gams.h"
26 #include <map>
27 #include <vector>
28 #include <iostream>
29 #include <algorithm>
30 
31 using namespace gams;
32 using namespace std;
33 
34 std::string getDataText()
35 {
36  return "Sets \n"
37  "i canning plants / seattle, san-diego /; \n"
38  " \n"
39  "Alias (i,ii); \n";
40 }
41 
58 int main(int argc, char* argv[])
59 {
60  cout << "---------- Domain Checking --------------" << endl;
61 
62  // define some data by using C++ data structures
63  vector<string> plants { "Seattle", "San-Diego" };
64  vector<string> markets { "New-York", "Chicago", "Topeka" };
65  map<string, double> capacity { { "Seattle", 350.0 }, { "San-Diego", 600.0 } };
66  map<string, double> demand { { "New-York", 325.0 }, { "Chicago", 300.0 }, { "Topeka", 275.0 } };
67  map<tuple<string, string>, double> distance {
68  { make_tuple<string,string> ("Seattle", "New-York"), 2.5 },
69  { make_tuple<string,string> ("Seattle", "Chicago"), 1.7 },
70  { make_tuple<string,string> ("Seattle", "Topeka"), 1.8 },
71  { make_tuple<string,string> ("San-Diego", "New-York"), 2.5 },
72  { make_tuple<string,string> ("San-Diego", "Chicago"), 1.8 },
73  { make_tuple<string,string> ("San-Diego", "Topeka"), 1.4 }
74  };
75 
76 
77  try {
78  GAMSWorkspaceInfo wsInfo;
79  if (argc > 1)
80  wsInfo.setSystemDirectory(argv[1]);
81  GAMSWorkspace ws(wsInfo);
82  // prepare a GAMSDatabase with data from the C++ data structures
83  GAMSDatabase db = ws.addDatabase();
84 
85  // add two sets to the GAMSDatabase
86  GAMSSet i = db.addSet("i", "");
87  for (auto plant: plants)
88  i.addRecord(plant);
89  GAMSSet j = db.addSet("j", "");
90  for (auto market: markets)
91  j.addRecord(market);
92 
93  // add a parameter with domain information
94  GAMSParameter a = db.addParameter("a", "capacity at plant", i);
95  for (string plant: plants)
96  a.addRecord(plant).setValue(capacity[plant]);
97 
98  // if we see a domain violation something went wrong
99  if (!a.checkDomains()) {
100  cout << "*** Unexpected domain violation in a" << endl;
101  return 1;
102  }
103 
104  // add a parameter with relaxed domain information
105  GAMSParameter b = db.addParameter("b", "demand at market j in cases", "j");
106  for (string market: markets)
107  b.addRecord(market).setValue(demand[market]);
108 
109  // if we see a domain violation something went wrong
110  if (!b.checkDomains()) {
111  cout << "*** Unexpected domain violation in b" << endl;
112  return 1;
113  }
114 
115  // add a 2-dim parameter with domain information
116  GAMSParameter d = db.addParameter("d", "distance in thousands of miles", i, j);
117  for (auto t : distance) {
118  auto tuple = t.first;
119  auto t1 = get<0>(tuple);
120  auto t2 = get<1>(tuple);
121  d.addRecord(t1, t2).setValue(distance[tuple]);
122  }
123 
124  // if we see a domain violation something went wrong
125  if (!d.checkDomains()) {
126  cout << "*** Unexpected domain violation in d" << endl;
127  return 1;
128  }
129 
130  // if we see a domain violation in the database something went wrong
131  if (!db.checkDomains()) {
132  cout << "*** Unexpected domain violation in db" << endl;
133  return 1;
134  }
135 
136  // create some "wrong" entries
137  d.addRecord("Seattle", "aa").setValue(1);
138  d.addRecord("bb", "Seattle").setValue(1);
139  a.addRecord("aa").setValue(1);
140  a.addRecord("bb").setValue(1);
141  b.addRecord("aa").setValue(1);
142  b.addRecord("bb").setValue(1);
143 
144  // now the GAMSdatabase as well as the symbols a and d should have domain violations
145  if (db.checkDomains()) {
146  cout << "*** Domain violation for db not recognized" << endl;
147  return 1;
148  }
149  if (a.checkDomains()) {
150  cout << "*** Domain violation for a not recognized" << endl;
151  return 1;
152  }
153  if (d.checkDomains()) {
154  cout << "*** Domain violation for d not recognized" << endl;
155  return 1;
156  }
157 
158  // b in contrast was defined with realxed domain info only, therefore we should never see a domain violation
159  if (!b.checkDomains()) {
160  cout << "*** Unexpected domain violation in b" << endl;
161  return 1;
162  }
163 
164  // for a we should see 2 domain violations ("aa" and "bb")
165  int dvCnt = 0;
166  cout << "Domain Violations of a:" << endl;
167  for (GAMSSymbolDomainViolation sdv: a.getSymbolDVs()) {
168  cout << " > ";
169  for (bool vi: sdv.violInd())
170  cout << (vi ? "true " : "false ");
171  cout << "<> ";
172  for (string k: sdv.violRec().keys())
173  cout << k << " ";
174  cout << "<<" << endl;
175  dvCnt++;
176  }
177  if (dvCnt != 2) {
178  cout << "*** Number of domain violations for a should be 2 but saw " << dvCnt << endl;
179  return 1;
180  }
181 
182  // for d we should see 3 domain violations ("Seattle", *"aa"*; *"bb"*, *"Seattle"*)
183  dvCnt = 0;
184  cout << "Domain Violations of d:" << endl;
185  for (GAMSSymbolDomainViolation sdv: d.getSymbolDVs()) {
186  cout << " > ";
187  for (bool vi: sdv.violInd()) {
188  cout << (vi ? "true " : "false ");
189  if (vi)
190  dvCnt++;
191  }
192  cout << "<> ";
193  for (string k: sdv.violRec().keys())
194  cout << k << " ";
195  cout << "<<" << endl;
196  }
197  if (dvCnt != 3) {
198  cout << "*** Number of domain violations for a should be 3 but saw " << dvCnt << endl;
199  return 1;
200  }
201 
202  // for db we should see 5 domain violations (all the ones from a and d)
203  dvCnt = 0;
204  cout << "Domain Violations of db:" << endl;
206  cout << " > " + DDV.violSym().name() + ": " << endl;
207  for (GAMSSymbolDomainViolation sdv: DDV.violRecs()) {
208  cout << " ";
209  for (bool vi: sdv.violInd()) {
210  cout << vi << " ";
211  if (vi)
212  dvCnt++;
213  }
214  cout << "<> ";
215  for (string k: sdv.violRec().keys())
216  cout << k << " ";
217  cout << "<<" << endl;
218  }
219  }
220  if (dvCnt != 5) {
221  cout << "*** Number of domain violations for db should be 5 but saw " << dvCnt << endl;
222  return 1;
223  }
224 
225  // now we limit the amount of violated records reported to a total of 3
226  dvCnt = 0;
227  cout << "Domain Violations of db:" << endl;
229  cout << " > " + DDV.violSym().name() + ": " << endl;
230  for (GAMSSymbolDomainViolation sdv: DDV.violRecs()) {
231  cout << " ";
232  for (bool vi: sdv.violInd())
233  cout << vi << " ";
234  cout << "<> ";
235  for (string k: sdv.violRec().keys())
236  cout << k << " ";
237  cout << "<<" << endl;
238  dvCnt++;
239  }
240  }
241  if (dvCnt != 3) {
242  cout << "*** Number of domain violations for db should be 3 but saw " << dvCnt << endl;
243  return 1;
244  }
245  // now we limit the amount of violated records reported to 1 per symbol
246  dvCnt = 0;
247  cout << "Domain Violations of db:" << endl;
248  for (GAMSDatabaseDomainViolation DDV: db.getDatabaseDVs(0, 1)) {
249  cout << " > " + DDV.violSym().name() + ": " << endl;
250  for (GAMSSymbolDomainViolation sdv: DDV.violRecs()) {
251  cout << " ";
252  for (bool vi: sdv.violInd())
253  cout << vi << " ";
254  cout << "<> ";
255  for (string k: sdv.violRec().keys())
256  cout << k << " ";
257  cout << "<<" << endl;
258  dvCnt++;
259  }
260  }
261  if (dvCnt != 2) {
262  cout << "*** Number of domain violations for db should be 2 but saw " << dvCnt << endl;
263  return 1;
264  }
265 
266  // by default we should get an exception when exporting a GAMSDatabase with domain violations
267  bool sawException = false;
268  try {
269  db.doExport("test.gdx");
270  } catch (...) {
271  sawException = true;
273  db.doExport("test.gdx");
274  }
275  if (!sawException) {
276  cout << "*** It should not be possible to export a GAMSDatabase containing domain violations by default" << endl;
277  return 1;
278  }
279 
280  // read a parameter with domain info from gdx
281  GAMSDatabase db2 = ws.addDatabaseFromGDX("test.gdx");
282  GAMSParameter d2 = db2.getParameter("d");
283 
284  // the domain of the parameter should be GAMSSet i and GAMSSet j
285  for (GAMSDomain item: d2.domains()) {
286  if (!item.isRelaxed()) {
287  GAMSSet domSet = item.getSet();
288  if (domSet.name() == "i") {
289  for (GAMSSetRecord uel: domSet)
290  if (find(plants.begin(), plants.end(), uel.key(0)) == plants.end()) {
291  cout << "*** Unexpected uel " + uel.key(0) + " found in domain i" << endl;
292  return 1;
293  }
294  } else if (domSet.name() == "j") {
295  for (GAMSSetRecord uel: domSet)
296  if (find(markets.begin(), markets.end(), uel.key(0)) == markets.end()) {
297  cout << "*** Unexpected uel " + uel.key(0) + " found in domain j" << endl;
298  return 1;
299  }
300  } else {
301  cout << "*** Expected GAMSSet i and j but found " + domSet.name() << endl;
302  return 1;
303  }
304  }
305  else
306  {
307  cout << "*** Expected GAMSSet as domain but found relaxed domain " + item.name() << endl;
308  return 1;
309  }
310  }
311 
312  /* *************************************************************** *
313  * This next section is acutally not about domain checking, but we *
314  * make sure that certain things are working as expected. *
315  * *************************************************************** */
316 
317  // Try reading an Alias as Set
318  GAMSJob jAlias = ws.addJobFromString(getDataText());
319  jAlias.run();
320  GAMSSet ii = jAlias.outDB().getSet("ii");
321  cout << "Elements of aliased Set:" << endl;
322  for (GAMSSetRecord item: ii)
323  cout << " > " + item.key(0) << endl;
324 
325  GAMSDatabase testDB = ws.addDatabase();
326  GAMSSet testSet = testDB.addSet("test", 1);
327 
328  // Try adding empty UEL
329  testSet.addRecord("");
330  cout << "Elements of test Set after adding empty UEL:" << endl;
331  cout << " > " << testSet.numberRecords() << endl;
332 
333  // GAMS strips pending blanks while leading blanks are relevant
334  testSet.addRecord(" a ").setText("a");
335  cout << "Record ' a ' should be the same as ' a':" << endl;
336  cout << " > " << testSet.findRecord(" a").text() << endl;
337 
338  // GAMS cannot handle UELs with more than 63 characters
339  // This should be OK ...
340  testSet.addRecord("123456789012345678901234567890123456789012345678901234567890123 ").setText("OK");
341  // ... but not this
342  sawException = false;
343  try {
344  testSet.addRecord("1234567890123456789012345678901234567890123456789012345678901234").setText("not OK");
345  } catch (...) {
346  sawException = true;
347  }
348  if (!sawException) {
349  cout << "*** It should not be possible to add a record with more than 63 characters" << endl;
350  return 1;
351  }
352 
353  // GAMS cannot handle explanatory texts with more than 255 characters
354  testDB.addSet("textOK", "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345");
355  sawException = false;
356  try {
357  testDB.addSet("textNotOK", "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456");
358  } catch (...) {
359  sawException = true;
360  }
361  if (!sawException) {
362  cout << "*** It should not be possible to add an explanatory text with more than 255 characters" << endl;
363  return 1;
364  }
365 
366  testSet.addRecord("OK").setText("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345");
367  sawException = false;
368  try {
369  testSet.addRecord("notOK").setText("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456");
370  } catch (...) {
371  sawException = true;
372  }
373  if (!sawException) {
374  cout << "*** It should not be possible to add an explanatory text with more than 255 characters" << endl;
375  return 1;
376  }
377 
378  // GAMS can handle UELs containing single and double quotes but not at the same time
379  testSet.addRecord("quote'");
380  testSet.addRecord("quote\"");
381  sawException = false;
382  try {
383  testSet.addRecord("quote'\"");
384  } catch (...) {
385  sawException = true;
386  }
387  if (!sawException) {
388  cout << "*** It should not be possible to add a record single AND double quote" << endl;
389  return 1;
390  }
391 
392  testDB.doExport("test.gdx");
393  } catch (GAMSException &ex) {
394  cout << "GAMSException occured: " << ex.what() << endl;
395  } catch (exception &ex) {
396  cout << ex.what() << endl;
397  }
398 
399  return 0;
400 }
void setSystemDirectory(std::string systemDir)
GAMSSetRecord findRecord(const std::vector< std::string > &keys)
std::vector< GAMSDatabaseDomainViolation > getDatabaseDVs(int maxViol=0, int maxViolPerSym=0)
std::string & name() const
GAMSSet addSet(const std::string &name, const int dimension, const std::string &explanatoryText="", GAMSEnum::SetType setType=GAMSEnum::SetType::Multi)
int numberRecords() const
GAMSParameterRecord addRecord(const std::vector< std::string > &keys)
std::vector< GAMSDomain > domains()
GAMSDatabase outDB()
std::vector< GAMSSymbolDomainViolation > getSymbolDVs(int maxViol=0)
void setSuppressAutoDomainChecking(bool value)
GAMSSetRecord addRecord(const std::vector< std::string > &keys)
GAMSParameter addParameter(const std::string &name, const int dimension, const std::string &explanatoryText="")
GAMSSet getSet(const std::string &name)
std::string text()
void setValue(const double val)
GAMSParameter getParameter(const std::string &name)
void doExport(const std::string &filePath="")
void setText(const std::string &text)