/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* * Main authors: * Guido Tack */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include namespace MiniZinc { namespace { // Test if all parameters and the return type are par bool is_completely_par(EnvI& env, FunctionI* fi, const std::vector& tv) { if (fi->e() != nullptr) { // This is not a builtin, so check parameters for (auto* p : fi->params()) { if (p->type().isvar()) { return false; } } } return fi->rtype(env, tv, false).isPar(); } } // namespace void check_output_par_fn(EnvI& env, Call* rhs) { std::vector tv(rhs->argCount()); for (unsigned int i = rhs->argCount(); (i--) != 0U;) { tv[i] = rhs->arg(i)->type(); tv[i].ti(Type::TI_PAR); } FunctionI* decl = env.output->matchFn(env, rhs->id(), tv, false); if (decl == nullptr) { FunctionI* origdecl = env.model->matchFn(env, rhs->id(), tv, false); if (origdecl == nullptr || !is_completely_par(env, origdecl, tv)) { std::ostringstream ss; ss << "function " << rhs->id() << " is used in output, par version needed"; throw FlatteningError(env, rhs->loc(), ss.str()); } if (!origdecl->fromStdLib()) { decl = copy(env, env.cmap, origdecl)->cast(); CollectOccurrencesE ce(env.outputVarOccurrences, decl); top_down(ce, decl->e()); top_down(ce, decl->ti()); for (unsigned int i = decl->params().size(); (i--) != 0U;) { top_down(ce, decl->params()[i]); } (void)env.output->registerFn(env, decl, true); env.output->addItem(decl); } else { decl = origdecl; } } rhs->type(decl->rtype(env, tv, false)); rhs->decl(decl); } bool cannot_use_rhs_for_output(EnvI& env, Expression* e, std::unordered_set& seen_functions) { if (e == nullptr) { return true; } class V : public EVisitor { public: EnvI& env; std::unordered_set& seenFunctions; bool success; V(EnvI& env0, std::unordered_set& seenFunctions0) : env(env0), seenFunctions(seenFunctions0), success(true) {} /// Visit anonymous variable void vAnonVar(const AnonVar& /*v*/) { success = false; } /// Visit array literal void vArrayLit(const ArrayLit& /*al*/) {} /// Visit array access void vArrayAccess(const ArrayAccess& /*aa*/) {} /// Visit array comprehension void vComprehension(const Comprehension& /*c*/) {} /// Visit if-then-else void vITE(const ITE& /*ite*/) {} /// Visit binary operator void vBinOp(const BinOp& /*bo*/) {} /// Visit unary operator void vUnOp(const UnOp& /*uo*/) {} /// Visit call void vCall(Call& c) { std::vector tv(c.argCount()); for (unsigned int i = c.argCount(); (i--) != 0U;) { tv[i] = c.arg(i)->type(); tv[i].ti(Type::TI_PAR); } FunctionI* decl = env.output->matchFn(env, c.id(), tv, false); Type t; if (decl == nullptr) { FunctionI* origdecl = env.model->matchFn(env, c.id(), tv, false); if (origdecl == nullptr) { std::ostringstream ss; ss << "function " << c.id() << " is used in output, par version needed"; throw FlatteningError(env, c.loc(), ss.str()); } bool seen = (seenFunctions.find(origdecl) != seenFunctions.end()); if (seen) { success = false; } else { seenFunctions.insert(origdecl); if ((origdecl->e() != nullptr) && cannot_use_rhs_for_output(env, origdecl->e(), seenFunctions)) { success = false; } else { if (!origdecl->fromStdLib()) { decl = copy(env, env.cmap, origdecl)->cast(); CollectOccurrencesE ce(env.outputVarOccurrences, decl); top_down(ce, decl->e()); top_down(ce, decl->ti()); for (unsigned int i = decl->params().size(); (i--) != 0U;) { top_down(ce, decl->params()[i]); } (void)env.output->registerFn(env, decl, true); env.output->addItem(decl); output_vardecls(env, origdecl, decl->e()); output_vardecls(env, origdecl, decl->ti()); } else { decl = origdecl; } c.decl(decl); } } } if (success) { t = decl->rtype(env, tv, false); if (!t.isPar()) { success = false; } } } void vId(const Id& /*id*/) {} /// Visit let void vLet(const Let& /*let*/) { success = false; } /// Visit variable declaration void vVarDecl(const VarDecl& /*vd*/) {} /// Visit type inst void vTypeInst(const TypeInst& /*ti*/) {} /// Visit TIId void vTIId(const TIId& /*tiid*/) {} /// Determine whether to enter node bool enter(Expression* /*e*/) const { return success; } } _v(env, seen_functions); top_down(_v, e); return !_v.success; } bool cannot_use_rhs_for_output(EnvI& env, Expression* e) { std::unordered_set seen_functions; return cannot_use_rhs_for_output(env, e, seen_functions); } void remove_is_output(VarDecl* vd) { if (vd == nullptr) { return; } vd->ann().remove(constants().ann.output_var); vd->ann().removeCall(constants().ann.output_array); } void copy_output(EnvI& e) { struct CopyOutput : public EVisitor { EnvI& env; CopyOutput(EnvI& env0) : env(env0) {} static void vId(Id& _id) { _id.decl(_id.decl()->flat()); } void vCall(Call& c) { std::vector tv(c.argCount()); for (unsigned int i = c.argCount(); (i--) != 0U;) { tv[i] = c.arg(i)->type(); tv[i].ti(Type::TI_PAR); } FunctionI* decl = c.decl(); if (!decl->fromStdLib()) { env.flatAddItem(decl); } } }; if (OutputI* oi = e.model->outputItem()) { GCLock lock; auto* noi = copy(e, oi)->cast(); CopyOutput co(e); top_down(co, noi->e()); e.flatAddItem(noi); } } void cleanup_output(EnvI& env) { for (auto& i : *env.output) { if (auto* vdi = i->dynamicCast()) { vdi->e()->flat(nullptr); } } } void make_par(EnvI& env, Expression* e) { class OutputJSON : public EVisitor { public: EnvI& env; OutputJSON(EnvI& env0) : env(env0) {} void vCall(Call& c) { if (c.id() == "outputJSON") { bool outputObjective = (c.argCount() == 1 && eval_bool(env, c.arg(0))); c.id(ASTString("array1d")); Expression* json = copy(env, env.cmap, create__json_output(env, outputObjective, false, false)); std::vector new_args({json}); new_args[0]->type(Type::parstring(1)); c.args(new_args); } } } _outputJSON(env); top_down(_outputJSON, e); class Par : public EVisitor { public: /// Visit variable declaration static void vVarDecl(VarDecl& vd) { vd.ti()->type(vd.type()); } /// Determine whether to enter node static bool enter(Expression* e) { Type t = e->type(); t.ti(Type::TI_PAR); t.cv(false); e->type(t); return true; } } _par; top_down(_par, e); class Decls : public EVisitor { public: EnvI& env; Decls(EnvI& env0) : env(env0) {} void vCall(Call& c) { if (c.id() == "format" || c.id() == "show" || c.id() == "showDzn" || c.id() == "showJSON") { unsigned int enumId = c.arg(c.argCount() - 1)->type().enumId(); if (enumId != 0U && c.arg(c.argCount() - 1)->type().dim() != 0) { const std::vector& enumIds = env.getArrayEnum(enumId); enumId = enumIds[enumIds.size() - 1]; } if (enumId > 0) { GCLock lock; Expression* obj = c.arg(c.argCount() - 1); Id* ti_id = env.getEnum(enumId)->e()->id(); std::string enumName = create_enum_to_string_name(ti_id, "_toString_"); bool is_json = c.id() == "showJSON"; const int dimensions = obj->type().dim(); if (is_json && dimensions > 1) { // Create generators for dimensions selection std::vector slice_dimensions(dimensions); std::vector generators; generators.reserve(dimensions - 1); auto* idx_ti = new TypeInst(Location().introduce(), Type::parint()); for (int i = 0; i < dimensions - 1; ++i) { auto* idx_i = new VarDecl(Location().introduce(), idx_ti, env.genId()); idx_i->toplevel(false); Call* index_set_xx = new Call( Location().introduce(), "index_set_" + std::to_string(i + 1) + "of" + std::to_string(dimensions), {obj}); index_set_xx->type(Type::parsetint()); generators.push_back(Generator({idx_i}, index_set_xx, nullptr)); slice_dimensions[i] = new BinOp(Location().introduce(), idx_i->id(), BOT_DOTDOT, idx_i->id()); slice_dimensions[i]->type(Type::parsetint()); } // Construct innermost slicing operation Call* index_set_n = new Call( Location().introduce(), "index_set_" + std::to_string(dimensions) + "of" + std::to_string(dimensions), {obj}); index_set_n->type(Type::parsetint()); slice_dimensions[dimensions - 1] = index_set_n; auto* al_slice_dim = new ArrayLit(Location().introduce(), slice_dimensions); al_slice_dim->type(Type::parsetint(1)); auto* slice_call = new Call(Location().introduce(), "slice_1d", {obj, al_slice_dim, index_set_n}); Type tt = obj->type(); tt.dim(1); slice_call->type(tt); Call* _toString_ENUM = new Call(Location().introduce(), enumName, {slice_call, constants().boollit(false), constants().boollit(true)}); _toString_ENUM->type(Type::parstring()); // Build multi-level JSON Array string auto* comma = new StringLit(Location().introduce(), ", "); comma->type(Type::parstring()); auto join = [&](Expression* expr, Generator gen) -> Expression* { Generators generators; generators.g.push_back(gen); auto* comp = new Comprehension(Location().introduce(), expr, generators, false); comp->type(Type::parstring(1)); Call* cc = new Call(Location().introduce(), "join", {comma, comp}); cc->type(Type::parstring()); return cc; }; auto* sl_open = new StringLit(Location().introduce(), "["); sl_open->type(Type::parstring()); auto* sl_close = new StringLit(Location().introduce(), "]"); sl_close->type(Type::parstring()); auto* al_concat = new ArrayLit( Location().introduce(), std::vector( {sl_open, join(_toString_ENUM, generators[dimensions - 2]), sl_close})); al_concat->type(Type::parstring(1)); for (int i = dimensions - 3; i >= 0; --i) { Call* concat = new Call(Location().introduce(), "concat", {al_concat}); concat->type(Type::parstring()); al_concat = new ArrayLit( Location().introduce(), std::vector({sl_open, join(concat, generators[i]), sl_close})); al_concat->type(Type::parstring(1)); } std::vector args = {al_concat}; c.args(args); c.id(ASTString("concat")); } else { std::vector args = {obj, constants().boollit(c.id() == "showDzn"), constants().boollit(is_json)}; c.args(args); c.id(ASTString(enumName)); } } if (c.id() == "showDzn" || (c.id() == "showJSON" && enumId > 0)) { c.id(constants().ids.show); } } c.decl(env.model->matchFn(env, &c, false)); } void vBinOp(BinOp& bo) { std::vector args = {bo.lhs(), bo.rhs()}; bo.decl(env.model->matchFn(env, bo.opToString(), args, false)); } void vUnop(UnOp& uo) { std::vector args = {uo.e()}; uo.decl(env.model->matchFn(env, uo.opToString(), args, false)); } } _decls(env); top_down(_decls, e); } void check_rename_var(EnvI& e, VarDecl* vd) { if (vd->id()->idn() != vd->flat()->id()->idn()) { auto* vd_rename_ti = copy(e, e.cmap, vd->ti())->cast(); auto* vd_rename = new VarDecl(Location().introduce(), vd_rename_ti, vd->flat()->id()->idn(), nullptr); vd_rename->flat(vd->flat()); make_par(e, vd_rename); vd->e(vd_rename->id()); e.output->addItem(new VarDeclI(Location().introduce(), vd_rename)); } } class ClearAnnotations { public: /// Push all elements of \a v onto \a stack template static void pushVec(std::vector& stack, ASTExprVec v) { for (unsigned int i = 0; i < v.size(); i++) { stack.push_back(v[i]); } } static void run(Expression* root) { std::vector stack; stack.push_back(root); while (!stack.empty()) { Expression* e = stack.back(); stack.pop_back(); if (e == nullptr) { continue; } e->ann().clear(); switch (e->eid()) { case Expression::E_INTLIT: case Expression::E_FLOATLIT: case Expression::E_BOOLLIT: case Expression::E_STRINGLIT: case Expression::E_ID: case Expression::E_ANON: case Expression::E_TIID: break; case Expression::E_SETLIT: pushVec(stack, e->template cast()->v()); break; case Expression::E_ARRAYLIT: for (unsigned int i = 0; i < e->cast()->size(); i++) { stack.push_back((*e->cast())[i]); } break; case Expression::E_ARRAYACCESS: pushVec(stack, e->template cast()->idx()); stack.push_back(e->template cast()->v()); break; case Expression::E_COMP: { auto* comp = e->template cast(); for (unsigned int i = comp->numberOfGenerators(); (i--) != 0U;) { stack.push_back(comp->where(i)); stack.push_back(comp->in(i)); for (unsigned int j = comp->numberOfDecls(i); (j--) != 0U;) { stack.push_back(comp->decl(i, j)); } } stack.push_back(comp->e()); } break; case Expression::E_ITE: { ITE* ite = e->template cast(); stack.push_back(ite->elseExpr()); for (int i = 0; i < ite->size(); i++) { stack.push_back(ite->ifExpr(i)); stack.push_back(ite->thenExpr(i)); } } break; case Expression::E_BINOP: stack.push_back(e->template cast()->rhs()); stack.push_back(e->template cast()->lhs()); break; case Expression::E_UNOP: stack.push_back(e->template cast()->e()); break; case Expression::E_CALL: for (unsigned int i = 0; i < e->template cast()->argCount(); i++) { stack.push_back(e->template cast()->arg(i)); } break; case Expression::E_VARDECL: stack.push_back(e->template cast()->e()); stack.push_back(e->template cast()->ti()); break; case Expression::E_LET: stack.push_back(e->template cast()->in()); pushVec(stack, e->template cast()->let()); break; case Expression::E_TI: stack.push_back(e->template cast()->domain()); pushVec(stack, e->template cast()->ranges()); break; } } } }; void output_vardecls(EnvI& env, Item* ci, Expression* e) { class O : public EVisitor { public: EnvI& env; Item* ci; O(EnvI& env0, Item* ci0) : env(env0), ci(ci0) {} void vId(Id& id) { if (&id == constants().absent) { return; } if (!id.decl()->toplevel()) { return; } VarDecl* vd = id.decl(); VarDecl* reallyFlat = vd->flat(); while (reallyFlat != nullptr && reallyFlat != reallyFlat->flat()) { reallyFlat = reallyFlat->flat(); } auto idx = reallyFlat != nullptr ? env.outputFlatVarOccurrences.idx.find(reallyFlat->id()) : env.outputFlatVarOccurrences.idx.end(); auto idx2 = env.outputVarOccurrences.idx.find(vd->id()); if (idx == env.outputFlatVarOccurrences.idx.end() && idx2 == env.outputVarOccurrences.idx.end()) { auto* nvi = new VarDeclI(Location().introduce(), copy(env, env.cmap, vd)->cast()); Type t = nvi->e()->ti()->type(); if (t.ti() != Type::TI_PAR) { t.ti(Type::TI_PAR); } make_par(env, nvi->e()); nvi->e()->ti()->domain(nullptr); nvi->e()->flat(vd->flat()); ClearAnnotations::run(nvi->e()); nvi->e()->introduced(false); if (reallyFlat != nullptr) { env.outputFlatVarOccurrences.addIndex(reallyFlat, static_cast(env.output->size())); } env.outputVarOccurrences.addIndex(nvi, static_cast(env.output->size())); env.outputVarOccurrences.add(nvi->e(), ci); env.output->addItem(nvi); IdMap::iterator it; if ((it = env.reverseMappers.find(nvi->e()->id())) != env.reverseMappers.end()) { Call* rhs = copy(env, env.cmap, it->second())->cast(); check_output_par_fn(env, rhs); output_vardecls(env, nvi, it->second()); nvi->e()->e(rhs); } else if ((reallyFlat != nullptr) && cannot_use_rhs_for_output(env, reallyFlat->e())) { assert(nvi->e()->flat()); nvi->e()->e(nullptr); if (nvi->e()->type().dim() == 0) { reallyFlat->addAnnotation(constants().ann.output_var); } else { std::vector args(reallyFlat->e()->type().dim()); for (unsigned int i = 0; i < args.size(); i++) { if (nvi->e()->ti()->ranges()[i]->domain() == nullptr) { args[i] = new SetLit(Location().introduce(), eval_intset(env, reallyFlat->ti()->ranges()[i]->domain())); } else { args[i] = new SetLit(Location().introduce(), eval_intset(env, nvi->e()->ti()->ranges()[i]->domain())); } } auto* al = new ArrayLit(Location().introduce(), args); args.resize(1); args[0] = al; reallyFlat->addAnnotation( new Call(Location().introduce(), constants().ann.output_array, args)); } check_rename_var(env, nvi->e()); } else { output_vardecls(env, nvi, nvi->e()->ti()); output_vardecls(env, nvi, nvi->e()->e()); } CollectOccurrencesE ce(env.outputVarOccurrences, nvi); top_down(ce, nvi->e()); } } } _o(env, ci); top_down(_o, e); } void process_deletions(EnvI& e) { std::vector deletedVarDecls; for (unsigned int i = 0; i < e.output->size(); i++) { if (auto* vdi = (*e.output)[i]->dynamicCast()) { if (!vdi->removed() && e.outputVarOccurrences.occurrences(vdi->e()) == 0 && !vdi->e()->ann().contains(constants().ann.mzn_check_var) && !(vdi->e()->id()->idn() == -1 && (vdi->e()->id()->v() == "_mzn_solution_checker" || vdi->e()->id()->v() == "_mzn_stats_checker"))) { CollectDecls cd(e.outputVarOccurrences, deletedVarDecls, vdi); top_down(cd, vdi->e()->e()); remove_is_output(vdi->e()->flat()); if (e.outputVarOccurrences.find(vdi->e()) != -1) { e.outputVarOccurrences.remove(vdi->e()); } vdi->remove(); } } } while (!deletedVarDecls.empty()) { VarDecl* cur = deletedVarDecls.back(); deletedVarDecls.pop_back(); if (e.outputVarOccurrences.occurrences(cur) == 0) { auto cur_idx = e.outputVarOccurrences.idx.find(cur->id()); if (cur_idx != e.outputVarOccurrences.idx.end()) { auto* vdi = (*e.output)[cur_idx->second]->cast(); if (!vdi->removed()) { CollectDecls cd(e.outputVarOccurrences, deletedVarDecls, vdi); top_down(cd, cur->e()); remove_is_output(vdi->e()->flat()); if (e.outputVarOccurrences.find(vdi->e()) != -1) { e.outputVarOccurrences.remove(vdi->e()); } vdi->remove(); } } } } for (auto& it : e.outputVarOccurrences.itemMap) { std::vector toRemove; for (auto* iit : it.second) { if (iit->removed()) { toRemove.push_back(iit); } } for (auto& i : toRemove) { it.second.erase(i); } } } void create_dzn_output_item(EnvI& e, bool outputObjective, bool includeOutputItem, bool hasChecker, bool outputForChecker) { std::vector outputVars; class DZNOVisitor : public ItemVisitor { protected: EnvI& _e; bool _outputObjective; bool _includeOutputItem; bool _outputForChecker; std::vector& _outputVars; bool _hadAddToOutput; public: DZNOVisitor(EnvI& e, bool outputObjective, bool includeOutputItem, bool outputForChecker, std::vector& outputVars) : _e(e), _outputObjective(outputObjective), _includeOutputItem(includeOutputItem), _outputForChecker(outputForChecker), _outputVars(outputVars), _hadAddToOutput(false) {} void vVarDeclI(VarDeclI* vdi) { VarDecl* vd = vdi->e(); bool process_var = false; if (_outputForChecker) { if (vd->ann().contains(constants().ann.mzn_check_var)) { process_var = true; } } else { if (_outputObjective && vd->id()->idn() == -1 && vd->id()->v() == "_objective") { process_var = true; } else { if (vd->ann().contains(constants().ann.add_to_output)) { if (!_hadAddToOutput) { _outputVars.clear(); } _hadAddToOutput = true; process_var = true; } else { if (!_hadAddToOutput) { process_var = false; if (vd->type().isvar()) { if (vd->e() != nullptr) { if (auto* al = vd->e()->dynamicCast()) { for (unsigned int i = 0; i < al->size(); i++) { if ((*al)[i]->isa()) { process_var = true; break; } } } else if (vd->ann().contains(constants().ann.rhs_from_assignment)) { process_var = true; } } else { process_var = true; } } } } } } if (process_var) { std::ostringstream s; s << vd->id()->str() << " = "; bool needArrayXd = false; if (vd->type().dim() > 0) { ArrayLit* al = nullptr; if ((vd->flat() != nullptr) && (vd->flat()->e() != nullptr)) { al = eval_array_lit(_e, vd->flat()->e()); } else if (vd->e() != nullptr) { al = eval_array_lit(_e, vd->e()); } if (al->size() > 0) { needArrayXd = true; s << "array" << vd->type().dim() << "d("; for (int i = 0; i < vd->type().dim(); i++) { unsigned int enumId = (vd->type().enumId() != 0 ? _e.getArrayEnum(vd->type().enumId())[i] : 0); if (enumId != 0) { s << _e.getEnum(enumId)->e()->id()->str() << ", "; } else if (al != nullptr) { s << al->min(i) << ".." << al->max(i) << ", "; } else { IntSetVal* idxset = eval_intset(_e, vd->ti()->ranges()[i]->domain()); s << *idxset << ", "; } } } } auto* sl = new StringLit(Location().introduce(), s.str()); _outputVars.push_back(sl); std::vector showArgs(1); showArgs[0] = vd->id(); Call* show = new Call(Location().introduce(), ASTString("showDzn"), showArgs); show->type(Type::parstring()); FunctionI* fi = _e.model->matchFn(_e, show, false); assert(fi); show->decl(fi); _outputVars.push_back(show); std::string ends = needArrayXd ? ")" : ""; ends += ";\n"; auto* eol = new StringLit(Location().introduce(), ends); _outputVars.push_back(eol); } } void vOutputI(OutputI* oi) { if (_includeOutputItem) { _outputVars.push_back(new StringLit(Location().introduce(), "_output = ")); Call* concat = new Call(Location().introduce(), ASTString("concat"), {oi->e()}); concat->type(Type::parstring()); FunctionI* fi = _e.model->matchFn(_e, concat, false); assert(fi); concat->decl(fi); Call* show = new Call(Location().introduce(), ASTString("showDzn"), {concat}); show->type(Type::parstring()); fi = _e.model->matchFn(_e, show, false); assert(fi); show->decl(fi); _outputVars.push_back(show); _outputVars.push_back(new StringLit(Location().introduce(), ";\n")); } oi->remove(); } } dznov(e, outputObjective, includeOutputItem, outputForChecker, outputVars); iter_items(dznov, e.model); if (hasChecker && !outputForChecker) { outputVars.push_back(new StringLit(Location().introduce(), "_checker = ")); auto* checker_output = new Call(Location().introduce(), ASTString("showCheckerOutput"), {}); checker_output->type(Type::parstring()); FunctionI* fi = e.model->matchFn(e, checker_output, false); assert(fi); checker_output->decl(fi); auto* show = new Call(Location().introduce(), ASTString("showDzn"), {checker_output}); show->type(Type::parstring()); fi = e.model->matchFn(e, show, false); assert(fi); show->decl(fi); outputVars.push_back(show); outputVars.push_back(new StringLit(Location().introduce(), ";\n")); } auto* newOutputItem = new OutputI(Location().introduce(), new ArrayLit(Location().introduce(), outputVars)); e.model->addItem(newOutputItem); } ArrayLit* create__json_output(EnvI& e, bool outputObjective, bool includeOutputItem, bool hasChecker) { std::vector outputVars; outputVars.push_back(new StringLit(Location().introduce(), "{\n")); class JSONOVisitor : public ItemVisitor { protected: EnvI& _e; bool _outputObjective; bool _includeOutputItem; std::vector& _outputVars; bool _hadAddToOutput; public: bool firstVar; JSONOVisitor(EnvI& e, bool outputObjective, bool includeOutputItem, std::vector& outputVars) : _e(e), _outputObjective(outputObjective), _outputVars(outputVars), _includeOutputItem(includeOutputItem), _hadAddToOutput(false), firstVar(true) {} void vVarDeclI(VarDeclI* vdi) { VarDecl* vd = vdi->e(); bool process_var = false; if (_outputObjective && vd->id()->idn() == -1 && vd->id()->v() == "_objective") { process_var = true; } else { if (vd->ann().contains(constants().ann.add_to_output)) { if (!_hadAddToOutput) { _outputVars.clear(); _outputVars.push_back(new StringLit(Location().introduce(), "{\n")); firstVar = true; } _hadAddToOutput = true; process_var = true; } else { if (!_hadAddToOutput) { process_var = vd->type().isvar() && (vd->e() == nullptr || vd->ann().contains(constants().ann.rhs_from_assignment)); } } } if (process_var) { std::ostringstream s; if (firstVar) { firstVar = false; } else { s << ",\n"; } s << " \"" << vd->id()->str() << "\"" << " : "; auto* sl = new StringLit(Location().introduce(), s.str()); _outputVars.push_back(sl); std::vector showArgs(1); showArgs[0] = vd->id(); Call* show = new Call(Location().introduce(), "showJSON", showArgs); show->type(Type::parstring()); FunctionI* fi = _e.model->matchFn(_e, show, false); assert(fi); show->decl(fi); _outputVars.push_back(show); } } void vOutputI(OutputI* oi) { if (_includeOutputItem) { std::ostringstream s; if (firstVar) { firstVar = false; } else { s << ",\n"; } s << " \"_output\"" << " : "; auto* sl = new StringLit(Location().introduce(), s.str()); _outputVars.push_back(sl); Call* concat = new Call(Location().introduce(), ASTString("concat"), {oi->e()}); concat->type(Type::parstring()); FunctionI* fi = _e.model->matchFn(_e, concat, false); assert(fi); concat->decl(fi); Call* show = new Call(Location().introduce(), ASTString("showJSON"), {concat}); show->type(Type::parstring()); fi = _e.model->matchFn(_e, show, false); assert(fi); show->decl(fi); _outputVars.push_back(show); } oi->remove(); } } jsonov(e, outputObjective, includeOutputItem, outputVars); iter_items(jsonov, e.model); if (hasChecker) { std::ostringstream s; if (jsonov.firstVar) { jsonov.firstVar = false; } else { s << ",\n"; } s << " \"_checker\"" << " : "; auto* sl = new StringLit(Location().introduce(), s.str()); outputVars.push_back(sl); Call* checker_output = new Call(Location().introduce(), ASTString("showCheckerOutput"), {}); checker_output->type(Type::parstring()); FunctionI* fi = e.model->matchFn(e, checker_output, false); assert(fi); checker_output->decl(fi); Call* show = new Call(Location().introduce(), ASTString("showJSON"), {checker_output}); show->type(Type::parstring()); fi = e.model->matchFn(e, show, false); assert(fi); show->decl(fi); outputVars.push_back(show); } outputVars.push_back(new StringLit(Location().introduce(), "\n}\n")); return new ArrayLit(Location().introduce(), outputVars); } void create_json_output_item(EnvI& e, bool outputObjective, bool includeOutputItem, bool hasChecker) { auto* newOutputItem = new OutputI(Location().introduce(), create__json_output(e, outputObjective, includeOutputItem, hasChecker)); e.model->addItem(newOutputItem); } void create_output(EnvI& e, FlatteningOptions::OutputMode outputMode, bool outputObjective, bool includeOutputItem, bool hasChecker) { // Create new output model OutputI* outputItem = nullptr; GCLock lock; switch (outputMode) { case FlatteningOptions::OUTPUT_DZN: create_dzn_output_item(e, outputObjective, includeOutputItem, hasChecker, false); break; case FlatteningOptions::OUTPUT_JSON: create_json_output_item(e, outputObjective, includeOutputItem, hasChecker); break; case FlatteningOptions::OUTPUT_CHECKER: create_dzn_output_item(e, outputObjective, includeOutputItem, hasChecker, true); break; default: if (e.model->outputItem() == nullptr) { create_dzn_output_item(e, outputObjective, false, false, false); } break; } // Copy output item from model into output model outputItem = copy(e, e.cmap, e.model->outputItem())->cast(); make_par(e, outputItem->e()); e.output->addItem(outputItem); // Copy all function definitions that are required for output into the output model class CollectFunctions : public EVisitor { public: EnvI& env; CollectFunctions(EnvI& env0) : env(env0) {} static bool enter(Expression* e) { if (e->type().isvar()) { Type t = e->type(); t.ti(Type::TI_PAR); e->type(t); } return true; } void vId(Id& i) { // Also collect functions from output_only variables we depend on if ((i.decl() != nullptr) && i.decl()->ann().contains(constants().ann.output_only)) { top_down(*this, i.decl()->e()); } } void vCall(Call& c) { std::vector tv(c.argCount()); for (unsigned int i = c.argCount(); (i--) != 0U;) { tv[i] = c.arg(i)->type(); tv[i].ti(Type::TI_PAR); } FunctionI* decl = env.output->matchFn(env, c.id(), tv, false); FunctionI* origdecl = env.model->matchFn(env, c.id(), tv, false); bool canReuseDecl = (decl != nullptr); if (canReuseDecl && (origdecl != nullptr)) { // Check if this is the exact same overloaded declaration as in the model for (unsigned int i = 0; i < decl->params().size(); i++) { if (decl->params()[i]->type() != origdecl->params()[i]->type()) { // no, the types don't match, so we have to copy the original decl canReuseDecl = false; break; } } } Type t; if (!canReuseDecl) { if (origdecl == nullptr || !is_completely_par(env, origdecl, tv)) { std::ostringstream ss; ss << "function " << c.id() << " is used in output, par version needed"; throw FlatteningError(env, c.loc(), ss.str()); } if (!origdecl->fromStdLib()) { auto* decl_copy = copy(env, env.cmap, origdecl)->cast(); if (decl_copy != decl) { decl = decl_copy; (void)env.output->registerFn(env, decl, true); env.output->addItem(decl); if (decl->e() != nullptr) { make_par(env, decl->e()); top_down(*this, decl->e()); } CollectOccurrencesE ce(env.outputVarOccurrences, decl); top_down(ce, decl->e()); top_down(ce, decl->ti()); for (unsigned int i = decl->params().size(); (i--) != 0U;) { top_down(ce, decl->params()[i]); } } } else { decl = origdecl; } } c.decl(decl); } } _cf(e); top_down(_cf, outputItem->e()); // If we are checking solutions using a checker model, all parameters of the checker model // have to be made available in the output model class OV1 : public ItemVisitor { public: EnvI& env; CollectFunctions& cf; OV1(EnvI& env0, CollectFunctions& cf0) : env(env0), cf(cf0) {} void vVarDeclI(VarDeclI* vdi) { if (vdi->e()->ann().contains(constants().ann.mzn_check_var)) { auto* output_vd = copy(env, env.cmap, vdi->e())->cast(); top_down(cf, output_vd); } } } _ov1(e, _cf); iter_items(_ov1, e.model); // Copying the output item and the functions it depends on has created copies // of all dependent VarDecls. However the output model does not contain VarDeclIs for // these VarDecls yet. This iterator processes all variable declarations of the // original model, and if they were copied (i.e., if the output model depends on them), // the corresponding VarDeclI is created in the output model. class OV2 : public ItemVisitor { public: EnvI& env; OV2(EnvI& env0) : env(env0) {} void vVarDeclI(VarDeclI* vdi) { auto idx = env.outputVarOccurrences.idx.find(vdi->e()->id()); if (idx != env.outputVarOccurrences.idx.end()) { return; } if (Expression* vd_e = env.cmap.find(vdi->e())) { // We found a copied VarDecl, now need to create a VarDeclI auto* vd = vd_e->cast(); auto* vdi_copy = copy(env, env.cmap, vdi)->cast(); Type t = vdi_copy->e()->ti()->type(); t.ti(Type::TI_PAR); vdi_copy->e()->ti()->domain(nullptr); vdi_copy->e()->flat(vdi->e()->flat()); bool isCheckVar = vdi_copy->e()->ann().contains(constants().ann.mzn_check_var); Call* checkVarEnum = vdi_copy->e()->ann().getCall(constants().ann.mzn_check_enum_var); vdi_copy->e()->ann().clear(); if (isCheckVar) { vdi_copy->e()->ann().add(constants().ann.mzn_check_var); } if (checkVarEnum != nullptr) { vdi_copy->e()->ann().add(checkVarEnum); } vdi_copy->e()->introduced(false); IdMap::iterator it; if (!vdi->e()->type().isPar()) { if (vd->flat() == nullptr && vdi->e()->e() != nullptr && vdi->e()->e()->type().isPar()) { // Don't have a flat version of this variable, but the original has a right hand // side that is par, so we can use that. Expression* flate = eval_par(env, vdi->e()->e()); output_vardecls(env, vdi_copy, flate); vd->e(flate); } else { vd = follow_id_to_decl(vd->id())->cast(); VarDecl* reallyFlat = vd->flat(); while ((reallyFlat != nullptr) && reallyFlat != reallyFlat->flat()) { reallyFlat = reallyFlat->flat(); } if (reallyFlat == nullptr) { // The variable doesn't have a flat version. This can only happen if // the original variable had type-inst var, but a right-hand-side that // was par, so follow_id_to_decl lead to a par variable. assert(vd->e() && vd->e()->type().isPar()); Expression* flate = eval_par(env, vd->e()); output_vardecls(env, vdi_copy, flate); vd->e(flate); } else if ((vd->flat()->e() != nullptr) && vd->flat()->e()->type().isPar()) { // We can use the right hand side of the flat version of this variable Expression* flate = copy(env, env.cmap, follow_id(reallyFlat->id())); output_vardecls(env, vdi_copy, flate); vd->e(flate); } else if ((it = env.reverseMappers.find(vd->id())) != env.reverseMappers.end()) { // Found a reverse mapper, so we need to add the mapping function to the // output model to map the FlatZinc value back to the model variable. Call* rhs = copy(env, env.cmap, it->second())->cast(); check_output_par_fn(env, rhs); output_vardecls(env, vdi_copy, rhs); vd->e(rhs); } else if (cannot_use_rhs_for_output(env, vd->e())) { // If the VarDecl does not have a usable right hand side, it needs to be // marked as output in the FlatZinc vd->e(nullptr); assert(vd->flat()); if (vd->type().dim() == 0) { vd->flat()->addAnnotation(constants().ann.output_var); check_rename_var(env, vd); } else { bool needOutputAnn = true; if ((reallyFlat->e() != nullptr) && reallyFlat->e()->isa()) { auto* al = reallyFlat->e()->cast(); for (unsigned int i = 0; i < al->size(); i++) { if (Id* id = (*al)[i]->dynamicCast()) { if (env.reverseMappers.find(id) != env.reverseMappers.end()) { needOutputAnn = false; break; } } } if (!needOutputAnn) { output_vardecls(env, vdi_copy, al); vd->e(copy(env, env.cmap, al)); } } if (needOutputAnn) { std::vector args(vdi->e()->type().dim()); for (unsigned int i = 0; i < args.size(); i++) { if (vdi->e()->ti()->ranges()[i]->domain() == nullptr) { args[i] = new SetLit(Location().introduce(), eval_intset(env, vd->flat()->ti()->ranges()[i]->domain())); } else { args[i] = new SetLit(Location().introduce(), eval_intset(env, vd->ti()->ranges()[i]->domain())); } } auto* al = new ArrayLit(Location().introduce(), args); args.resize(1); args[0] = al; vd->flat()->addAnnotation( new Call(Location().introduce(), constants().ann.output_array, args)); check_rename_var(env, vd); } } } if ((reallyFlat != nullptr) && env.outputFlatVarOccurrences.find(reallyFlat) == -1) { env.outputFlatVarOccurrences.addIndex(reallyFlat, static_cast(env.output->size())); } } } else { if (vd->flat() == nullptr && vdi->e()->e() != nullptr) { // Need to process right hand side of variable, since it may contain // identifiers that are only in the FlatZinc and that we would // therefore fail to copy into the output model output_vardecls(env, vdi_copy, vdi->e()->e()); } } make_par(env, vdi_copy->e()); env.outputVarOccurrences.addIndex(vdi_copy, static_cast(env.output->size())); CollectOccurrencesE ce(env.outputVarOccurrences, vdi_copy); top_down(ce, vdi_copy->e()); env.output->addItem(vdi_copy); } } } _ov2(e); iter_items(_ov2, e.model); CollectOccurrencesE ce(e.outputVarOccurrences, outputItem); top_down(ce, outputItem->e()); e.model->mergeStdLib(e, e.output); process_deletions(e); } Expression* is_fixed_domain(EnvI& env, VarDecl* vd) { if (vd->type() != Type::varbool() && vd->type() != Type::varint() && vd->type() != Type::varfloat()) { return nullptr; } Expression* e = vd->ti()->domain(); if (e == constants().literalTrue || e == constants().literalFalse) { return e; } if (auto* sl = Expression::dynamicCast(e)) { if (sl->type().bt() == Type::BT_INT) { IntSetVal* isv = eval_intset(env, sl); return isv->min() == isv->max() ? IntLit::a(isv->min()) : nullptr; } if (sl->type().bt() == Type::BT_FLOAT) { FloatSetVal* fsv = eval_floatset(env, sl); return fsv->min() == fsv->max() ? FloatLit::a(fsv->min()) : nullptr; } } return nullptr; } void finalise_output(EnvI& e) { if (e.output->size() > 0) { // Adapt existing output model // (generated by repeated flattening) e.outputVarOccurrences.clear(); for (unsigned int i = 0; i < e.output->size(); i++) { Item* item = (*e.output)[i]; if (item->removed()) { continue; } switch (item->iid()) { case Item::II_VD: { VarDecl* vd = item->cast()->e(); IdMap::iterator it; GCLock lock; VarDecl* reallyFlat = vd->flat(); while ((reallyFlat != nullptr) && reallyFlat != reallyFlat->flat()) { reallyFlat = reallyFlat->flat(); } if (vd->e() == nullptr) { if (((vd->flat()->e() != nullptr) && vd->flat()->e()->type().isPar()) || (is_fixed_domain(e, vd->flat()) != nullptr)) { VarDecl* reallyFlat = vd->flat(); while (reallyFlat != reallyFlat->flat()) { reallyFlat = reallyFlat->flat(); } remove_is_output(reallyFlat); Expression* flate; if (Expression* fd = is_fixed_domain(e, vd->flat())) { flate = fd; } else { flate = copy(e, e.cmap, follow_id(reallyFlat->id())); } output_vardecls(e, item, flate); vd->e(flate); } else if ((it = e.reverseMappers.find(vd->id())) != e.reverseMappers.end()) { Call* rhs = copy(e, e.cmap, it->second())->cast(); check_output_par_fn(e, rhs); remove_is_output(reallyFlat); output_vardecls(e, item, it->second()->cast()); vd->e(rhs); if (e.varOccurrences.occurrences(reallyFlat) == 0 && reallyFlat->e() == nullptr) { auto it = e.varOccurrences.idx.find(reallyFlat->id()); assert(it != e.varOccurrences.idx.end()); e.flatRemoveItem((*e.flat())[it->second]->cast()); } } else { // If the VarDecl does not have a usable right hand side, it needs to be // marked as output in the FlatZinc assert(vd->flat()); bool needOutputAnn = true; if ((reallyFlat->e() != nullptr) && reallyFlat->e()->isa()) { Id* ident = reallyFlat->e()->cast(); if (e.reverseMappers.find(ident) != e.reverseMappers.end()) { needOutputAnn = false; remove_is_output(vd); remove_is_output(reallyFlat); vd->e(copy(e, e.cmap, ident)); Type al_t(vd->e()->type()); al_t.ti(Type::TI_PAR); vd->e()->type(al_t); output_vardecls(e, item, ident); if (e.varOccurrences.occurrences(reallyFlat) == 0) { auto it = e.varOccurrences.idx.find(reallyFlat->id()); assert(it != e.varOccurrences.idx.end()); e.flatRemoveItem((*e.flat())[it->second]->cast()); } } } else if ((reallyFlat->e() != nullptr) && reallyFlat->e()->isa()) { auto* al = reallyFlat->e()->cast(); for (unsigned int i = 0; i < al->size(); i++) { if (Id* ident = follow_id_to_value((*al)[i])->dynamicCast()) { if (e.reverseMappers.find(ident) != e.reverseMappers.end()) { needOutputAnn = false; break; } } } if (!needOutputAnn) { remove_is_output(vd); remove_is_output(reallyFlat); if (e.varOccurrences.occurrences(reallyFlat) == 0) { auto it = e.varOccurrences.idx.find(reallyFlat->id()); assert(it != e.varOccurrences.idx.end()); e.flatRemoveItem((*e.flat())[it->second]->cast()); } output_vardecls(e, item, al); vd->e(copy(e, e.cmap, al)); Type al_t(vd->e()->type()); al_t.ti(Type::TI_PAR); vd->e()->type(al_t); } } if (needOutputAnn) { if (!is_output(vd->flat())) { GCLock lock; if (vd->type().dim() == 0) { vd->flat()->addAnnotation(constants().ann.output_var); } else { std::vector args(vd->type().dim()); for (unsigned int i = 0; i < args.size(); i++) { if (vd->ti()->ranges()[i]->domain() == nullptr) { args[i] = new SetLit(Location().introduce(), eval_intset(e, vd->flat()->ti()->ranges()[i]->domain())); } else { args[i] = new SetLit(Location().introduce(), eval_intset(e, vd->ti()->ranges()[i]->domain())); } } auto* al = new ArrayLit(Location().introduce(), args); args.resize(1); args[0] = al; vd->flat()->addAnnotation( new Call(Location().introduce(), constants().ann.output_array, args)); } check_rename_var(e, vd); } } } vd->flat(nullptr); // Remove enum type Type vdt = vd->type(); vdt.enumId(0); vd->type(vdt); vd->ti()->type(vdt); } e.outputVarOccurrences.addIndex(item->cast(), static_cast(i)); CollectOccurrencesE ce(e.outputVarOccurrences, item); top_down(ce, vd); } break; case Item::II_OUT: { CollectOccurrencesE ce(e.outputVarOccurrences, item); top_down(ce, item->cast()->e()); } break; case Item::II_FUN: { CollectOccurrencesE ce(e.outputVarOccurrences, item); top_down(ce, item->cast()->e()); top_down(ce, item->cast()->ti()); for (unsigned int i = item->cast()->params().size(); (i--) != 0U;) { top_down(ce, item->cast()->params()[i]); } } break; default: throw FlatteningError(e, item->loc(), "invalid item in output model"); } } } process_deletions(e); } } // namespace MiniZinc