The models, scripts, and results of the benchmarks performed for a Half Reification Journal paper
at develop 688 lines 21 kB view raw
1/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ 2 3/* 4 * Main authors: 5 * Guido Tack <guido.tack@monash.edu> 6 */ 7 8/* This Source Code Form is subject to the terms of the Mozilla Public 9 * License, v. 2.0. If a copy of the MPL was not distributed with this 10 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 11 12#include <minizinc/astexception.hh> 13#include <minizinc/flatten_internal.hh> 14#include <minizinc/model.hh> 15#include <minizinc/prettyprinter.hh> 16 17#undef MZN_DEBUG_FUNCTION_REGISTRY 18 19namespace MiniZinc { 20 21Model::FnEntry::FnEntry(FunctionI* fi0) : t(fi0->paramCount()), fi(fi0), isPolymorphic(false) { 22 for (unsigned int i = 0; i < fi->paramCount(); i++) { 23 t[i] = fi->param(i)->type(); 24 isPolymorphic |= (t[i].bt() == Type::BT_TOP); 25 } 26} 27 28bool Model::FnEntry::operator<(const Model::FnEntry& f) const { 29 assert(!compare(*this, f) || !compare(f, *this)); 30 return compare(*this, f); 31} 32 33bool Model::FnEntry::compare(const Model::FnEntry& e1, const Model::FnEntry& e2) { 34 if (e1.t.size() < e2.t.size()) { 35 return true; 36 } 37 if (e1.t.size() == e2.t.size()) { 38 for (unsigned int i = 0; i < e1.t.size(); i++) { 39 if (e1.t[i] != e2.t[i]) { 40 if (e1.t[i].isSubtypeOf(e2.t[i], true)) { 41 return true; 42 } 43 if (e2.t[i].isSubtypeOf(e1.t[i], true)) { 44 return false; 45 } 46 switch (e1.t[i].cmp(e2.t[i])) { 47 case -1: 48 return true; 49 case 1: 50 return false; 51 default: 52 assert(false); 53 } 54 } 55 } 56 } 57 return false; 58} 59 60Model::Model() : _parent(nullptr), _solveItem(nullptr), _outputItem(nullptr) {} 61 62Model::~Model() { 63 for (auto* i : _items) { 64 if (auto* ii = i->dynamicCast<IncludeI>()) { 65 if (ii->own()) { 66 delete ii->m(); 67 ii->m(nullptr); 68 } 69 } 70 } 71} 72 73VarDeclIteratorContainer Model::vardecls() { return VarDeclIteratorContainer(this); } 74ConstraintIteratorContainer Model::constraints() { return ConstraintIteratorContainer(this); } 75FunctionIteratorContainer Model::functions() { return FunctionIteratorContainer(this); } 76 77VarDeclIterator VarDeclIteratorContainer::begin() { return VarDeclIterator(_m, _m->begin()); } 78VarDeclIterator VarDeclIteratorContainer::end() { return VarDeclIterator(_m, _m->end()); } 79ConstraintIterator ConstraintIteratorContainer::begin() { 80 return ConstraintIterator(_m, _m->begin()); 81} 82ConstraintIterator ConstraintIteratorContainer::end() { return ConstraintIterator(_m, _m->end()); } 83FunctionIterator FunctionIteratorContainer::begin() { return FunctionIterator(_m, _m->begin()); } 84FunctionIterator FunctionIteratorContainer::end() { return FunctionIterator(_m, _m->end()); } 85 86SolveI* Model::solveItem() { return _solveItem; } 87 88OutputI* Model::outputItem() { return _outputItem; } 89 90void Model::addItem(Item* i) { 91 _items.push_back(i); 92 if (i->isa<SolveI>()) { 93 Model* m = this; 94 while (m->_parent != nullptr) { 95 m = m->_parent; 96 } 97 m->_solveItem = i->cast<SolveI>(); 98 } else if (i->isa<OutputI>()) { 99 Model* m = this; 100 while (m->_parent != nullptr) { 101 m = m->_parent; 102 } 103 m->_outputItem = i->cast<OutputI>(); 104 } 105} 106 107void Model::setOutputItem(OutputI* oi) { 108 Model* m = this; 109 while (m->_parent != nullptr) { 110 m = m->_parent; 111 } 112 m->_outputItem = oi; 113} 114 115namespace { 116/// Return lowest possible base type given other type-inst restrictions 117Type::BaseType lowest_bt(const Type& t) { 118 if (t.st() == Type::ST_SET && t.ti() == Type::TI_VAR) { 119 return Type::BT_INT; 120 } 121 return Type::BT_BOOL; 122} 123/// Return highest possible base type given other type-inst restrictions 124Type::BaseType highest_bt(const Type& t) { 125 if (t.st() == Type::ST_SET && t.ti() == Type::TI_VAR) { 126 return Type::BT_INT; 127 } 128 if (t.ti() == Type::TI_VAR || t.st() == Type::ST_SET) { 129 return Type::BT_FLOAT; 130 } 131 return Type::BT_ANN; 132} 133} // namespace 134 135void Model::addPolymorphicInstances(Model::FnEntry& fe, std::vector<FnEntry>& entries) { 136 entries.push_back(fe); 137 if (fe.isPolymorphic) { 138 FnEntry cur = fe; 139 std::vector<std::vector<Type*> > type_ids; 140 141 // First step: initialise all type variables to bool 142 // and collect them in the stack vector 143 for (unsigned int i = 0; i < cur.t.size(); i++) { 144 if (cur.t[i].bt() == Type::BT_TOP) { 145 std::vector<Type*> t; 146 for (unsigned int j = i; j < cur.t.size(); j++) { 147 assert(cur.fi->param(i)->ti()->domain() && cur.fi->param(i)->ti()->domain()->isa<TIId>()); 148 if ((cur.fi->param(j)->ti()->domain() != nullptr) && 149 cur.fi->param(j)->ti()->domain()->isa<TIId>()) { 150 TIId* id0 = cur.fi->param(i)->ti()->domain()->cast<TIId>(); 151 TIId* id1 = cur.fi->param(j)->ti()->domain()->cast<TIId>(); 152 if (id0->v() == id1->v()) { 153 // Found parameter with same type variable 154 // Initialise to lowest concrete base type (bool) 155 cur.t[j].bt(lowest_bt(cur.t[j])); 156 t.push_back(&cur.t[j]); 157 } 158 } 159 } 160 type_ids.push_back(t); 161 } 162 } 163 164 std::vector<int> stack; 165 for (unsigned int i = 0; i < type_ids.size(); i++) { 166 stack.push_back(i); 167 } 168 int final_id = static_cast<int>(type_ids.size()) - 1; 169 170 while (!stack.empty()) { 171 if (stack.back() == final_id) { 172 // If this instance isn't in entries yet, add it 173 bool alreadyDefined = false; 174 for (auto& entry : entries) { 175 if (entry.t == cur.t) { 176 alreadyDefined = true; 177 break; 178 } 179 } 180 if (!alreadyDefined) { 181 entries.push_back(cur); 182 } 183 } 184 185 Type& back_t = *type_ids[stack.back()][0]; 186 if (back_t.bt() == highest_bt(back_t) && back_t.st() == Type::ST_SET) { 187 // last type, remove this item 188 stack.pop_back(); 189 } else { 190 if (back_t.bt() == highest_bt(back_t)) { 191 // Create set type for current item 192 for (auto& i : type_ids[stack.back()]) { 193 i->st(Type::ST_SET); 194 i->bt(lowest_bt(*i)); 195 } 196 } else { 197 // Increment type of current item 198 auto nextType = static_cast<Type::BaseType>(back_t.bt() + 1); 199 for (auto& i : type_ids[stack.back()]) { 200 i->bt(nextType); 201 } 202 } 203 // Reset types of all further items and push them 204 for (unsigned int i = stack.back() + 1; i < type_ids.size(); i++) { 205 for (auto& j : type_ids[i]) { 206 j->bt(lowest_bt(*j)); 207 } 208 stack.push_back(i); 209 } 210 } 211 } 212 } 213} 214 215bool Model::registerFn(EnvI& env, FunctionI* fi, bool keepSorted, bool throwIfDuplicate) { 216 Model* m = this; 217 while (m->_parent != nullptr) { 218 m = m->_parent; 219 } 220 auto i_id = m->_fnmap.find(fi->id()); 221 if (i_id == m->_fnmap.end()) { 222 // new element 223 std::vector<FnEntry> v; 224 FnEntry fe(fi); 225 addPolymorphicInstances(fe, v); 226 m->_fnmap.insert(std::pair<ASTString, std::vector<FnEntry> >(fi->id(), v)); 227 } else { 228 // add to list of existing elements 229 std::vector<FnEntry>& v = i_id->second; 230 for (auto& i : v) { 231 if (i.fi == fi) { 232 return true; 233 } 234 if (i.fi->paramCount() == fi->paramCount()) { 235 bool alleq = true; 236 for (unsigned int j = 0; j < fi->paramCount(); j++) { 237 Type t1 = i.fi->param(j)->type(); 238 Type t2 = fi->param(j)->type(); 239 t1.enumId(0); 240 t2.enumId(0); 241 if (t1 != t2) { 242 alleq = false; 243 break; 244 } 245 } 246 if (alleq) { 247 if ((i.fi->e() != nullptr) && (fi->e() != nullptr) && !i.isPolymorphic) { 248 if (throwIfDuplicate) { 249 throw TypeError( 250 env, fi->loc(), 251 "function with the same type already defined in " + i.fi->loc().toString()); 252 } 253 return false; 254 } 255 if ((fi->e() != nullptr) || i.isPolymorphic) { 256 if (Call* deprecated = i.fi->ann().getCall(constants().ann.mzn_deprecated)) { 257 fi->ann().add(deprecated); 258 } 259 i = fi; 260 } else if (Call* deprecated = fi->ann().getCall(constants().ann.mzn_deprecated)) { 261 i.fi->ann().add(deprecated); 262 } 263 return true; 264 } 265 } 266 } 267 FnEntry fe(fi); 268 addPolymorphicInstances(fe, v); 269 if (keepSorted) { 270 std::sort(i_id->second.begin(), i_id->second.end()); 271 } 272 } 273 if (fi->id() == "mzn_reverse_map_var") { 274 if (fi->paramCount() != 1 || fi->ti()->type() != Type::varbool()) { 275 throw TypeError(env, fi->loc(), 276 "functions called `mzn_reverse_map_var` must have a single argument and " 277 "return type var bool"); 278 } 279 Type t = fi->param(0)->type(); 280 _revmapmap.insert(std::pair<int, FunctionI*>(t.toInt(), fi)); 281 } 282 return true; 283} 284 285FunctionI* Model::matchFn(EnvI& env, const ASTString& id, const std::vector<Type>& t, 286 bool strictEnums) { 287 if (id == constants().varRedef->id()) { 288 return constants().varRedef; 289 } 290 Model* m = this; 291 while (m->_parent != nullptr) { 292 m = m->_parent; 293 } 294 auto i_id = m->_fnmap.find(id); 295 if (i_id == m->_fnmap.end()) { 296 return nullptr; 297 } 298 std::vector<FnEntry>& v = i_id->second; 299 for (auto& i : v) { 300 std::vector<Type>& fi_t = i.t; 301#ifdef MZN_DEBUG_FUNCTION_REGISTRY 302 std::cerr << "try " << *i.fi; 303#endif 304 if (fi_t.size() == t.size()) { 305 bool match = true; 306 for (unsigned int j = 0; j < t.size(); j++) { 307 if (!env.isSubtype(t[j], fi_t[j], strictEnums)) { 308#ifdef MZN_DEBUG_FUNCTION_REGISTRY 309 std::cerr << t[j].toString(env) << " does not match " << fi_t[j].toString(env) << "\n"; 310#endif 311 match = false; 312 break; 313 } 314 } 315 if (match) { 316 return i.fi; 317 } 318 } 319 } 320 return nullptr; 321} 322 323void Model::mergeStdLib(EnvI& env, Model* m) const { 324 for (const auto& it : _fnmap) { 325 for (const auto& cit : it.second) { 326 if (cit.fi->fromStdLib()) { 327 (void)m->registerFn(env, cit.fi); 328 } 329 } 330 } 331 m->sortFn(); 332} 333 334void Model::sortFn() { 335 Model* m = this; 336 while (m->_parent != nullptr) { 337 m = m->_parent; 338 } 339 for (auto& it : m->_fnmap) { 340 // Sort all functions by type 341 std::sort(it.second.begin(), it.second.end()); 342 } 343} 344 345void Model::fixFnMap() { 346 Model* m = this; 347 while (m->_parent != nullptr) { 348 m = m->_parent; 349 } 350 for (auto& it : m->_fnmap) { 351 for (auto& i : it.second) { 352 for (unsigned int j = 0; j < i.t.size(); j++) { 353 if (i.t[j].isunknown()) { 354 i.t[j] = i.fi->param(j)->type(); 355 } 356 } 357 } 358 } 359} 360 361void Model::checkFnOverloading(EnvI& env) { 362 Model* m = this; 363 while (m->_parent != nullptr) { 364 m = m->_parent; 365 } 366 for (auto& it : m->_fnmap) { 367 std::vector<FnEntry>& fs = it.second; 368 for (unsigned int i = 0; i < fs.size() - 1; i++) { 369 FunctionI* cur = fs[i].fi; 370 for (unsigned int j = i + 1; j < fs.size(); j++) { 371 FunctionI* cmp = fs[j].fi; 372 if (cur == cmp || cur->paramCount() != cmp->paramCount()) { 373 break; 374 } 375 bool allEqual = true; 376 for (unsigned int i = 0; i < cur->paramCount(); i++) { 377 Type t1 = cur->param(i)->type(); 378 Type t2 = cmp->param(i)->type(); 379 t1.enumId(0); 380 t2.enumId(0); 381 if (t1 != t2) { 382 allEqual = false; 383 break; 384 } 385 } 386 if (allEqual) { 387 throw TypeError(env, cur->loc(), 388 "unsupported type of overloading. \nFunction/predicate with equivalent " 389 "signature defined in " + 390 cmp->loc().toString()); 391 } 392 } 393 } 394 } 395} 396 397namespace { 398int match_idx(std::vector<FunctionI*>& matched, Expression*& botarg, EnvI& env, 399 const std::vector<Model::FnEntry>& v, const std::vector<Expression*>& args, 400 bool strictEnums) { 401 botarg = nullptr; 402 for (unsigned int i = 0; i < v.size(); i++) { 403 const std::vector<Type>& fi_t = v[i].t; 404#ifdef MZN_DEBUG_FUNCTION_REGISTRY 405 std::cerr << "try " << *v[i].fi; 406#endif 407 if (fi_t.size() == args.size()) { 408 bool match = true; 409 for (unsigned int j = 0; j < args.size(); j++) { 410 if (!env.isSubtype(args[j]->type(), fi_t[j], strictEnums)) { 411#ifdef MZN_DEBUG_FUNCTION_REGISTRY 412 std::cerr << args[j]->type().toString(env) << " does not match " << fi_t[j].toString(env) 413 << "\n"; 414#endif 415 match = false; 416 break; 417 } 418 if (args[j]->type().isbot() && fi_t[j].bt() != Type::BT_TOP) { 419 botarg = args[j]; 420 } 421 } 422 if (match) { 423 matched.push_back(v[i].fi); 424 if (botarg == nullptr) { 425 return static_cast<int>(i); 426 } 427 } 428 } 429 } 430 return -1; 431} 432} // namespace 433 434FunctionI* Model::matchFn(EnvI& env, const ASTString& id, const std::vector<Expression*>& args, 435 bool strictEnums) const { 436 if (id == constants().varRedef->id()) { 437 return constants().varRedef; 438 } 439 const Model* m = this; 440 while (m->_parent != nullptr) { 441 m = m->_parent; 442 } 443 auto it = m->_fnmap.find(id); 444 if (it == m->_fnmap.end()) { 445 return nullptr; 446 } 447 const std::vector<FnEntry>& v = it->second; 448 std::vector<FunctionI*> matched; 449 Expression* botarg; 450 (void)match_idx(matched, botarg, env, v, args, strictEnums); 451 if (matched.empty()) { 452 return nullptr; 453 } 454 if (matched.size() == 1) { 455 return matched[0]; 456 } 457 Type t = matched[0]->ti()->type(); 458 t.ti(Type::TI_PAR); 459 for (unsigned int i = 1; i < matched.size(); i++) { 460 if (!env.isSubtype(t, matched[i]->ti()->type(), strictEnums)) { 461 throw TypeError(env, botarg->loc(), "ambiguous overloading on return type of function"); 462 } 463 } 464 return matched[0]; 465} 466 467FunctionI* Model::matchFn(EnvI& env, Call* c, bool strictEnums, bool throwIfNotFound) const { 468 if (c->id() == constants().varRedef->id()) { 469 return constants().varRedef; 470 } 471 const Model* m = this; 472 while (m->_parent != nullptr) { 473 m = m->_parent; 474 } 475 auto it = m->_fnmap.find(c->id()); 476 if (it == m->_fnmap.end()) { 477 if (throwIfNotFound) { 478 std::ostringstream oss; 479 oss << "no function or predicate with name `"; 480 oss << c->id() << "' found"; 481 482 ASTString mostSimilar; 483 int minEdits = 3; 484 for (const auto& decls : m->_fnmap) { 485 if (std::abs(static_cast<int>(c->id().size()) - static_cast<int>(decls.first.size())) <= 486 3) { 487 int edits = c->id().levenshteinDistance(decls.first); 488 if (edits < minEdits && edits < std::min(c->id().size(), decls.first.size())) { 489 minEdits = edits; 490 mostSimilar = decls.first; 491 } 492 } 493 } 494 if (mostSimilar.size() > 0) { 495 oss << ", did you mean `" << mostSimilar << "'?"; 496 } 497 throw TypeError(env, c->loc(), oss.str()); 498 } 499 return nullptr; 500 } 501 const std::vector<FnEntry>& v = it->second; 502 std::vector<FunctionI*> matched; 503 Expression* botarg = nullptr; 504 for (const auto& i : v) { 505 const std::vector<Type>& fi_t = i.t; 506#ifdef MZN_DEBUG_FUNCTION_REGISTRY 507 std::cerr << "try " << *i.fi; 508#endif 509 if (fi_t.size() == c->argCount()) { 510 bool match = true; 511 for (unsigned int j = 0; j < c->argCount(); j++) { 512 if (!env.isSubtype(c->arg(j)->type(), fi_t[j], strictEnums)) { 513#ifdef MZN_DEBUG_FUNCTION_REGISTRY 514 std::cerr << c->arg(j)->type().toString(env) << " does not match " 515 << fi_t[j].toString(env) << "\n"; 516 std::cerr << "Wrong argument is " << *c->arg(j); 517#endif 518 match = false; 519 break; 520 } 521 if (c->arg(j)->type().isbot() && fi_t[j].bt() != Type::BT_TOP) { 522 botarg = c->arg(j); 523 } 524 } 525 if (match) { 526 if (botarg != nullptr) { 527 matched.push_back(i.fi); 528 } else { 529 return i.fi; 530 } 531 } 532 } 533 } 534 if (matched.empty()) { 535 if (throwIfNotFound) { 536 std::ostringstream oss; 537 oss << "no function or predicate with this signature found: `"; 538 oss << c->id() << "("; 539 for (unsigned int i = 0; i < c->argCount(); i++) { 540 oss << c->arg(i)->type().toString(env); 541 if (i < c->argCount() - 1) { 542 oss << ","; 543 } 544 } 545 oss << ")'\n"; 546 oss << "Cannot use the following functions or predicates with the same identifier:\n"; 547 Printer pp(oss, 0, false, &env); 548 for (const auto& i : v) { 549 const std::vector<Type>& fi_t = i.t; 550 Expression* body = i.fi->e(); 551 i.fi->e(nullptr); 552 pp.print(i.fi); 553 i.fi->e(body); 554 if (fi_t.size() == c->argCount()) { 555 for (unsigned int j = 0; j < c->argCount(); j++) { 556 if (!env.isSubtype(c->arg(j)->type(), fi_t[j], strictEnums)) { 557 oss << " (argument " << (j + 1) << " expects type " << fi_t[j].toString(env); 558 oss << ", but type " << c->arg(j)->type().toString(env) << " given)\n"; 559 } 560 if (c->arg(j)->type().isbot() && fi_t[j].bt() != Type::BT_TOP) { 561 botarg = c->arg(j); 562 } 563 } 564 } else { 565 oss << " (requires " << i.fi->paramCount() << " argument" 566 << (i.fi->paramCount() == 1 ? "" : "s") << ", but " << c->argCount() << " given)\n"; 567 } 568 } 569 throw TypeError(env, c->loc(), oss.str()); 570 } 571 return nullptr; 572 } 573 if (matched.size() == 1) { 574 return matched[0]; 575 } 576 Type t = matched[0]->ti()->type(); 577 t.ti(Type::TI_PAR); 578 for (unsigned int i = 1; i < matched.size(); i++) { 579 if (!env.isSubtype(t, matched[i]->ti()->type(), strictEnums)) { 580 throw TypeError(env, botarg->loc(), "ambiguous overloading on return type of function"); 581 } 582 } 583 return matched[0]; 584} 585 586namespace { 587int first_overloaded(EnvI& env, const std::vector<Model::FnEntry>& v_f, int i_f) { 588 int first_i_f = i_f; 589 for (; (first_i_f--) != 0;) { 590 // find first instance overloaded on subtypes 591 if (v_f[first_i_f].t.size() != v_f[i_f].t.size()) { 592 break; 593 } 594 bool allSubtypes = true; 595 for (unsigned int i = 0; i < v_f[first_i_f].t.size(); i++) { 596 if (!env.isSubtype(v_f[first_i_f].t[i], v_f[i_f].t[i], false)) { 597 allSubtypes = false; 598 break; 599 } 600 } 601 if (!allSubtypes) { 602 break; 603 } 604 } 605 return first_i_f + 1; 606} 607} // namespace 608 609bool Model::sameOverloading(EnvI& env, const std::vector<Expression*>& args, FunctionI* f, 610 FunctionI* g) const { 611 const Model* m = this; 612 while (m->_parent != nullptr) { 613 m = m->_parent; 614 } 615 auto it_f = m->_fnmap.find(f->id()); 616 auto it_g = m->_fnmap.find(g->id()); 617 assert(it_f != m->_fnmap.end()); 618 assert(it_g != m->_fnmap.end()); 619 const std::vector<FnEntry>& v_f = it_f->second; 620 const std::vector<FnEntry>& v_g = it_g->second; 621 622 std::vector<FunctionI*> dummyMatched; 623 Expression* dummyBotarg; 624 int i_f = match_idx(dummyMatched, dummyBotarg, env, v_f, args, true); 625 if (i_f == -1) { 626 return false; 627 } 628 int i_g = match_idx(dummyMatched, dummyBotarg, env, v_g, args, true); 629 if (i_g == -1) { 630 return false; 631 } 632 assert(i_f < v_f.size()); 633 assert(i_g < v_g.size()); 634 unsigned int first_i_f = first_overloaded(env, v_f, i_f); 635 unsigned int first_i_g = first_overloaded(env, v_g, i_g); 636 if (i_f - first_i_f != i_g - first_i_g) { 637 // not the same number of overloaded versions 638 return false; 639 } 640 for (; first_i_f <= i_f; first_i_f++, first_i_g++) { 641 if (!(v_f[first_i_f].t == v_g[first_i_g].t)) { 642 // one of the overloaded versions does not agree in the types 643 return false; 644 } 645 } 646 return true; 647} 648 649FunctionI* Model::matchRevMap(EnvI& env, const Type& t0) const { 650 const Model* m = this; 651 while (m->_parent != nullptr) { 652 m = m->_parent; 653 } 654 Type t = t0; 655 t.enumId(0); 656 auto it = _revmapmap.find(t.toInt()); 657 if (it != _revmapmap.end()) { 658 return it->second; 659 } 660 return nullptr; 661} 662 663Item*& Model::operator[](unsigned int i) { 664 assert(i < _items.size()); 665 return _items[i]; 666} 667const Item* Model::operator[](unsigned int i) const { 668 assert(i < _items.size()); 669 return _items[i]; 670} 671unsigned int Model::size() const { return static_cast<unsigned int>(_items.size()); } 672 673std::vector<Item*>::iterator Model::begin() { return _items.begin(); } 674 675std::vector<Item*>::const_iterator Model::begin() const { return _items.begin(); } 676 677std::vector<Item*>::iterator Model::end() { return _items.end(); } 678 679std::vector<Item*>::const_iterator Model::end() const { return _items.end(); } 680 681void Model::compact() { 682 struct { 683 bool operator()(const Item* i) { return i->removed(); } 684 } isremoved; 685 _items.erase(remove_if(_items.begin(), _items.end(), isremoved), _items.end()); 686} 687 688} // namespace MiniZinc