this repo has no description
at develop 17 kB view raw
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_components.hh> 8#include <minizinc/solvers/nl/nl_file.hh> 9 10#include <utility> 11 12using namespace std; 13 14namespace MiniZinc { 15 16/* *** *** *** NLBound *** *** *** */ 17 18// Constructors 19NLBound::NLBound(Bound tag, double lb, double ub) : tag(tag), lb(lb), ub(ub) {} 20 21NLBound NLBound::makeBounded(double lb, double ub) { 22 if (lb == ub) { 23 return makeEqual(lb); 24 } 25 assert(lb < ub); 26 return NLBound(LB_UB, lb, ub); 27} 28 29NLBound NLBound::makeUBBounded(double ub) { return NLBound(UB, 0, ub); } 30 31NLBound NLBound::makeLBBounded(double lb) { return NLBound(LB, lb, 0); } 32 33NLBound NLBound::makeNoBound() { return NLBound(NONE, 0, 0); } 34 35NLBound NLBound::makeEqual(double val) { return NLBound(EQ, val, val); } 36 37/** Update the lower bound */ 38void NLBound::updateLB(double new_lb) { 39 switch (tag) { 40 // LB <= var <= UB. Same tag 41 case NLBound::LB_UB: { 42 assert(new_lb <= ub); 43 if (new_lb > lb) { 44 lb = new_lb; 45 } 46 break; 47 } 48 // var <= UB. Update tag 49 case NLBound::UB: { 50 assert(new_lb <= ub); 51 tag = LB_UB; 52 lb = new_lb; 53 break; 54 } 55 // LB <= var. Same tag 56 case NLBound::LB: { 57 if (new_lb > lb) { 58 lb = new_lb; 59 } 60 break; 61 } 62 // No bound. Update tag 63 case NLBound::NONE: { 64 tag = LB; 65 lb = new_lb; 66 break; 67 } 68 // LB = var = UB. Should not happen 69 case NLBound::EQ: { 70 should_not_happen( 71 "Updating a bound already set to \"equals\". We only allow tightening update."); 72 } 73 } 74} 75 76/** Update the upper bound */ 77void NLBound::updateUB(double new_ub) { 78 switch (tag) { 79 // LB <= var <= UB. Same tag 80 case NLBound::LB_UB: { 81 assert(lb <= new_ub); 82 if (new_ub < ub) { 83 ub = new_ub; 84 } 85 break; 86 } 87 // var <= UB. Same tag 88 case NLBound::UB: { 89 if (new_ub < ub) { 90 ub = new_ub; 91 } 92 break; 93 } 94 // LB <= var. Update tag 95 case NLBound::LB: { 96 assert(lb <= new_ub); 97 tag = LB_UB; 98 ub = new_ub; 99 break; 100 } 101 // No bound. Update tag 102 case NLBound::NONE: { 103 tag = UB; 104 ub = new_ub; 105 break; 106 } 107 // LB = var = UB. Should not happen 108 case NLBound::EQ: { 109 should_not_happen( 110 "Updating a bound already set to \"equals\". We only allow tightening update."); 111 } 112 } 113} 114 115/** Update equal bound */ 116void NLBound::updateEq(double new_eq) { 117 switch (tag) { 118 // LB <= var <= UB. Update tag 119 case NLBound::LB_UB: { 120 assert(lb <= new_eq && new_eq <= ub); 121 tag = EQ; 122 lb = new_eq; 123 ub = new_eq; 124 } 125 // var <= UB. Update tag 126 case NLBound::UB: { 127 assert(new_eq <= ub); 128 tag = EQ; 129 lb = new_eq; 130 ub = new_eq; 131 } 132 // LB <= var. Update tag 133 case NLBound::LB: { 134 assert(lb <= new_eq); 135 tag = EQ; 136 lb = new_eq; 137 ub = new_eq; 138 } 139 // No bound. Update tag 140 case NLBound::NONE: { 141 tag = EQ; 142 lb = new_eq; 143 ub = new_eq; 144 } 145 // LB = var = UB. Can happen only if we "update" with the same bound. 146 case NLBound::EQ: { 147 assert(lb == new_eq); 148 } 149 } 150} 151 152/** Printing with a name. */ 153ostream& NLBound::printToStream(ostream& os, const string& vname) const { 154 switch (tag) { 155 case LB_UB: { 156 os << "0 " << lb << " " << ub << " # " << lb << " =< " << vname << " =< " << ub; 157 break; 158 } 159 case UB: { 160 os << "1 " << ub << " # " << vname << " =< " << ub; 161 break; 162 } 163 case LB: { 164 os << "2 " << lb << " # " << lb << " =< " << vname; 165 break; 166 } 167 case NONE: { 168 os << "3" 169 << " # No constraint"; 170 break; 171 } 172 case EQ: { 173 os << "4 " << lb << " # " << vname << " = " << lb; 174 break; 175 } 176 } 177 return os; 178} 179 180/** Default printing */ 181ostream& NLBound::printToStream(ostream& os) const { 182 printToStream(os, "body"); 183 return os; 184} 185 186/* *** *** *** NLVar *** *** *** */ 187 188NLVar NLVar::copyWithBound(NLBound bound) const { 189 NLVar v = NLVar(*this); // copy constructor 190 v.bound = bound; 191 return v; 192} 193 194/* *** *** *** NLArray *** *** *** */ 195 196/* *** *** *** NLToken *** *** *** */ 197 198const char* NLToken::getName(OpCode oc) { 199 switch (oc) { 200 case OpCode::OPPLUS: 201 return "OPPLUS"; 202 case OpCode::OPMINUS: 203 return "OPMINUS"; 204 case OpCode::OPMULT: 205 return "OPMULT"; 206 case OpCode::OPDIV: 207 return "OPDIV"; 208 case OpCode::OPREM: 209 return "OPREM"; 210 case OpCode::OPPOW: 211 return "OPPOW"; 212 case OpCode::OPLESS: 213 return "OPLESS"; 214 case OpCode::FLOOR: 215 return "FLOOR"; 216 case OpCode::CEIL: 217 return "CEIL"; 218 case OpCode::ABS: 219 return "ABS"; 220 case OpCode::OPUMINUS: 221 return "OPUMINUS"; 222 case OpCode::OPOR: 223 return "OPOR"; 224 case OpCode::OPAND: 225 return "OPAND"; 226 case OpCode::LT: 227 return "LT"; 228 case OpCode::LE: 229 return "LE"; 230 case OpCode::EQ: 231 return "EQ"; 232 case OpCode::GE: 233 return "GE"; 234 case OpCode::GT: 235 return "GT"; 236 case OpCode::NE: 237 return "NE"; 238 case OpCode::OPNOT: 239 return "OPNOT"; 240 case OpCode::OPIFnl: 241 return "OPIFnl"; 242 case OpCode::OP_tanh: 243 return "OP_tanh"; 244 case OpCode::OP_tan: 245 return "OP_tan"; 246 case OpCode::OP_sqrt: 247 return "OP_sqrt"; 248 case OpCode::OP_sinh: 249 return "OP_sinh"; 250 case OpCode::OP_sin: 251 return "OP_sin"; 252 case OpCode::OP_log10: 253 return "OP_log10"; 254 case OpCode::OP_log: 255 return "OP_log"; 256 case OpCode::OP_exp: 257 return "OP_exp"; 258 case OpCode::OP_cosh: 259 return "OP_cosh"; 260 case OpCode::OP_cos: 261 return "OP_cos"; 262 case OpCode::OP_atanh: 263 return "OP_atanh"; 264 case OpCode::OP_atan2: 265 return "OP_atan2"; 266 case OpCode::OP_atan: 267 return "OP_atan"; 268 case OpCode::OP_asinh: 269 return "OP_asinh"; 270 case OpCode::OP_asin: 271 return "OP_asin"; 272 case OpCode::OP_acosh: 273 return "OP_acosh"; 274 case OpCode::OP_acos: 275 return "OP_acos"; 276 case OpCode::OPintDIV: 277 return "OPintDIV"; 278 case OpCode::OPprecision: 279 return "OPprecision"; 280 case OpCode::OPround: 281 return "OPround"; 282 case OpCode::OPtrunc: 283 return "OPtrunc"; 284 case OpCode::OPATLEAST: 285 return "OPATLEAST"; 286 case OpCode::OPATMOST: 287 return "OPATMOST"; 288 case OpCode::OPPLTERM: 289 return "OPPLTERM"; 290 case OpCode::OPIFSYM: 291 return "OPIFSYM"; 292 case OpCode::OPEXACTLY: 293 return "OPEXACTLY"; 294 case OpCode::OPNOTATLEAST: 295 return "OPNOTATLEAST"; 296 case OpCode::OPNOTATMOST: 297 return "OPNOTATMOST"; 298 case OpCode::OPNOTEXACTLY: 299 return "OPNOTEXACTLY"; 300 case OpCode::OPIMPELSE: 301 return "OPIMPELSE"; 302 case OpCode::OP_IFF: 303 return "OP_IFF"; 304 case OpCode::OPSOMESAME: 305 return "OPSOMESAME"; 306 case OpCode::OP1POW: 307 return "OP1POW"; 308 case OpCode::OP2POW: 309 return "OP2POW"; 310 case OpCode::OPCPOW: 311 return "OPCPOW"; 312 case OpCode::OPFUNCALL: 313 return "OPFUNCALL"; 314 case OpCode::OPNUM: 315 return "OPNUM"; 316 case OpCode::OPHOL: 317 return "OPHOL"; 318 case OpCode::OPVARVAL: 319 return "OPVARVAL"; 320 case OpCode::N_OPS: 321 return "N_OPS"; 322 default: 323 assert(false); 324 return nullptr; 325 } 326}; 327 328const char* NLToken::getName(MOpCode moc) { 329 switch (moc) { 330 case MOpCode::MINLIST: 331 return "MINLIST"; 332 case MOpCode::MAXLIST: 333 return "MAXLIST"; 334 case MOpCode::OPSUMLIST: 335 return "OPSUMLIST"; 336 case MOpCode::OPCOUNT: 337 return "OPCOUNT"; 338 case MOpCode::OPNUMBEROF: 339 return "OPNUMBEROF"; 340 case MOpCode::OPNUMBEROFs: 341 return "OPNUMBEROFs"; 342 case MOpCode::ANDLIST: 343 return "ANDLIST"; 344 case MOpCode::ORLIST: 345 return "ORLIST"; 346 case MOpCode::OPALLDIFF: 347 return "OPALLDIFF"; 348 default: 349 assert(false); 350 return nullptr; 351 } 352} 353 354NLToken NLToken::n(double value) { 355 NLToken tok; 356 tok.kind = Kind::NUMERIC; 357 tok.numericValue = value; 358 return tok; 359} 360 361NLToken NLToken::v(string vname) { 362 NLToken tok; 363 tok.kind = Kind::VARIABLE; 364 tok.str = std::move(vname); 365 return tok; 366} 367 368NLToken NLToken::o(OpCode opc) { 369 NLToken tok; 370 tok.kind = Kind::OP; 371 tok.oc = opc; 372 return tok; 373} 374 375NLToken NLToken::mo(MOpCode mopc, int nb) { 376 NLToken tok; 377 tok.kind = Kind::MOP; 378 tok.moc = mopc; 379 tok.argCount = nb; 380 return tok; 381} 382 383bool NLToken::isVariable() const { return kind == VARIABLE; } 384 385bool NLToken::isConstant() const { return kind == NUMERIC; } 386 387ostream& NLToken::printToStream(ostream& os, const NLFile& nl_file) const { 388 switch (kind) { 389 case Kind::NUMERIC: { 390 os << "n" << numericValue; 391 break; 392 } 393 394 case Kind::VARIABLE: { 395 os << "v" << nl_file.variableIndexes.at(str) << " # " << str; 396 break; 397 } 398 399 case Kind::STRING: { 400 should_not_happen("NL string token (Kind::STRING) not implemented"); 401 } 402 403 case Kind::FUNCALL: { 404 should_not_happen("NL function call token (Kind::FUNCALL) not implemented"); 405 } 406 407 case Kind::OP: { 408 os << "o" << oc << " # " << getName(oc); 409 break; 410 } 411 412 case Kind::MOP: { 413 os << "o" << moc << " # " << getName(moc) << " " << argCount << endl; 414 os << argCount; 415 break; 416 } 417 418 default: 419 should_not_happen("Unknown token kind: " << kind); 420 } 421 422 return os; 423} 424 425/* *** *** *** NLAlgCons *** *** *** */ 426 427/** Method to build the var_coeff vector. */ 428void NLAlgCons::setJacobian(const vector<string>& vnames, const vector<double>& coeffs, 429 NLFile* nl_file) { 430 assert(vnames.size() == coeffs.size()); 431 for (int i = 0; i < vnames.size(); ++i) { 432 string vn = vnames[i]; 433 nl_file->variables.at(vn).jacobianCount++; 434 jacobian.emplace_back(vn, coeffs[i]); 435 } 436} 437 438/** A constraint is considered linear if the expressionGraph is empty. */ 439bool NLAlgCons::isLinear() const { return expressionGraph.empty(); } 440 441/** Printing. */ 442ostream& NLAlgCons::printToStream(ostream& os, const NLFile& nl_file) const { 443 int idx = nl_file.constraintIndexes.at(name); 444 445 // Print the 'C' segment: if no expression graph, print "n0". 446 os << "C" << idx << " # Non linear part of " << name << endl; 447 if (expressionGraph.empty()) { 448 os << "n0 # No non linear part coded as the value '0'" << endl; 449 } else { 450 for (const auto& t : expressionGraph) { 451 t.printToStream(os, nl_file); 452 os << endl; 453 } 454 } 455 456 // Print the 'J' segment if present. 457 if (!jacobian.empty()) { 458 os << "J" << idx << " " << jacobian.size() << " # Linear part of " << name << endl; 459 for (const auto& vn_coef : jacobian) { 460 os << nl_file.variableIndexes.at(vn_coef.first) << " " << vn_coef.second << " # " 461 << vn_coef.first << endl; 462 } 463 } 464 465 return os; 466} 467 468/* *** *** *** NLLogicalCons *** *** *** */ 469 470/** Printing. */ 471ostream& NLLogicalCons::printToStream(ostream& os, const NLFile& nl_file) const { 472 os << "L" << index << " # Logical constraint " << name << endl; 473 for (const auto& t : expressionGraph) { 474 t.printToStream(os, nl_file); 475 os << endl; 476 } 477 478 return os; 479} 480 481/* *** *** *** NLHeader *** *** *** */ 482 483/** Printing the header. 484 * The header is composed of then lines that we describe as we proceed. 485 * A '#' starts a comment until the end of the line. However, it cannot be a line on its own!*/ 486ostream& NLHeader::printToStream(ostream& os, const NLFile& nl_file) { 487 // 1st line: 488 // 'g': file will be in text format 489 // other numbers: as given in the doc (no other explanation...) 490 os << "g3 1 1 0" << endl; 491 492 // 2nd line: 493 os << nl_file.variables.size() << " " // Total number of variables 494 << nl_file.constraints.size() 495 << " " // Total number of algebraic constraint (including 'range' and 'eq') 496 << 1 << " " // Always 1 objective 497 << nl_file.algConsRangeCount << " " // Number of algebraic range constraints 498 << nl_file.algConsEqCount << " " // Number of algebraic eq constraints 499 << nl_file.logicalConstraints.size() << " " // Number of logical constraints 500 << "# Total nb of: variables, algebraic constraints, objectives, ranges, eqs, logical " 501 "constraints" 502 << endl; 503 504 // 3rd line: Nonlinear and complementary information 505 os << nl_file.cnames_nl_general.size() << " " // Non linear constraints 506 << (nl_file.objective.isLinear() ? 0 : 1) << " " // Non linear objective 507 << "# Nb of nonlinear constraints, nonlinar objectives." << endl; 508 /* This was found in the online source of the ASL parser, but is not produce in our ampl tests. 509 * If needed, should be put on the same line 510 << nb_complementarity_linear_conditions << " " 511 << nb_complementarity_nonlinear_conditions << " " 512 << nb_complementarity_items_double_inequalities << " " 513 << nb_complemented_vars_non_zero_lowerbound << " " 514 << "Nb of complementary: linear & nonlinear conditions, double inequalities, vars with non-0 lower 515 bound." 516 << endl; 517 */ 518 519 // 4th line: Network constraints 520 os << nl_file.cnames_nl_network.size() << " " // Number of nonlinear network constraints 521 << nl_file.cnames_lin_network.size() << " " // Number of linear network constraints 522 << "# Nb of network constraints: nonlinear, linear." << endl; 523 524 // 5th line: nonlinear variables: 525 os << nl_file.lvcCount() << " " // Nb of nonlinear vars in constraints 526 << nl_file.lvoCount() << " " // Nb of nonlinear vars in objectives 527 << nl_file.lvbCount() << " " // Nb of nonlinear vars in both 528 << "# Nb of non linear vars in: constraints, objectives, both." << endl; 529 530 // 6th line: 531 os << nl_file.wvCount() << " " // Nb of linear network vars 532 << "0" 533 << " " // Nb of functions. Not Implemented 534 << "0 1 " 535 << "# Nb of: linear network vars, functions. Floating point arithmetic mode (TEXT == 0). " 536 "Flag: if 1, add .sol suffixe." 537 << endl; 538 539 // 7th line: discrete variables 540 os << nl_file.bvCount() << " " // Nb of linear binary vars 541 << nl_file.ivCount() << " " // Nb of linear integer vars 542 << nl_file.lvbiCount() << " " // Nb of nonlinear integer vars in both 543 << nl_file.lvciCount() << " " // Nb of nonlinear integer vars in constraints only 544 << nl_file.lvoiCount() << " " // Nb of nonlinear integer vars in objectives only 545 << "# Nb of linear vars: binary, integer (non binary). " 546 << "Nb of nonlinear integer vars in: both, constraints only, objectives only." << endl; 547 548 // 8th line: non zeros 549 os << nl_file.jacobianCount() << " " // Nb of nonzero in jacobian 550 << nl_file.objective.gradientCount() << " " // Nb of nonzero in gradient 551 << "# Nb of non zeros in: jacobian, objective gradients." << endl; 552 553 // 9th line: name length. Our tests always produce 0... 554 os << "0" 555 << " " // Max constraint name length (??) 556 << "0" 557 << " " // Max var name length (??) 558 << "# Longest name among: contraints' name, vars' name." << endl; 559 560 // 10th line: common expressions. Not enough infor for now... 561 os << "0" 562 << " " // Nb common exprs in both 563 << "0" 564 << " " // Nb common exprs in constraints only 565 << "0" 566 << " " // Nb common exprs in objectives only 567 << "0" 568 << " " // Nb common exprs in single constraint only 569 << "0" 570 << " " // Nb common exprs in single objectives only 571 << "# Nb of common expressions in: both, constraints only, objectives only, single " 572 "constraint, single objective."; 573 574 return os; 575} 576 577/* *** *** *** NLObjective *** *** *** */ 578 579/** Gradient count. */ 580int NLObjective::gradientCount() const { return gradient.size(); } 581 582/** Set the gradient. */ 583void NLObjective::setGradient(const vector<string>& vnames, const vector<double>& coeffs) { 584 assert(vnames.size() == coeffs.size()); 585 for (int i = 0; i < vnames.size(); ++i) { 586 string vn = vnames[i]; 587 gradient.emplace_back(vn, coeffs[i]); 588 } 589} 590 591/** A objective is considered as linear if its expression graph is non empty. */ 592bool NLObjective::isDefined() const { return minmax != UNDEF; } 593 594bool NLObjective::isLinear() const { return expressionGraph.empty(); } 595 596bool NLObjective::isOptimisation() const { return minmax >= MINIMIZE; } 597 598/** Printing. */ 599ostream& NLObjective::printToStream(ostream& os, const NLFile& nl_file) const { 600 if (minmax != UNDEF) { 601 if (minmax == SATISFY) { 602 os << "O0 0 # Satisfy objectif implemented as 'minimize 0'" << endl; 603 os << "n0" << endl; 604 } else { 605 os << "O0 " << minmax << " # Objectif (0: minimize, 1: maximize)" << endl; 606 if (expressionGraph.empty()) { 607 os << "n0 # No expression graph" << endl; 608 } else { 609 for (const auto& tok : expressionGraph) { 610 tok.printToStream(os, nl_file) << endl; 611 } 612 } 613 // Print gradient 614 if (!gradient.empty()) { 615 os << "G0 " << gradient.size() << " # Objective Linear part" << endl; 616 for (const auto& vn_coef : gradient) { 617 os << nl_file.variableIndexes.at(vn_coef.first) << " " << vn_coef.second << " # " 618 << vn_coef.first << endl; 619 } 620 } 621 } 622 } 623 624 return os; 625} 626} // namespace MiniZinc