3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-06-04 13:21:22 +00:00

Update Network Simplex implementation

This commit is contained in:
Anh-Dung Phan 2013-10-28 18:29:14 -07:00
parent d78d22deb6
commit 906bbb4eeb
4 changed files with 200 additions and 152 deletions

View file

@ -598,7 +598,7 @@ namespace datalog {
// 0 <= y - x - k - 1 // 0 <= y - x - k - 1
if (is_le(to_app(cond->get_arg(0)), x, k, y, is_int) && is_int) { if (is_le(to_app(cond->get_arg(0)), x, k, y, is_int) && is_int) {
k.neg(); k.neg();
k -= rational::one(); k -= rational::one();
std::swap(x, y); std::swap(x, y);
return true; return true;
} }

View file

@ -38,6 +38,10 @@ namespace smt {
// Solve minimum cost flow problem using Network Simplex algorithm // Solve minimum cost flow problem using Network Simplex algorithm
template<typename Ext> template<typename Ext>
class network_flow : private Ext { class network_flow : private Ext {
enum edge_state {
NON_BASIS = 0,
BASIS = 1
};
typedef dl_var node; typedef dl_var node;
typedef dl_edge<Ext> edge; typedef dl_edge<Ext> edge;
typedef dl_graph<Ext> graph; typedef dl_graph<Ext> graph;
@ -46,56 +50,66 @@ namespace smt {
graph m_graph; graph m_graph;
// Denote supply/demand b_i on node i // Denote supply/demand b_i on node i
vector<numeral> m_balances; vector<fin_numeral> m_balances;
// Duals of flows which are convenient to compute dual solutions // Duals of flows which are convenient to compute dual solutions
vector<numeral> m_potentials; vector<numeral> m_potentials;
// Keep optimal solution of the min cost flow problem // Keep optimal solution of the min cost flow problem
inf_int_rational m_objective; numeral m_objective_value;
// Costs on edges // Costs on edges
vector<fin_numeral> const & m_costs; vector<fin_numeral> m_costs;
// Basic feasible flows // Basic feasible flows
vector<numeral> m_flows; vector<numeral> m_flows;
svector<edge_state> m_states;
// An element is true if the corresponding edge points upwards (compared to the root node) // An element is true if the corresponding edge points upwards (compared to the root node)
svector<bool> m_upwards; svector<bool> m_upwards;
// Store the parent of a node in the spanning tree // Store the parent of a node i in the spanning tree
svector<node> m_pred; svector<node> m_pred;
// Store the number of edge on the path to the root // Store the number of edge on the path from node i to the root
svector<int> m_depth; svector<int> m_depth;
// Store the pointer to the next node in depth first search ordering // Store the pointer from node i to the next node in depth first search ordering
svector<node> m_thread; svector<node> m_thread;
// Reverse orders of m_thread
svector<node> m_rev_thread;
// Store a final node of the sub tree rooted at node i
svector<node> m_final;
// Number of nodes in the sub tree rooted at node i
svector<int> m_num_node;
bool m_is_optimal; edge_id m_entering_edge;
edge_id m_leaving_edge;
node m_join_node;
numeral m_delta;
public: public:
network_flow(graph & g, vector<fin_numeral> const & costs); network_flow(graph & g, vector<fin_numeral> const & balances);
// Initialize the network with a feasible spanning tree // Initialize the network with a feasible spanning tree
void initialize(); void initialize();
void compute_potentials(); void update_potentials();
void compute_flows(); void update_flows();
// If all reduced costs are non-negative, the current flow is optimal // If all reduced costs are non-negative, return false since the current spanning tree is optimal
// If not optimal, return a violating edge in the corresponding variable // Otherwise return true and update m_entering_edge
bool is_optimal(edge_id & violating_edge); bool choose_entering_edge();
// Send as much flow as possible around the cycle, the first basic edge with flow 0 will leave // Send as much flow as possible around the cycle, the first basic edge with flow 0 will leave
edge_id choose_leaving_edge(edge_id entering_edge); // Return false if the problem is unbounded
bool choose_leaving_edge();
void update_spanning_tree(edge_id entering_edge, edge_id leaving_edge); void update_spanning_tree();
bool is_unbounded();
// Compute the optimal solution // Compute the optimal solution
void get_optimal_solution(numeral & objective, vector<numeral> & flows); numeral get_optimal_solution(vector<numeral> & result, bool is_dual);
// Minimize cost flows // Minimize cost flows
// Return true if found an optimal solution, and return false if unbounded // Return true if found an optimal solution, and return false if unbounded

View file

@ -25,170 +25,212 @@ Notes:
namespace smt { namespace smt {
template<typename Ext> template<typename Ext>
network_flow<Ext>::network_flow(graph & g, vector<fin_numeral> const& costs) : network_flow<Ext>::network_flow(graph & g, vector<fin_numeral> const& balances) :
m_graph(g), m_graph(g),
m_costs(costs) { m_balances(balances) {
unsigned num_nodes = m_balances.size() + 1;
unsigned num_edges = m_graph.get_num_edges();
vector<edge> const & es = m_graph.get_all_edges();
for (unsigned i = 0; i < num_edges; ++i) {
fin_numeral cost(es[i].get_weight().get_rational());
m_costs.push_back(cost);
}
m_balances.resize(num_nodes);
for (unsigned i = 0; i < balances.size(); ++i) {
m_costs.push_back(balances[i]);
}
m_potentials.resize(num_nodes);
m_costs.resize(num_edges);
m_flows.resize(num_edges);
m_states.resize(num_edges);
m_upwards.resize(num_nodes);
m_pred.resize(num_nodes);
m_depth.resize(num_nodes);
m_thread.resize(num_nodes);
m_rev_thread.resize(num_nodes);
m_final.resize(num_nodes);
m_num_node.resize(num_nodes);
} }
template<typename Ext> template<typename Ext>
void network_flow<Ext>::initialize() { void network_flow<Ext>::initialize() {
// TODO: construct an initial spanning tree i.e. inializing m_pred, m_depth and m_thread. // Create an artificial root node to construct initial spanning tree
compute_potentials(); unsigned num_nodes = m_balances.size();
compute_flows(); unsigned num_edges = m_graph.get_num_edges();
} node root = num_nodes;
m_pred[root] = -1;
m_thread[root] = 0;
m_rev_thread[0] = root;
m_num_node[root] = num_nodes + 1;
m_final[root] = root - 1;
m_potentials[root] = numeral::zero();
template<typename Ext> fin_numeral sum_supply = fin_numeral::zero();
void network_flow<Ext>::compute_potentials() { for (unsigned i = 0; i < m_balances.size(); ++i) {
SASSERT(!m_potentials.empty()); sum_supply += m_balances[i];
SASSERT(!m_thread.empty()); }
SASSERT(m_thread.size() == m_pred.size()); m_balances[root] = -sum_supply;
m_potentials.set(0, numeral::zero()); m_states.resize(num_nodes + num_edges);
node target = m_thread[0]; m_states.fill(NON_BASIS);
while (target != 0) { // Create artificial edges and initialize the spanning tree
node source = m_pred[target]; for (unsigned i = 0; i < num_nodes; ++i) {
edge_id e_id; m_upwards[i] = m_balances[i] >= fin_numeral::zero();
if (m_graph.get_edge_id(source, target, e_id)) { m_pred[i] = root;
m_potentials.set(target, m_potentials[source] - m_graph.get_weight(e_id)); m_depth[i] = 1;
} m_thread[i] = i + 1;
if (m_graph.get_edge_id(target, source, e_id)) { m_final[i] = i;
m_potentials.set(target, m_potentials[source] + m_graph.get_weight(e_id)); m_rev_thread[i] = (i = 0) ? root : i - 1;
} m_num_node[i] = 1;
target = m_thread[target]; m_states[num_edges + i] = BASIS;
} }
} }
template<typename Ext> template<typename Ext>
void network_flow<Ext>::compute_flows() { void network_flow<Ext>::update_potentials() {
vector<numeral> balances(m_balances); node src = m_graph.get_source(m_entering_edge);
node tgt = m_graph.get_source(m_entering_edge);
// OPTIMIZE: Need a set data structure for efficiently removing elements numeral cost = m_graph.get_weight(m_entering_edge);
vector<edge_id> basics; numeral change = m_upwards[src] ? (cost - m_potentials[src] + m_potentials[tgt]) :
while (!basics.empty()) { (-cost + m_potentials[src] - m_potentials[tgt]);
// Find a leaf node of a spanning tree node last = m_thread[m_final[src]];
node target; for (node u = src; u != last; u = m_thread[u]) {
for (unsigned int i = 0; i < m_thread.size(); ++i) { m_potentials[u] += change;
if (m_depth[i] <= m_depth[m_thread[i]]) { }
target = i; }
break;
} template<typename Ext>
} void network_flow<Ext>::update_flows() {
node source = m_pred[target]; numeral val = m_state[m_entering_edge] == NON_BASIS ? numeral::zero() : m_delta;
m_flows[m_entering_edge] += val;
for (unsigned u = m_graph.get_source(m_entering_edge); u != m_join_node; u = m_pred[u]) {
edge_id e_id; edge_id e_id;
if (m_graph.get_edge_id(source, target, e_id)) { m_graph.get_edge_id(u, m_pred[u], e_id);
m_flows.set(e_id, -m_graph.get_weight(basics[target])); m_flows[e_id] += m_upwards[u] ? -val : val;
basics[source] += basics[target]; }
basics.erase(e_id); for (unsigned u = m_graph.get_target(m_entering_edge); u != m_join_node; u = m_pred[u]) {
} edge_id e_id;
else if (m_graph.get_edge_id(target, source, e_id)) { m_graph.get_edge_id(u, m_pred[u], e_id);
m_flows.set(e_id, m_graph.get_weight(basics[target])); m_flows[e_id] += m_upwards[u] ? val : -val;
basics[source] += basics[target];
basics.erase(e_id);
}
} }
} }
template<typename Ext> template<typename Ext>
bool network_flow<Ext>::is_optimal(edge_id & violating_edge) { bool network_flow<Ext>::choose_entering_edge() {
// TODO: how to get nonbasics vector? vector<edge> const & es = m_graph.get_all_edges();
vector<edge> nonbasics; for (unsigned int i = 0; i < es.size(); ++i) {
typename vector<edge>::iterator it = nonbasics.begin(); edge const & e = es[i];
typename vector<edge>::iterator end = nonbasics.end(); edge_id e_id;
bool found = false; if (e.is_enabled() && m_graph.get_edge_id(e.get_source(), e.get_target(), e_id) && m_states[e_id] == BASIS) {
for (unsigned int i = 0; i < nonbasics.size(); ++i) {
edge & e = nonbasics[i];
if (e.is_enabled()) {
node source = e.get_source(); node source = e.get_source();
node target = e.get_target(); node target = e.get_target();
numeral cost = e.get_weight() - m_potentials[source] + m_potentials[target]; numeral cost = e.get_weight() - m_potentials[source] + m_potentials[target];
// Choose the first negative-cost edge to be the violating edge // Choose the first negative-cost edge to be the violating edge
// TODO: add multiple pivoting strategies // TODO: add multiple pivoting strategies
numeral zero(0); if (cost < numeral::zero()) {
if (cost < zero) { m_entering_edge = e_id;
edge_id e_id; return true;
m_graph.get_edge_id(source, target, e_id);
violating_edge = e_id;
found = true;
break;
} }
} }
} }
return !found; return false;
} }
template<typename Ext> template<typename Ext>
edge_id network_flow<Ext>::choose_leaving_edge(edge_id entering_edge) { bool network_flow<Ext>::choose_leaving_edge() {
node source = m_graph.get_source(entering_edge); node source = m_graph.get_source(m_entering_edge);
node target = m_graph.get_target(entering_edge); node target = m_graph.get_target(m_entering_edge);
while (source != target) { node u = source, v = target;
if (m_depth[source] > m_depth[target]) while (u != v) {
source = m_pred[source]; if (m_depth[u] > m_depth[v])
else if (m_depth[source] < m_depth[target]) u = m_pred[u];
target = m_pred[target]; else if (m_depth[u] < m_depth[v])
v = m_pred[v];
else { else {
source = m_pred[source]; u = m_pred[u];
target = m_pred[target]; v = m_pred[v];
} }
} }
edge_id e_id; // Found first common ancestor of source and target
m_graph.get_edge_id(source, target, e_id); m_join_node = u;
return e_id; // FIXME: need to get truly finite value
} numeral infty = numeral(INT_MAX);
m_delta = infty;
template<typename Ext> node src, tgt;
void network_flow<Ext>::update_spanning_tree(edge_id entering_edge, edge_id leaving_edge) { // Send flows along the path from source to the ancestor
// Need special handling in case two edges are identical for (unsigned u = source; u != m_join_node; u = m_pred[u]) {
SASSERT(entering_edge != leaving_edge); edge_id e_id;
m_graph.get_edge_id(u, m_pred[u], e_id);
// Update potentials numeral d = m_upwards[u] ? m_flows[e_id] : infty;
node target = m_upwards[leaving_edge] ? m_graph.get_source(leaving_edge) : m_graph.get_target(leaving_edge); if (d < m_delta) {
numeral src_pot = m_potentials[m_graph.get_source(entering_edge)]; m_delta = d;
numeral tgt_pot = m_potentials[m_graph.get_target(entering_edge)]; src = u;
numeral weight = m_graph.get_weight(entering_edge); tgt = m_pred[u];
numeral change = m_upwards[entering_edge] ? (weight - src_pot + tgt_pot) : (-weight + src_pot - tgt_pot); }
m_potentials[target] += change;
node start = m_thread[target];
while (m_depth[start] > m_depth[target]) {
m_potentials[start] += change;
start = m_thread[start];
} }
// Send flows along the path from target to the ancestor
for (unsigned u = target; u != m_join_node; u = m_pred[u]) {
edge_id e_id;
m_graph.get_edge_id(u, m_pred[u], e_id);
numeral d = m_upwards[u] ? infty : m_flows[e_id];
if (d <= m_delta) {
m_delta = d;
src = u;
tgt = m_pred[u];
}
}
if (m_delta < infty) {
m_graph.get_edge_id(src, tgt, m_leaving_edge);
return true;
}
return false;
} }
template<typename Ext> template<typename Ext>
bool network_flow<Ext>::is_unbounded() { void network_flow<Ext>::update_spanning_tree() {
return false;
} }
// Get the optimal solution // Get the optimal solution
template<typename Ext> template<typename Ext>
void network_flow<Ext>::get_optimal_solution(numeral & objective, vector<numeral> & flows) { typename network_flow<Ext>::numeral network_flow<Ext>::get_optimal_solution(vector<numeral> & result, bool is_dual) {
SASSERT(m_is_optimal); m_objective_value = numeral::zero();
flows.reset(); for (unsigned i = 0; i < m_flows.size(); ++i) {
flows.append(m_flows); m_objective_value += m_costs[i] * m_flows[i];
objective = numeral::zero();
for (unsigned int i = 0; i < m_flows.size(); ++i) {
objective += m_costs[i] * m_flows[i];
} }
result.reset();
if (is_dual) {
result.append(m_potentials);
}
else {
result.append(m_flows);
}
return m_objective_value;
} }
// Minimize cost flows // Minimize cost flows
// Return true if found an optimal solution, and return false if unbounded // Return true if found an optimal solution, and return false if unbounded
template<typename Ext> template<typename Ext>
bool network_flow<Ext>::min_cost() { bool network_flow<Ext>::min_cost() {
SASSERT(!m_graph.get_all_edges().empty());
initialize(); initialize();
edge_id entering_edge; while (choose_entering_edge()) {
while (!is_optimal(entering_edge)) { bool bounded = choose_leaving_edge();
edge_id leaving_edge = choose_leaving_edge(entering_edge); if (!bounded) return false;
update_spanning_tree(entering_edge, leaving_edge); if (m_entering_edge != m_leaving_edge) {
if (is_unbounded()) { m_states[m_entering_edge] = BASIS;
m_is_optimal = false; m_states[m_leaving_edge] = NON_BASIS;
return m_is_optimal; update_spanning_tree();
update_potentials();
} }
} }
m_is_optimal = true; return true;
return m_is_optimal;
} }
} }

View file

@ -1016,34 +1016,26 @@ bool theory_diff_logic<Ext>::maximize(theory_var v) {
// m_graph as well. // m_graph as well.
dl_graph<GExt> g; dl_graph<GExt> g;
vector<dl_edge<GExt> > const& es = m_graph.get_all_edges(); vector<dl_edge<GExt> > const& es = m_graph.get_all_edges();
dl_var offset = m_graph.get_num_edges();
for (unsigned i = 0; i < es.size(); ++i) { for (unsigned i = 0; i < es.size(); ++i) {
dl_edge<GExt> const & e = es[i]; dl_edge<GExt> const & e = es[i];
if (e.is_enabled()) { if (e.is_enabled()) {
g.enable_edge(g.add_edge(e.get_source(), e.get_target(), e.get_weight(), e.get_explanation())); g.enable_edge(g.add_edge(e.get_source(), e.get_target(), e.get_weight(), e.get_explanation()));
g.enable_edge(g.add_edge(e.get_target() + offset, e.get_source() + offset, e.get_weight(), e.get_explanation()));
} }
} }
// Objective coefficients now become costs // Objective coefficients now become balances
vector<fin_numeral> base_costs, aux_costs; vector<fin_numeral> balances;
for (unsigned i = 0; i < objective.size(); ++i) { for (unsigned i = 0; i < objective.size(); ++i) {
fin_numeral cost(objective[i].second); fin_numeral balance(objective[i].second);
base_costs.push_back(cost); balances.push_back(balance);
aux_costs.push_back(-cost);
} }
vector<fin_numeral> costs;
costs.append(base_costs);
costs.append(aux_costs);
network_flow<GExt> net_flow(g, costs); network_flow<GExt> net_flow(g, balances);
bool is_optimal = net_flow.min_cost(); bool is_optimal = net_flow.min_cost();
if (is_optimal) { if (is_optimal) {
numeral objective_value; vector<numeral> potentials;
vector<numeral> flows; m_objective_value = net_flow.get_optimal_solution(potentials, true);
net_flow.get_optimal_solution(objective_value, flows); // TODO: return the model of the optimal solution from potential
m_objective_value = objective_value.get_rational();
// TODO: return the model of the optimal solution
} }
return is_optimal; return is_optimal;
} }