Skip to content
Snippets Groups Projects
Commit a0a9571e authored by Jonas Seidel's avatar Jonas Seidel
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
Edge.cpp 0 → 100644
#include "Edge.h"
//get state:
Node* Edge::get_a(){
return a;
}
Node* Edge::get_b(){
return b;
}
Node* Edge::get_tikz_origin(){
#ifdef DRAW_IMPROVING_EDGES
return (get_residual_cost(get_a()) < 0 ? get_a() : get_b());
#else
return get_a();
#endif
}
double Edge::get_cost(Node* from){
if(a == from){
return cost;
}else{
return -cost;
}
}
double Edge::get_min(Node* from){
if(a == from){
return min;
}else{
return -max;
}
}
double Edge::get_max(Node* from){
if(a == from){
return max;
}else{
return -min;
}
}
double Edge::get_flow(Node* from){
if(a == from){
return flow;
}else{
return -flow;
}
}
// derived metrics:
double Edge::get_residual_capacity(Node* from){
return get_max(from)-get_flow(from);
}
double Edge::get_residual_cost(Node* from){
Node* to = (a == from) ? b : a;
return get_cost(from) - from->get_potential() + to->get_potential();
}
// set state:
void Edge::set_a(Node* a){
this->a = a;
}
void Edge::set_b(Node* b){
this->b = b;
}
void Edge::set_cost(Node* from, double cost){
if(a == from){
this->cost = cost;
}else{
this->cost = -cost;
}
}
void Edge::set_min(Node* from, double min){
if(a == from){
this->min = min;
}else{
this->max = -min;
}
}
void Edge::set_max(Node* from, double max){
if(a == from){
this->max = max;
}else{
this->min = -max;
}
}
void Edge::set_flow(Node* from, double flow){
double delta = flow - get_flow(from);
Node* to = (a == from) ? b : a;
from->set_balance_delta(delta);
to->set_balance_delta(-delta);
if(a == from){
this->flow = flow;
}else{
this->flow = -flow;
}
#ifndef SUPPRESS_PROTOCOL
std::cout << "Sending " << delta << " flow down \n " << *this << "\n resulting in : " << flow << "\n form " << *from << std::endl;
#endif
}
// refine reset
bool Edge::reset(){
if(get_residual_cost(a) < 0){
set_flow(a,get_max(a));
return true;
}else if (get_residual_cost(a) > 0){
set_flow(a,get_min(a));
return true;
}
return false;
}
Edge::Edge(Node* a, Node* b, double cost, double min, double max, double shift_scalar, std::vector<Node::Pos> line_path) : shift_scalar(shift_scalar), line_path(line_path){
set_a(a);
set_b(b);
set_cost(a,cost);
set_min(a,min);
set_max(a,max);
set_flow(a,min);
a->add_adj(this);
b->add_adj(this);
}
std::ostream& operator<<(std::ostream& os, Edge& e){
os << "Edge " << &e << "{\n";
os << "\t from Node : " << e.get_a() << "\n";
os << "\t to Node : " << e.get_b() << "\n";
os << "\t cost : " << e.get_cost(e.get_a()) << "\n";
os << "\t min : " << e.get_min(e.get_a()) << "\n";
os << "\t max : " << e.get_max(e.get_a()) << "\n";
os << "\t flow : " << e.get_flow(e.get_a()) << "\n";
os << "\t residuum from-to : " << e.get_residual_capacity(e.get_a()) << "\n";
os << "\t residuum to-from : " << e.get_residual_capacity(e.get_b()) << "\n";
os << "\t c_p from-to : " << e.get_residual_cost(e.get_a()) << "\n";
os << "\t c_p to-from : " << e.get_residual_cost(e.get_b()) << "\n";
os << "}";
return os;
}
Edge.h 0 → 100644
#ifndef EDGE_H
#define EDGE_H
#include "preprocessor_compile_control.h"
#include <vector>
#include <string>
#include <iostream>
#include <set>
#include "Node.h"
class Edge{
private:
Node* a;
Node* b;
double cost;
double min;
double max;
double flow;
public:
double shift_scalar;
std::vector<Node::Pos> line_path;
//get state:
Node* get_a();
Node* get_b();
Node* get_tikz_origin();
double get_cost(Node*);
double get_min(Node*);
double get_max(Node*);
double get_flow(Node*);
// derived metrics:
double get_residual_capacity(Node*);
double get_residual_cost(Node*);
// set state:
void set_a(Node*);
void set_b(Node*);
void set_cost(Node*, double);
void set_min(Node*, double);
void set_max(Node*, double);
void set_flow(Node*, double);
// refine reset
bool reset();
Edge(Node*, Node*, double, double, double, double, std::vector<Node::Pos> = {});
};
std::ostream& operator<<(std::ostream&, Edge&);
#endif
#include "Graph.h"
#include "latex.cpp"
std::vector<Node*>& Graph::get_nodes(){
return nodes;
}
std::vector<Edge*>& Graph::get_edges(){
return edges;
}
#ifdef CHECK_ASSERT
void Graph::check_all_epsilon_optimality(){
for(Node* n : get_nodes()){
n->check_epsilon_optimality();
}
}
#endif
Graph::Graph(std::vector<Node*> nodes,std::vector<Edge*> edges) : nodes(nodes), edges(edges){
std::filesystem::path dir("./latex");
if(!(std::filesystem::exists(dir))){
std::cout<<"Doesn't Exists"<<std::endl;
if (std::filesystem::create_directory(dir))
std::cout << "....Successfully Created !" << std::endl;
}
tikz_output_stream = std::ofstream("./latex/test.tex");
tikz_output_stream.precision(2);
tikz_picture = std::stringstream("");
tikz_picture.precision(2);
latex_preamble(tikz_output_stream);
}
Graph::~Graph(){
for(Node* n : get_nodes()){
delete n;
}
for(Edge* e : get_edges()){
delete e;
}
tikz_output_stream << "\\end{document}" << std::endl;
}
std::ostream& operator<<(std::ostream& os, Graph& g){
os << "\t Nodes: \n";
for(Node* n : g.get_nodes()){
os << *n << "\n";
}
os << "\t Edges: \n";
for(Edge* e : g.get_edges()){
os << *e << "\n";
}
os << std::endl;
return os;
}
Graph.h 0 → 100644
#ifndef GRAPH_H
#define GRAPH_H
#include "preprocessor_compile_control.h"
#include <vector>
#include <set>
#include <sstream>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <climits>
#include <cmath>
#include <cfloat>
#include "Edge.h"
#include "Node.h"
class Graph{
public:
struct Changes{
std::set<Node*> changed_nodes;
std::set<Edge*> changed_edges;
};
private:
std::ofstream tikz_output_stream;
std::stringstream tikz_picture;
std::vector<Node*> nodes;
std::vector<Edge*> edges;
public:
std::vector<Node*>& get_nodes();
std::vector<Edge*>& get_edges();
#ifdef CHECK_ASSERT
void check_all_epsilon_optimality();
#endif
void update_tikzpicture(Changes);
void generate_slide(Changes, double);
Graph(std::vector<Node*>,std::vector<Edge*>);
~Graph();
};
std::ostream& operator<<(std::ostream&, Graph&);
#endif
Makefile 0 → 100644
CXXFLAGS = -O0 -g -std=c++17 -Wall -Wextra -Wpedantic
a.out: exec.o Graph.o Edge.o Node.o
$(CXX) $(CXXFLAGS) -o $@ $^
exec.o: exec.cpp alg.o Graph.o
$(CXX) $(CXXFLAGS) -c $<
Graph.o: Graph.cpp latex.cpp Graph.h Edge.o Node.o preprocessor_compile_control.h
$(CXX) $(CXXFLAGS) -c $<
Edge.o: Edge.cpp Edge.h Node.o preprocessor_compile_control.h
$(CXX) $(CXXFLAGS) -c $<
Node.o: Node.cpp Node.h preprocessor_compile_control.h
$(CXX) $(CXXFLAGS) -c $<
clean:
rm -f Node.o Edge.o Graph.o exec.o a.out
.PHONY: clean
Node.cpp 0 → 100644
#include "Edge.h"
Node::Pos Node::Pos::operator-(Pos a){
return {this->x-a.x, this->y-a.y};
}
Node::Pos Node::Pos::operator*(double r){
return {this->x*r, this->y*r};
}
#ifdef CHECK_ASSERT
void Node::check_epsilon_optimality(){
for(Edge* e : get_adj()){
if(e->get_residual_cost(this) < -current_epsilon_optimality && e->get_residual_capacity(this) > 0){
std::cerr << "\033[1;31mbold Local epsilon optimality not non-decreasing! \033[0m\n" << std::endl;
}
}
}
#endif
void Node::add_adj(Edge* n){
adj.push_back(n);
}
Node::Pos Node::get_position(){
return position;
}
std::vector<Edge*> Node::get_adj(){
return adj;
}
double Node::get_potential(){
return potential;
}
double Node::get_balance(){
return balance;
}
void Node::set_potential(double potential
#ifdef CHECK_ASSERT
, double eps
#endif
){
#ifdef CHECK_ASSERT
if(potential <= this->potential){
std::cerr << "\033[1;31mbold Potential decreased! \033[0m\n" << std::endl;
}
#endif
#ifndef SUPPRESS_PROTOCOL
std::cout << "Changing Potential of " << *this
<< "from " << this->potential << " to " << potential << " difference " << potential - this->potential << std::endl;
#endif
this->potential = potential;
#ifdef CHECK_ASSERT
this->current_epsilon_optimality = eps;
check_epsilon_optimality();
#endif
}
void Node::set_balance(double balance){
#ifndef SUPPRESS_PROTOCOL
std::cout << "Changing Balance of " << *this
<< "from " << this->balance << " to " << balance << " difference " << balance - this->balance << std::endl;
#endif
this->balance = balance;
}
void Node::set_balance_delta(double bal_delta){
set_balance(get_balance() - bal_delta);
}
Node::Node(double x, double y) : position({x,y}), adj(std::vector<Edge*>()), potential(0), balance(0) {}
std::ostream& operator<<(std::ostream& os, Node& n){
os << "Node " << &n << "{\n";
for(Edge* ep : n.get_adj()){
os << "\t\t " << ep << "\n";
}
os << "\t potential : " << n.get_potential() << "\n";
os << "\t balance : " << n.get_balance() << "\n";
os << "}";
return os;
}
std::ostream& operator<<(std::ostream& os, Node::Pos position){
os << "(" << position.x << ", " << position.y << ")";
return os;
}
Node.h 0 → 100644
#ifndef NODE_H
#define NODE_H
#include "preprocessor_compile_control.h"
#include <vector>
#include <iostream>
#include <cfloat>
class Edge;
class Node{
public:
struct Pos{
double x;
double y;
Pos operator-(Pos);
Pos operator*(double);
};
private:
Pos position;
std::vector<Edge*> adj;
double potential;
double balance;
public:
#ifdef CHECK_ASSERT
double current_epsilon_optimality = DBL_MAX;
void check_epsilon_optimality();
#endif
void add_adj(Edge* n);
Pos get_position();
std::vector<Edge*> get_adj();
double get_potential();
double get_balance();
void set_potential(double
#ifdef CHECK_ASSERT
, double
#endif
);
void set_balance(double);
void set_balance_delta(double);
Node(double, double);
};
std::ostream& operator<<(std::ostream&, Node&);
std::ostream& operator<<(std::ostream&, Node::Pos);
#endif
Purpose
===
Generate latex slides visualizing the operations of the algorithm described in **[1]** on arbitrary graphs. This is the linear variant. For the analogous algorithm operating on graphs with convex costs see [here]() (a fork of this project).
Use
===
populate Graph like already done in main, execute. Output will be written to ./latex/; folder created in Graph::Graph. Tested on Pop!\_OS 20.04 LTS; your milage may vary.
---
* **[1]** Andrew V. Goldberg, Robert E. Tarjan _Solving Minimum-Cost Flow Problems by Successive Approximation_ Proceedings of the nineteenth annual ACM symposium on Theory of computing (1987)
alg.cpp 0 → 100644
#include "Graph.h"
void refine(Graph& g, double eps){
std::set<Edge*> changed_edges;
for(Edge* e : g.get_edges()){
if(e->reset()){
changed_edges.insert(e);
}
}
g.generate_slide({{},changed_edges},eps);
while(true){
bool terminate = true;
for(Node* n : g.get_nodes()){ // execute pushes
repeat_iteration:
if(n->get_balance() > 0){ // node is active
terminate = false;
double min_res_cost = INT_MAX;
for(Edge* e : n->get_adj()){
double curr_res_cost = e->get_residual_cost(n);
double curr_res_cap = e->get_residual_capacity(n);
if(curr_res_cap > 0){ // pushing possible
if(min_res_cost > curr_res_cost){ // updating minimum
min_res_cost = curr_res_cost;
}
if(curr_res_cost < 0){ // and pushing improves
if(n->get_balance() <= curr_res_cap){
e->set_flow(n,e->get_flow(n)+n->get_balance()); // non-sat push
g.generate_slide({{}, {e}},eps);
goto repeat_iteration; // no longer active, so the check will fail and the next iteration begins
}else{
e->set_flow(n,e->get_flow(n)+curr_res_cap); // saturating push
}
g.generate_slide({{}, {e}},eps);
}
}
}
double new_potential = min_res_cost+n->get_potential()+eps;
if(n->get_potential() != new_potential){
n->set_potential(new_potential
#ifdef CHECK_ASSERT
, eps
#endif
);
#ifdef CHECK_ASSERT
g.check_all_epsilon_optimality();
#endif
g.generate_slide({{n},{}},eps);
}
goto repeat_iteration;
}
}
if(terminate){
#ifndef SUPPRESS_PROTOCOL
std::cout << g << "\n" << std::endl;
#endif
break;
}
//std::cin.ignore();
}
}
void cost_scaling(Graph& g){
g.update_tikzpicture({{},{}});
double terminate = static_cast<double>(1)/g.get_nodes().size();
double eps;
{
int max = INT_MIN;
for(Edge* e : g.get_edges()){
int tmp = std::abs(e->get_cost(e->get_a()));
if(tmp > max){
max = tmp;
}
}
eps = max;
}
while(eps > terminate){
eps = eps/2;
refine(g,eps);
}
}
exec.cpp 0 → 100644
#include "Graph.h"
#include "alg.cpp"
#include <climits>
int main(){
Graph g = Graph(std::vector<Node*>(), std::vector<Edge*>());
std::vector<Node*>& nodes = g.get_nodes();
std::vector<Edge*>& edges = g.get_edges();
{// populate Graph
nodes.push_back(new Node(0,0));
nodes.push_back(new Node(2,-1.5));
nodes.push_back(new Node(2,1.5));
nodes.push_back(new Node(4,0));
edges.push_back(new Edge(nodes[0],nodes[1],7,0,5,-3.5));
edges.push_back(new Edge(nodes[0],nodes[2],3,0,2,3.5));
edges.push_back(new Edge(nodes[1],nodes[2],1,0,1,3.5));
edges.push_back(new Edge(nodes[1],nodes[3],1,0,2,-3.5));
edges.push_back(new Edge(nodes[2],nodes[3],2,0,1,3.5));
// target to source
edges.push_back(new Edge(nodes[3],nodes[0],-7,3,4,-3.5,{{4,2.5},{-.5,2.5},{-.5,1.5/4}}));
}
#ifndef SUPPRESS_PROTOCOL
std::cout << g << std::endl;
#endif
cost_scaling(g);
#ifndef SUPPRESS_PROTOCOL
std::cout << g << std::endl;
#endif
}
latex.cpp 0 → 100644
#include <cmath>
/*
Latex creation helper functions
*/
std::string clean_output(double num){
std::stringstream tmp;
tmp << std::setprecision(3) << num;
return (std::abs(num) > INT_MAX/2) ? ((num > 0) ? "\\infty" : "-\\infty") : tmp.str();
}
std::string wrap_minipage(std::string content, char alignment, double width_textwidth, std::string preamble){
std::stringstream output;
output << "\\begin{minipage}[" << alignment << "]{" << width_textwidth << "\\textwidth}" << std::endl
<< preamble << std::endl
<< content << std::endl
<< "\\end{minipage}";
return output.str();
}
double log_argument(Node::Pos p){
p = {p.x/std::sqrt(p.x*p.x+p.y*p.y), -p.y/std::sqrt(p.x*p.x+p.y*p.y)};
double s = std::asin(p.y);
double c = std::acos(p.x);
if(s == c){
return s;
}else{
return M_PI-s;
}
}
/*
Latex creation has been aggregated in this file, functions are associated with ::Graph
*/
/*
TODO: mark admissable arcs
*/
void Graph::generate_slide(Graph::Changes changes, double eps){
{
tikz_output_stream << "\\newpage \n $\\varepsilon = " << eps << "$ \\vspace*{1.5cm} \n" << std::endl;
tikz_output_stream << wrap_minipage(tikz_picture.str(),'c', .4, "\\flushleft") << std::endl;
tikz_output_stream << wrap_minipage("\\vspace*{1.2cm}$\\rightsquigarrow$",'c',.15,"\\centering") << "\\hfill" << std::endl;
}
{
tikz_output_stream << "\\newpage \n $\\varepsilon = " << eps << "$ \\vspace*{1.5cm} \n" << std::endl;
tikz_output_stream << wrap_minipage(tikz_picture.str(),'c', .4, "\\flushleft") << std::endl;
tikz_output_stream << wrap_minipage("\\vspace*{1.2cm}$\\rightsquigarrow$",'c',.15,"\\centering") << "\\hfill" << std::endl;
tikz_picture.str("");
update_tikzpicture(changes);
tikz_output_stream << wrap_minipage(tikz_picture.str(), 'c', .4, "\\flushright") << std::endl;
}
}
void Graph::update_tikzpicture(Graph::Changes changes){
tikz_picture << "\\begin{tikzpicture}" << std::endl;
// Nodes:
for(Node* n : get_nodes()){
std::stringstream name("");
name << n;
std::string name_str = name.str().substr(name.str().size()-5);
std::stringstream styles("");
if(n->get_balance() > 0){
styles << "active, ";
}
if(changes.changed_nodes.find(n) != changes.changed_nodes.end()){
styles << "update, ";
if(n->get_balance() > 0){
styles << "update_active, ";
}
}
tikz_picture << "\\node[vertex, " << styles.str() << "] at " << n->get_position() << "(" << n << ")" << "{\\color{red}$" << clean_output(n->get_potential()) << "$ \\nodepart{lower} \\color{blue}$" << clean_output(n->get_balance()) << "$};" << std::endl;
}
// Edges:
for(Edge* e : get_edges()){
// drawn edge
std::stringstream style ("");
style << "edge, ";
if(e->get_residual_cost(e->get_tikz_origin()) == 0){
style << "-, ";
}else if(e->get_tikz_origin() == e->get_a()){
style << "->, ";
}else{
style << "<-, ";
}
if(changes.changed_edges.find(e) != changes.changed_edges.end()){
style << "edge_update, ";
}
if(e->get_residual_cost(e->get_tikz_origin()) < 0 && e->get_residual_capacity(e->get_tikz_origin()) > 0){
style << "admissable, ";
}
tikz_picture << "\\draw[" << style.str() << "] (" << e->get_a() << ")";
for(Node::Pos p : e->line_path){
tikz_picture << " -- " << p;
}
tikz_picture << " -- (" << e->get_b() << ");";
// guiding edge for node positioning
Node::Pos direction = {0,0};
Node::Pos last_pos = e->get_a()->get_position();
tikz_picture << "\\draw[positioning_edge] (" << e->get_a() << ")";
if(e->line_path.size() > 0){
for(size_t i = 0; i < e->line_path.size()/2+1; i++){
Node::Pos p = e->line_path[i];
tikz_picture << " -- " << p;
direction = (p - last_pos);
last_pos = p;
}
}else{
tikz_picture << " -- (" << e->get_b() << ")";
direction = (e->get_b()->get_position()-last_pos);
}
int precision = tikz_picture.precision();
tikz_picture.precision(10);
#ifdef PEDANTIC_LABELING
Node::Pos shift = direction*e->shift_scalar;
tikz_picture << "node[pos = .35, xshift=" << -shift.y << ", yshift=" << shift.x << ", edge_label, text width = .75cm]{"
<< "$c = " << clean_output(e->get_cost(e->get_tikz_origin())) << "$}" << std::endl;
tikz_picture << "node[pos = .2, xshift=" << -shift.y << ", yshift=" << shift.x << ", edge_label, text width = .75cm]{"
<< "$c_{p} = " << clean_output(e->get_residual_cost(e->get_tikz_origin())) << "$}" << std::endl;
tikz_picture << "node[pos = .8, xshift=" << -shift.y << ", yshift=" << shift.x << ", edge_label, text width = 1cm]{"
<< "$[" << clean_output(e->get_min(e->get_tikz_origin())) << ", " << clean_output(e->get_max(e->get_tikz_origin())) << "]$}" << std::endl;
tikz_picture << "node[pos = .65, xshift=" << -shift.y << ", yshift=" << shift.x << ", edge_label, text width = 1cm]{"
<< "$r_f = " << clean_output(e->get_residual_capacity(e->get_tikz_origin())) << "$}" << std::endl;
M_PI
tikz_picture << "node[pos = .5, midway, xshift=" << -shift.y << ", yshift=" << shift.x << ", edge_label]{"
<< "$f = " << clean_output(e->get_flow(e->get_tikz_origin())) << "$};" << std::endl;
#else
tikz_picture << "node[pos = .5, rotate=" << 180*log_argument(direction)/M_PI+180 << ", yshift=" << e->shift_scalar << ", edge_label]{"
<< "$c_{p} = " << clean_output(e->get_residual_cost(e->get_tikz_origin())) << "$}" << std::endl;
tikz_picture << "node[pos = .5, rotate=" << 180*log_argument(direction)/M_PI+180 << ", yshift=" << -e->shift_scalar << ", edge_label]{"
<< "$f\\! = \\!" << clean_output(e->get_flow(e->get_tikz_origin())) << "\\!\\in \\![" << clean_output(e->get_min(e->get_tikz_origin())) << ", " << clean_output(e->get_max(e->get_tikz_origin())) << "]$};" << std::endl;
#endif
tikz_picture.precision(precision);
}
tikz_picture << "\\end{tikzpicture}" << std::endl;
}
void latex_preamble(std::ostream& tikz_os){
tikz_os << "\\documentclass{beamer}"
<< "\n\\usepackage{lmodern}\n"
<< "\\usepackage{amssymb}\n"
<< "\\usepackage{tikz}\n"
<< "\\usetikzlibrary{calc, positioning, shapes, shapes.multipart, shadows, arrows}\n"
<< "\\setbeamersize{text margin left=5pt,text margin right=5pt} \n"
<< "\\begin{document}\n"
<< "\\tikzstyle{vertex}=[draw, circle split, draw=gray, fill=white, font=\\fontsize{5}{0}\\selectfont, inner sep = 1pt, line width = 1pt, minimum size=.75cm]\n"
<< "\\tikzstyle{active}=[draw=teal, double = white,line width = .7pt]\n"
<< "\\tikzstyle{update_active}=[]\n"
<< "\\tikzstyle{update}=[double=magenta!50,font=\\fontsize{6}{0}\\selectfont, line width = .7pt]\n"
<< "\\tikzstyle{vertex_lower_label}=[draw=white, fill = white, text=blue]\n"
<< "\\tikzstyle{positioning_edge} = [draw = none]\n"
<< "\\tikzstyle{edge} = [draw = lightgray, line width=1pt, double = white, line width = .3pt]\n"
<< "\\tikzstyle{edge_update} = [double = magenta!25,line width = .4pt]\n"
<< "\\tikzstyle{edge_label}=[font=\\fontsize{4}{0}\\selectfont, text centered]\n"
<< "\\tikzstyle{admissable}=[color = teal]\n"
<< std::endl;
}
#ifndef SUPPRESS_PROTOCOL
/*
* delete following line if a protocol should be generated
*/
#define SUPPRESS_PROTOCOL
#endif
#ifndef CHECK_ASSERT
/*
delete following line if basic assertions should not be checked (for testing)
leads to asymptotically worse runtime!
*/
//#define CHECK_ASSERT
#endif
#ifndef PEDANTIC_LABELING
/*
delete if cost and residual capacity should not be given on edges of tikzpictures
*/
//#define PEDANTIC_LABELING
#endif
#ifndef DRAW_IMPROVING_EDGES
/*
delete if tikz edges should always be drawn from get_a() to get_b()
Otherwise it will be drawn s.t. the edge has non positive get_residual_cost
Influence limited to get_tikz_origin()'s return value
*/
#define DRAW_IMPROVING_EDGES
#endif
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment