void kMST_ILP::solve() { try { // initialize CPLEX env = IloEnv(); model = IloModel( env ); addTreeConstraints(); // call first, initialises edges // add model-specific constraints if( model_type == "scf" ) modelSCF(); else if( model_type == "mcf" ) modelMCF(); else if( model_type == "mtz" ) modelMTZ(); else { cerr << "No existing model chosen\n"; exit( -1 ); } addObjectiveFunction(); // build model cplex = IloCplex( model ); // export model to a text file //cplex.exportModel( "model.lp" ); // set parameters setCPLEXParameters(); // solve model cout << "Calling CPLEX solve ...\n"; cplex.solve(); cout << "CPLEX finished.\n\n"; cout << "CPLEX status: " << cplex.getStatus() << "\n"; cout << "Branch-and-Bound nodes: " << cplex.getNnodes() << "\n"; cout << "Objective value: " << cplex.getObjValue() << "\n"; cout << "CPU time: " << Tools::CPUtime() << "\n\n"; // show result IloNumArray edgesSelected(env, edges.getSize()), flowRes(env, edges.getSize()), uRes(env, instance.n_nodes); cplex.getValues(edgesSelected, edges); if (model_type == "scf") { cplex.getValues(flowRes, flow_scf); } else if (model_type == "mtz") { try { cplex.getValues(uRes, u); } catch ( IloException& e ) { cerr << "Exception while extracting u: " << e << endl; uRes = IloNumArray(env, 0); } } cout << "Edges:\n"; for (unsigned int i=0; i<edges.getSize(); i++) { // skip unused ones if (((int)edgesSelected[i]) == 0 ) { continue; } if (i == instance.n_edges) { cout << endl; } bool direction = ( i >= instance.n_edges); cout << " " << setw(4) << i << ": " << ((int)edgesSelected[i]) << " "; // flow if (model_type == "scf") { cout << "f: " << setw(2) << (flowRes[i]); } else if (model_type == "mtz") { if (uRes.getSize() != 0) { cout << "u: " ; if (i < instance.n_edges) { //cout << setw(2) << instance.edges[i % instance.n_edges].v1 << ": "; cout << setw(2) <<((int)uRes[ instance.edges[i % instance.n_edges].v1]) << " "; //cout << setw(2) << instance.edges[i % instance.n_edges].v2 << ": "; cout << setw(2) << ((int)uRes[ instance.edges[i % instance.n_edges].v2]) ; } else { //cout << setw(2) << instance.edges[i % instance.n_edges].v2 << ": "; cout << setw(2) <<((int)uRes[ instance.edges[i % instance.n_edges].v2]) << " "; //cout << setw(2) << instance.edges[i % instance.n_edges].v1 << ": "; cout << setw(2) <<((int)uRes[ instance.edges[i % instance.n_edges].v1]) ; } } } cout << " " << Tools::edgeToString(instance.edges[i % instance.n_edges], direction) ; cout << endl; } } catch (IloAlgorithm::CannotExtractException& e) { cerr << "CannotExtractException: " << e << endl ; IloExtractableArray failed = e.getExtractables(); for (IloInt i = 0; i < failed.getSize(); ++i) { cerr << "\t" << failed[i] << std::endl; } } catch( IloException& e ) { cerr << "kMST_ILP: exception " << e << "\n"; exit( -1 ); } catch( ... ) { cerr << "kMST_ILP: unknown exception.\n"; exit( -1 ); } }
Variables *kMST_ILP::modelMCF() { MCFVariables *v = new MCFVariables(); /***** generic part ***/ const vector<Instance::Edge> edges = directed_edges(instance.edges); const u_int n_edges = edges.size(); /* $x_{ij} \in \{0, 1\}$ variables denote whether edge (i, j) is active. */ v->xs = createVarArrayXs(env, edges, n_edges); /* $v_i \in \{0, 1\}$ variables denote whether node i is active. */ v->vs = createVarArrayVs(env, instance.n_nodes); /* add objective function */ addObjectiveFunction(env, model, v->xs, edges, n_edges); /* There are exactly k - 1 edges not counting edges from the artificial root node 0. */ addConstraint_k_minus_one_active_edges(env,model,v->xs,edges,n_edges,this->k); /* Exactly one node is chosen as the tree root. */ addConstraint_one_active_outgoing_arc_for_node_zero(env,model,v->xs,edges,n_edges); /* No edge leads back to the artificial root node 0. */ addConstraint_no_active_incoming_arc_for_node_zero(env,model,v->xs,edges,n_edges); IloExprArray e_in_degree = createExprArray_in_degree(env, edges, n_edges, v->xs, instance); IloExprArray e_out_degree = createExprArray_out_degree(env, edges, n_edges, v->xs, instance); /* Inactive nodes have no outgoing active edges, active ones at most k - 1. TODO: A tighter bound is to take the sum of incoming goods - 1.*/ addConstraint_bound_on_outgoing_arcs(model,v->vs,e_out_degree,instance,this->k); /* Active nodes have at least one active arc.*/ addConstraint_active_node_at_least_one_active_arc(model,v->vs,e_in_degree, e_out_degree,instance); /* Exactly one incoming edge for an active node and none for an inactive node (omitting artificial root). */ addConstraint_in_degree_one_for_active_node_zero_for_inactive(model,v->vs,e_in_degree,instance); //note: position matters. Tried worse positions than this one /* $\sum_{i > 0} v_i = k$. Ensure that exactly k nodes are active. */ addConstraint_k_nodes_active(env, model, v->vs, instance, this->k); e_in_degree.endElements(); e_out_degree.endElements(); /***** MCF specific part ***/ /* $f^k_{ij} \in \{0, 1\}$ variables denote the flow on edge (i, j) for commodity k. */ for (u_int i = 0; i < instance.n_nodes; i++) { v->fss.push_back(IloBoolVarArray(env, n_edges)); } for (u_int k = 0; k < n_edges; k++) { const u_int i = edges[k].v1; const u_int j = edges[k].v2; for (u_int l = 0; l < (u_int) instance.n_nodes; l++) { v->fss[l][k] = IloBoolVar(env, Tools::indicesToString("f", l, i, j).c_str()); } } /* * Each commodity l is generated once by the artificial root node if node l is active, not at all otherwise: * $\forall l \in \{1, \ldots, n\}: \sum_{j:j>0,(0,j) \in A} f^l_{0j} == v_l$ */ for (u_int c = 1; c < instance.n_nodes; c++){ IloExpr e_one_commodity(env); for (u_int m = 0; m < n_edges; m++) { const u_int i = edges[m].v1; const u_int j = edges[m].v2; if (i == 0 && j > 0){ e_one_commodity += v->fss[c][m]; } } model.add(e_one_commodity == v->vs[c]); e_one_commodity.end(); } /* * The artifical root generates k commodities: * $\forall l \in \{0,\ldots,n\}\sum_{j:j>0,(0,j) \in A} f^l_{0j} = k$. */ IloExpr e_root_generates_k(env); for (u_int c = 0; c < instance.n_nodes; c++){ for (u_int m = 0; m < n_edges; m++) { const u_int i = edges[m].v1; const u_int j = edges[m].v2; if (i == 0 && j > 0){ e_root_generates_k += v->fss[c][m]; } } } model.add(e_root_generates_k == this->k); e_root_generates_k.end(); /* * No commodity is generated for the artificial root: * $\forall i, j: f^0_{ij} = 0$. */ for (u_int m = 0; m < n_edges; m++) { model.add(v->fss[0][m] == 0); } /* * Transmitted commodities end up at the target node: * $\forall l>0: \sum_i f^l_{il} = \sum_j f^l_{0j}$. (here: = v_l) */ for (u_int c = 1; c < (u_int) instance.n_nodes; c++){ IloExpr e_commodity_reaches_target(env); for (u_int m = 0; m < n_edges; m++) { const u_int i = edges[m].v1; const u_int j = edges[m].v2; if (i != c && j == c){ e_commodity_reaches_target += v->fss[c][m]; } } model.add(e_commodity_reaches_target == v->vs[c]); e_commodity_reaches_target.end(); } /* * Once reached, the commodity never leaves the target node: * $\forall l>0: \sum_j f^l_{lj} = 0$. */ for (u_int c = 1; c < (u_int) instance.n_nodes; c++){ IloExpr e_commodity_stays_at_target(env); for (u_int m = 0; m < n_edges; m++) { const u_int i = edges[m].v1; const u_int j = edges[m].v2; if (i == c && j != c){ e_commodity_stays_at_target += v->fss[c][m]; } } model.add(e_commodity_stays_at_target == 0); e_commodity_stays_at_target.end(); } /* * Flow is conserved when not at target node. * $\forall j, l s.t. j \neq l: \sum_i f^l_{ij} = \sum_i f^l_{ji}$. */ for (u_int c = 0; c < (u_int) instance.n_nodes; c++){ IloExprArray e_in_flow(env, instance.n_nodes); IloExprArray e_out_flow(env, instance.n_nodes); for (u_int m = 0; m < instance.n_nodes; m++){ e_in_flow[m] = IloExpr(env); e_out_flow[m] = IloExpr(env); } for (u_int m = 0; m < n_edges; m++) { const u_int i = edges[m].v1; const u_int j = edges[m].v2; e_out_flow[i] += v->fss[c][m]; e_in_flow[j] += v->fss[c][m]; } for (u_int m = 1; m < instance.n_nodes; m++){ if (m != c) { model.add(e_in_flow[m] == e_out_flow[m]); } } e_in_flow.endElements(); e_out_flow.endElements(); } /* * Commodities may only be transmitted on active edges: * $\forall l, i, j: f^l_{ij} \leq x_{ij}$. */ for (u_int c = 0; c < (u_int) instance.n_nodes; c++){ for (u_int m = 0; m < n_edges; m++) { model.add(v->fss[c][m] <= v->xs[m]); } } /* * For each commodity l , the total flow is <= k if node l is active, 0 otherwise * (works well for all before g05, k=n/2 which is a bit slower with this) */ for (u_int c = 1; c < (u_int) instance.n_nodes; c++){ IloExpr e_total_flow(env); for (u_int m = 0; m < n_edges; m++) { e_total_flow += v->fss[c][m]; } model.add(e_total_flow <= this->k * v->vs[c]); e_total_flow.end(); } return v; }
Variables *kMST_ILP::modelMTZ() { MTZVariables *v = new MTZVariables(); /***** generic part ***/ const vector<Instance::Edge> edges = directed_edges(instance.edges); const u_int n_edges = edges.size(); /* $x_{ij} \in \{0, 1\}$ variables denote whether edge (i, j) is active. */ v->xs = createVarArrayXs(env, edges, n_edges); /* $v_i \in \{0, 1\}$ variables denote whether node i is active. */ v->vs = createVarArrayVs(env, instance.n_nodes); /* add objective function */ addObjectiveFunction(env, model, v->xs, edges, n_edges); /* There are exactly k - 1 edges not counting edges from the artificial root node 0. */ addConstraint_k_minus_one_active_edges(env,model,v->xs,edges,n_edges,this->k); /* Exactly one node is chosen as the tree root. */ addConstraint_one_active_outgoing_arc_for_node_zero(env,model,v->xs,edges,n_edges); /* No edge leads back to the artificial root node 0. */ addConstraint_no_active_incoming_arc_for_node_zero(env,model,v->xs,edges,n_edges); IloExprArray e_in_degree = createExprArray_in_degree(env, edges, n_edges, v->xs, instance); IloExprArray e_out_degree = createExprArray_out_degree(env, edges, n_edges, v->xs, instance); /* Inactive nodes have no outgoing active edges, active ones at most k - 1. TODO: A tighter bound is to take the sum of incoming goods - 1.*/ addConstraint_bound_on_outgoing_arcs(model,v->vs,e_out_degree,instance,this->k); /* Active nodes have at least one active arc.*/ addConstraint_active_node_at_least_one_active_arc(model,v->vs,e_in_degree, e_out_degree,instance); /* Exactly one incoming edge for an active node and none for an inactive node (omitting artificial root). */ addConstraint_in_degree_one_for_active_node_zero_for_inactive(model,v->vs,e_in_degree,instance); //note: position matters. Tried worse positions than this one /* $\sum_{i > 0} v_i = k$. Ensure that exactly k nodes are active. */ addConstraint_k_nodes_active(env, model, v->vs, instance, this->k); e_in_degree.endElements(); e_out_degree.endElements(); /***** MTZ specific part ***/ /* $u_i \in [0, k]$ variables are used to impose an order on nodes. */ v->us = IloIntVarArray(env, instance.n_nodes); for (u_int i = 0; i < instance.n_nodes; i++) { v->us[i] = IloIntVar(env, 0, k, Tools::indicesToString("u", i).c_str()); } IloExpr e3(env); e3 += v->us[0]; /* $u_0 = 0$. Set level of artificial root 0 to 0. */ model.add(e3 == 0); e3.end(); for (u_int k = 0; k < n_edges; k++) { const u_int i = edges[k].v1; const u_int j = edges[k].v2; IloExpr e4(env); e4 = v->us[i] + v->xs[k] - v->us[j] - (-v->xs[k] + 1) * this->k; /* $\forall i, j: u_i + x_{ij} \leq u_j + (1 - x_{ij})k$. * Enforce order hierarchy on nodes. */ model.add(e4 <= 0); e4.end(); } for (u_int i = 0; i < instance.n_nodes; i++) { /* $\forall i: u_i <= nv_i$ force order of inactive nodes to 0 */ /* helps with big instances 6,7,8 */ model.add(v->us[i] <= v->vs[i] * (int) instance.n_nodes); } return v; }
Variables *kMST_ILP::modelSCF() { SCFVariables *v = new SCFVariables(); const vector<Instance::Edge> edges = directed_edges(instance.edges); const u_int n_edges = edges.size(); /* $x_{ij} \in \{0, 1\}$ variables denote whether edge (i, j) is active. */ v->xs = createVarArrayXs(env, edges, n_edges); /* $v_i \in \{0, 1\}$ variables denote whether node i is active. */ v->vs = createVarArrayVs(env, instance.n_nodes); /* add objective function */ addObjectiveFunction(env, model, v->xs, edges, n_edges); /* There are exactly k - 1 edges not counting edges from the artificial root node 0. */ addConstraint_k_minus_one_active_edges(env,model,v->xs,edges,n_edges,this->k); /* Exactly one node is chosen as the tree root. */ addConstraint_one_active_outgoing_arc_for_node_zero(env,model,v->xs,edges,n_edges); /* No edge leads back to the artificial root node 0. */ addConstraint_no_active_incoming_arc_for_node_zero(env,model,v->xs,edges,n_edges); IloExprArray e_in_degree = createExprArray_in_degree(env, edges, n_edges, v->xs, instance); IloExprArray e_out_degree = createExprArray_out_degree(env, edges, n_edges, v->xs, instance); /* Inactive nodes have no outgoing active edges, active ones at most k - 1. TODO: A tighter bound is to take the sum of incoming goods - 1.*/ addConstraint_bound_on_outgoing_arcs(model,v->vs,e_out_degree,instance,this->k); /* Active nodes have at least one active arc.*/ addConstraint_active_node_at_least_one_active_arc(model,v->vs,e_in_degree, e_out_degree,instance); /* Exactly one incoming edge for an active node and none for an inactive node (omitting artificial root). */ addConstraint_in_degree_one_for_active_node_zero_for_inactive(model,v->vs,e_in_degree,instance); //note: position matters. Tried worse positions than this one /* $\sum_{i > 0} v_i = k$. Ensure that exactly k nodes are active. */ addConstraint_k_nodes_active(env, model, v->vs, instance, this->k); e_in_degree.endElements(); e_out_degree.endElements(); /* $f_{ij} \in [0, k - 1]$ variables denote the number of goods on edge (i, j). */ v->fs = createVarArrayFs(env, edges, n_edges); IloExprArray e_in_flow = createExprArray_in_flow(env, edges, n_edges, v->fs, instance); IloExprArray e_out_flow = createExprArray_out_flow(env, edges, n_edges, v->fs, instance); /* * Active nodes consume exactly 1 commodity, inactive nodes conserve flow. * $\forall i \neq 0: \sum_j (f_{ji} - f_{ij}) == v_i$ */ for (u_int i = 0; i < instance.n_nodes; i++) { if (i == 0){ /* Don't add a constraint for the artificial root. */ } else if (i > 0) { /* outflow = inflow -1 for active nodes, same for inactive nodes. */ model.add(v->vs[i] == e_in_flow[i] - e_out_flow[i]); } } e_in_flow.endElements(); e_out_flow.endElements(); /* $\forall i, j \neq 0: f_{ij} \leq kx_{ij}$. Only active edges transport goods. * TODO: bound sum of incoming goods per node. * $\forall i, j s.t. i or j is 0: f_{ij} = kx_{ij}$. Only a single edge * incident on the artificial root transports goods. */ for (u_int k = 0; k < n_edges; k++) { const u_int i = edges[k].v1; const u_int j = edges[k].v2; if (i == 0 || j == 0) { model.add(v->fs[k] == this->k * v->xs[k]); } else { model.add(v->fs[k] <= (this->k) * v->xs[k]); } } return v; }