z3-z3-4.13.0.src.math.simplex.network_flow_def.h Maven / Gradle / Ivy
The newest version!
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
network_flow_def.h
Abstract:
Implements Network Simplex algorithm for min cost flow problem
Author:
Anh-Dung Phan (t-anphan) 2013-10-24
Notes:
--*/
#pragma once
#include "math/simplex/network_flow.h"
#include "util/uint_set.h"
#include "smt/spanning_tree_def.h"
namespace smt {
template
bool network_flow::first_eligible_pivot::choose_entering_edge() {
numeral cost = numeral::zero();
int num_edges = m_graph.get_num_edges();
for (int i = m_next_edge; i < m_next_edge + num_edges; ++i) {
edge_id id = i % num_edges;
if (edge_in_tree(id)) {
continue;
}
node src = m_graph.get_source(id);
node tgt = m_graph.get_target(id);
cost = m_potentials[src] - m_potentials[tgt] - m_graph.get_weight(id);
if (cost.is_pos()) {
m_next_edge = m_enter_id = id;
return true;
}
}
return false;
};
template
bool network_flow::best_eligible_pivot::choose_entering_edge() {
unsigned num_edges = m_graph.get_num_edges();
numeral cost = numeral::zero();
for (unsigned i = 0; i < num_edges; ++i) {
node src = m_graph.get_source(i);
node tgt = m_graph.get_target(i);
if (!edge_in_tree(i)) {
numeral new_cost = m_potentials[src] - m_potentials[tgt] - m_graph.get_weight(i);
if (new_cost > cost) {
cost = new_cost;
m_enter_id = i;
}
}
}
return cost.is_pos();
};
template
bool network_flow::candidate_list_pivot::choose_entering_edge() {
numeral cost = numeral::zero();
if (m_current_length == 0 || m_minor_step == MINOR_STEP_LIMIT) {
// Build the candidate list
unsigned num_edges = m_graph.get_num_edges();
m_current_length = 0;
for (unsigned i = m_next_edge; i < m_next_edge + num_edges; ++i) {
edge_id id = (i >= num_edges) ? i - num_edges : i;
node src = m_graph.get_source(id);
node tgt = m_graph.get_target(id);
if (!edge_in_tree(id)) {
numeral new_cost = m_potentials[src] - m_potentials[tgt] - m_graph.get_weight(id);
if (new_cost.is_pos()) {
m_candidates[m_current_length] = id;
++m_current_length;
if (new_cost > cost) {
cost = new_cost;
m_enter_id = id;
}
}
if (m_current_length >= m_num_candidates) break;
}
}
m_next_edge = m_enter_id;
m_minor_step = 1;
return cost.is_pos();
}
++m_minor_step;
for (unsigned i = 0; i < m_current_length; ++i) {
edge_id id = m_candidates[i];
node src = m_graph.get_source(id);
node tgt = m_graph.get_target(id);
if (!edge_in_tree(id)) {
numeral new_cost = m_potentials[src] - m_potentials[tgt] - m_graph.get_weight(id);
if (new_cost > cost) {
cost = new_cost;
m_enter_id = id;
}
// Remove stale candidates
if (!new_cost.is_pos()) {
--m_current_length;
m_candidates[i] = m_candidates[m_current_length];
--i;
}
}
}
return cost.is_pos();
};
template
network_flow::network_flow(graph & g, vector const & balances) :
m_balances(balances) {
// Network flow graph has the edges in the reversed order compared to constraint graph
// We only take enabled edges from the original graph
for (unsigned i = 0; i < g.get_num_nodes(); ++i) {
m_graph.init_var(i);
}
vector const & es = g.get_all_edges();
for (unsigned i = 0; i < es.size(); ++i) {
edge const & e = es[i];
if (e.is_enabled()) {
m_graph.add_edge(e.get_target(), e.get_source(), e.get_weight(), explanation());
}
}
TRACE("network_flow", {
tout << "Difference logic optimization:" << std::endl;
display_dual(tout);
tout << "Minimum cost flow:" << std::endl;
display_primal(tout);
};);
m_step = 0;
m_tree = alloc(basic_spanning_tree, m_graph);
}
template
void network_flow::initialize() {
TRACE("network_flow", tout << "initialize...\n";);
// Create an artificial root node to construct initial spanning tree
unsigned num_nodes = m_graph.get_num_nodes();
unsigned num_edges = m_graph.get_num_edges();
node root = num_nodes;
m_graph.init_var(root);
m_potentials.resize(num_nodes + 1);
m_potentials[root] = numeral::zero();
m_balances.resize(num_nodes + 1);
fin_numeral sum_supply = fin_numeral::zero();
for (unsigned i = 0; i < num_nodes; ++i) {
sum_supply += m_balances[i];
}
m_balances[root] = -sum_supply;
m_flows.resize(num_nodes + num_edges);
m_flows.fill(numeral::zero());
m_states.resize(num_nodes + num_edges);
m_states.fill(LOWER);
// Create artificial edges from/to root node to/from other nodes and initialize the spanning tree
svector tree;
for (unsigned i = 0; i < num_nodes; ++i) {
bool is_forward = !m_balances[i].is_neg();
m_states[num_edges + i] = BASIS;
node src = is_forward ? i : root;
node tgt = is_forward ? root : i;
m_flows[num_edges + i] = is_forward ? m_balances[i] : -m_balances[i];
m_potentials[i] = is_forward ? numeral::one() : -numeral::one();
tree.push_back(m_graph.add_edge(src, tgt, numeral::one(), explanation()));
}
m_tree->initialize(tree);
TRACE("network_flow",
tout << pp_vector("Potentials", m_potentials);
tout << pp_vector("Flows", m_flows);
tout << "Cost: " << get_cost() << "\n";
tout << "Spanning tree:\n";
display_spanning_tree(tout);
display_primal(tout););
SASSERT(check_well_formed());
}
template
void network_flow::update_potentials() {
node src = m_graph.get_source(m_enter_id);
node tgt = m_graph.get_target(m_enter_id);
numeral cost = m_potentials[src] - m_potentials[tgt] - m_graph.get_weight(m_enter_id);
numeral change;
node start;
if (m_tree->in_subtree_t2(tgt)) {
change = cost;
start = tgt;
}
else {
change = -cost;
start = src;
}
SASSERT(m_tree->in_subtree_t2(start));
TRACE("network_flow", tout << "update_potentials of T_" << start << " with change = " << change << "...\n";);
svector descendants;
m_tree->get_descendants(start, descendants);
SASSERT(descendants.size() >= 1);
for (unsigned i = 0; i < descendants.size(); ++i) {
node u = descendants[i];
m_potentials[u] += change;
}
TRACE("network_flow", tout << pp_vector("Potentials", m_potentials););
}
template
void network_flow::update_flows() {
m_flows[m_enter_id] += *m_delta;
node src = m_graph.get_source(m_enter_id);
node tgt = m_graph.get_target(m_enter_id);
svector path;
bool_vector against;
m_tree->get_path(src, tgt, path, against);
SASSERT(path.size() >= 1);
for (unsigned i = 0; i < path.size(); ++i) {
edge_id e_id = path[i];
m_flows[e_id] += against[i] ? - *m_delta : *m_delta;
}
TRACE("network_flow", tout << pp_vector("Flows", m_flows););
}
template
bool network_flow::choose_leaving_edge() {
node src = m_graph.get_source(m_enter_id);
node tgt = m_graph.get_target(m_enter_id);
m_delta.set_invalid();
edge_id leave_id = null_edge_id;
svector path;
bool_vector against;
m_tree->get_path(src, tgt, path, against);
SASSERT(path.size() >= 1);
for (unsigned i = 0; i < path.size(); ++i) {
edge_id e_id = path[i];
if (against[i] && (!m_delta || m_flows[e_id] < *m_delta)) {
m_delta = m_flows[e_id];
leave_id = e_id;
}
}
m_leave_id = leave_id;
return m_delta;
}
template
void network_flow::update_spanning_tree() {
m_tree->update(m_enter_id, m_leave_id);
}
template
bool network_flow::choose_entering_edge(pivot_rule pr) {
if (!m_pivot || pr != m_pivot->rule()) {
switch (pr) {
case FIRST_ELIGIBLE:
m_pivot = alloc(first_eligible_pivot, m_graph, m_potentials, m_states, m_enter_id);
break;
case BEST_ELIGIBLE:
m_pivot = alloc(best_eligible_pivot, m_graph, m_potentials, m_states, m_enter_id);
break;
case CANDIDATE_LIST:
m_pivot = alloc(candidate_list_pivot, m_graph, m_potentials, m_states, m_enter_id);
break;
default:
UNREACHABLE();
}
}
return m_pivot->choose_entering_edge();
}
// Minimize cost flows
template
min_flow_result network_flow::min_cost(pivot_rule pr) {
initialize();
while (choose_entering_edge(pr)) {
bool bounded = choose_leaving_edge();
if (!bounded) return UNBOUNDED;
vectorconst& es = m_graph.get_all_edges();
TRACE("network_flow",
{
edge const& e_in = es[m_enter_id];
edge const& e_out = es[m_leave_id];
node src_in = e_in.get_source();
node tgt_in = e_in.get_target();
node src_out = e_out.get_source();
node tgt_out = e_out.get_target();
numeral c1 = m_potentials[src_in] - m_potentials[tgt_in] - m_graph.get_weight(m_enter_id);
numeral c2 = m_potentials[src_out] - m_potentials[tgt_out] - m_graph.get_weight(m_leave_id);
tout << "new base: y_" << src_in << "_" << tgt_in << " cost: " << c1 << " delta: " << *m_delta << "\n";
tout << "old base: y_" << src_out << "_" << tgt_out << " cost: " << c2 << "\n";
}
);
update_flows();
if (m_enter_id != m_leave_id) {
SASSERT(edge_in_tree(m_leave_id));
SASSERT(!edge_in_tree(m_enter_id));
m_states[m_enter_id] = BASIS;
m_states[m_leave_id] = LOWER;
update_spanning_tree();
update_potentials();
TRACE("network_flow",
tout << "Spanning tree:\n";
display_spanning_tree(tout);
tout << "Cost: " << get_cost() << "\n";
display_primal(tout);
);
SASSERT(check_well_formed());
}
}
TRACE("network_flow",
tout << "Spanning tree:\n";
display_spanning_tree(tout);
tout << "Cost: " << get_cost() << "\n";
display_primal(tout);
);
if (is_infeasible()) return INFEASIBLE;
TRACE("network_flow", tout << "Found optimal solution.\n";);
SASSERT(check_optimal());
return OPTIMAL;
}
template
bool network_flow::is_infeasible() {
// Flows of artificial arcs should be zero
unsigned num_nodes = m_graph.get_num_nodes();
unsigned num_edges = m_graph.get_num_edges();
SASSERT(m_flows.size() == num_edges);
for (unsigned i = 1; i < num_nodes; ++i) {
if (m_flows[num_edges - i].is_pos()) return true;
}
return false;
}
// Get the optimal solution
template
typename network_flow::numeral network_flow::get_optimal_solution(vector & result, bool is_dual) {
numeral objective_value = get_cost();
result.reset();
if (is_dual) {
result.append(m_potentials);
}
else {
result.append(m_flows);
}
return objective_value;
}
template
typename network_flow::numeral network_flow::get_cost() const {
numeral objective_value = numeral::zero();
unsigned num_edges = m_graph.get_num_edges();
for (unsigned i = 0; i < num_edges; ++i) {
if (edge_in_tree(i)) {
objective_value += m_flows[i].get_rational() * m_graph.get_weight(i);
}
}
return objective_value;
}
template
bool network_flow::edge_in_tree(edge_id id) const {
return m_states[id] == BASIS;
}
template
bool network_flow::check_well_formed() {
SASSERT(m_tree->check_well_formed());
SASSERT(!m_delta || !(*m_delta).is_neg());
// m_flows are zero on non-basic edges
for (unsigned i = 0; i < m_flows.size(); ++i) {
SASSERT(!m_flows[i].is_neg());
SASSERT(edge_in_tree(i) || m_flows[i].is_zero());
}
unsigned num_edges = m_graph.get_num_edges();
for (unsigned i = 0; i < num_edges; ++i) {
if (edge_in_tree(i)) {
dl_var src = m_graph.get_source(i);
dl_var tgt = m_graph.get_target(i);
numeral weight = m_graph.get_weight(i);
SASSERT(m_potentials[src] - m_potentials[tgt] == weight);
}
}
return true;
}
template
bool network_flow::check_optimal() {
numeral total_cost = get_cost();
unsigned num_edges = m_graph.get_num_edges();
for (unsigned i = 0; i < num_edges; ++i) {
dl_var src = m_graph.get_source(i);
dl_var tgt = m_graph.get_target(i);
numeral weight = m_graph.get_weight(i);
SASSERT(m_potentials[src] - m_potentials[tgt] <= weight);
}
// m_flows are zero on non-basic edges
for (unsigned i = 0; i < m_flows.size(); ++i) {
SASSERT(edge_in_tree(i) || m_flows[i].is_zero());
}
numeral total_balance = numeral::zero();
for (unsigned i = 0; i < m_potentials.size(); ++i) {
total_balance += m_balances[i] * m_potentials[i];
}
TRACE("network_flow", tout << "Total balance: " << total_balance << ", total cost: " << total_cost << std::endl;);
return total_cost == total_balance;
}
// display methods
template
void network_flow::display_primal(std::ofstream & os) {
vector const & es = m_graph.get_all_edges();
for (unsigned i = 0; i < m_graph.get_num_edges(); ++i) {
edge const & e = es[i];
os << "(declare-fun y_" << e.get_source() << "_" << e.get_target() << " () Real)" << std::endl;
};
for (unsigned i = 0; i < m_graph.get_num_edges(); ++i) {
edge const & e = es[i];
os << "(assert (>= y_" << e.get_source() << "_" << e.get_target() << " 0))" << std::endl;
};
for (unsigned i = 0; i < m_graph.get_num_nodes(); ++i) {
bool initialized = false;
for (unsigned j = 0; j < m_graph.get_num_edges(); ++j) {
edge const & e = es[j];
if (e.get_target() == i || e.get_source() == i) {
if (!initialized) {
os << "(assert (= (+";
}
initialized = true;
if (e.get_target() == i) {
os << " y_" << e.get_source() << "_" << e.get_target();
}
else {
os << " (- y_" << e.get_source() << "_" << e.get_target() << ")";
}
}
}
if(initialized) {
os << " " << m_balances[i] << ") 0))" << std::endl;
}
}
os << "(minimize (+";
for (unsigned i = 0; i < m_graph.get_num_edges(); ++i) {
edge const & e = es[i];
os << " (* " << e.get_weight() << " y_" << e.get_source() << "_" << e.get_target() << ")";
};
os << "))" << std::endl;
os << "(optimize)" << std::endl;
if (!m_flows.empty()) {
for (unsigned i = 0; i < m_graph.get_num_edges(); ++i) {
edge const & e = es[i];
os << "; y_" << e.get_source() << "_" << e.get_target() << " = " << m_flows[i] << "\n";
}
}
}
template
void network_flow::display_dual(std::ofstream & os) {
for (unsigned i = 0; i < m_graph.get_num_nodes(); ++i) {
os << "(declare-fun v" << i << " () Real)" << std::endl;
}
vector const & es = m_graph.get_all_edges();
for (unsigned i = 0; i < m_graph.get_num_edges(); ++i) {
edge const & e = es[i];
os << "(assert (<= (- v" << e.get_source() << " v" << e.get_target() << ") " << e.get_weight() << "))" << std::endl;
};
os << "(assert (= v0 0))" << std::endl;
os << "(maximize (+";
for (unsigned i = 0; i < m_balances.size(); ++i) {
os << " (* " << m_balances[i] << " v" << i << ")";
};
os << "))" << std::endl;
os << "(optimize)" << std::endl;
}
template
void network_flow::display_spanning_tree(std::ofstream & os) {
++m_step;;
std::string prefix = "T";
prefix.append(std::to_string(m_step));
prefix.append("_");
unsigned root = m_graph.get_num_nodes() - 1;
for (unsigned i = 0; i < root; ++i) {
os << prefix << i << "[shape=circle,label=\"" << prefix << i << " [";
os << m_potentials[i] << "/" << m_balances[i] << "]\"];\n";
}
os << prefix << root << "[shape=doublecircle,label=\"" << prefix << root << " [";
os << m_potentials[root] << "/" << m_balances[root] << "]\"];\n";
unsigned num_edges = m_graph.get_num_edges();
for (unsigned i = 0; i < num_edges; ++i) {
os << prefix << m_graph.get_source(i) << " -> " << prefix << m_graph.get_target(i);
if (edge_in_tree(i)) {
os << "[color=red,penwidth=3.0,label=\"" << m_flows[i] << "/" << m_graph.get_weight(i) << "\"];\n";
}
else {
os << "[label=\"" << m_flows[i] << "/" << m_graph.get_weight(i) << "\"];\n";
}
}
os << std::endl;
}
}