https://github.com/ASchidler/coloring
Tip revision: dd03517187447fd44cfd9f03db6ab7313a21a64a authored by Andre Schidler on 14 March 2022, 12:24:58 UTC
Initial commit
Initial commit
Tip revision: dd03517
clique.h
//
// Created on 13.10.21.
//
#ifndef CPP_CLIQUE_H
#define CPP_CLIQUE_H
#include "conflict_graph.h"
#include "glucose30/core/Solver.h"
#include "sat_encode.h"
using namespace std;
namespace planarity {
class Myciel {
private:
void init(const ConflictGraph& g) {
unordered_map<node_t, dynamic_bitset<>> S;
S.reserve(clique_set.count() + 100);
dynamic_bitset<> common_nbs(g.getNumNodes());
common_nbs.set(0);
while(common_nbs.any()) {
for(auto n=component.find_first(); n != dynamic_bitset<>::npos; n = component.find_next(n)) {
// Find all nodes that share a common neighborhood with the current node (all nodes that are neighbors of all neighbors of n)
auto nnb = (g.adjacency[n] & component);
dynamic_bitset<> viable(g.getNumNodes());
viable.flip();
for (auto n2=nnb.find_first(); n2 != dynamic_bitset<>::npos; n2 = nnb.find_next(n2)) {
viable &= g.adjacency[n2];
}
assert(viable[n]);
S[n] = std::move(viable);
}
// Now share at least one node in S for each n in the current component.
common_nbs = component;
common_nbs.flip();
for(auto n=component.find_first(); n != dynamic_bitset<>::npos && common_nbs.any(); n = component.find_next(n)) {
// Add all nodes adjacent to any of the entries.
dynamic_bitset<> cnb(g.getNumNodes());
for (auto n2=S[n].find_first(); n2 != dynamic_bitset<>::npos; n2 = S[n].find_next(n2)) {
cnb |= g.adjacency[n2];
}
common_nbs &= cnb;
}
if (common_nbs.any()) {
bound += 1;
// Add w
auto new_n = common_nbs.find_first();
dynamic_bitset<> new_nodes(g.getNumNodes());
new_nodes.set(new_n);
// Add one shared node per node in the Myciel component and w
for(auto n=component.find_first(); n != dynamic_bitset<>::npos; n = component.find_next(n)) {
auto sharedNb = (g.adjacency[new_n] & S[n]);
// Check if one necessary node has already been added.
if (!((component | new_nodes) & sharedNb ).any()) {
auto add_n = sharedNb.find_first();
new_nodes.set(add_n);
}
}
component |= new_nodes;
}
}
component -= clique_set;
assert(verify(g));
}
Myciel(dynamic_bitset<>& clique_set, dynamic_bitset<>& component, node_t bound) : clique_set(clique_set), component(component), bound(bound) {
}
public:
dynamic_bitset<> clique_set;
dynamic_bitset<> component;
node_t bound = 0;
Myciel(const ConflictGraph& g, const dynamic_bitset<>& clique) : component(g.getNumNodes()), clique_set(g.getNumNodes()) {
bound = clique.count();
clique_set = clique;
component = clique;
init(g);
}
Myciel(const ConflictGraph& g, const vector<node_t>& clique) : component(g.getNumNodes()), clique_set(g.getNumNodes()) {
bound = clique.size();
for(auto n: clique) {
clique_set.set(n);
component.set(n);
}
init(g);
}
[[nodiscard]] bool verify(const ConflictGraph& g) const{
unordered_map<node_t, dynamic_bitset<>> reserved;
reserved.reserve(component.count());
auto added_nodes = component;
color_t c_bound = 0;
// Set clique colors
for(auto i=clique_set.find_first(); i != dynamic_bitset<>::npos; i = clique_set.find_next(i)) {
auto common = g.adjacency[i] & component;
for(auto n2=common.find_first(); n2 != dynamic_bitset<>::npos; n2 = common.find_next(n2)) {
auto it = reserved.find(n2);
if (it == reserved.end())
it = get<0>(reserved.emplace(n2, bound));
it->second.set(c_bound);
}
c_bound++;
}
// Add entries for nodes not adjacent to the clique
for(auto n=added_nodes.find_first(); n != dynamic_bitset<>::npos; n = added_nodes.find_next(n)) {
auto it = reserved.find(n);
if (it == reserved.end())
reserved.emplace(n, bound);
}
// Start coloring
bool changed = true;
while (changed) {
changed = false;
for(auto offset=1; offset >= 0; offset--) {
for (auto n = added_nodes.find_first(); n != dynamic_bitset<>::npos && !changed; n = added_nodes.find_next(n)) {
// First the implied colors and then the nodes that cannot be colored within the current bound
if (reserved[n].count() + offset == c_bound) {
changed = true;
// Try to find nodes with an implied color first, if none, try to find those that have no color available
reserved[n].flip();
auto cc = reserved[n].find_first();
if (cc == dynamic_bitset<>::npos)
return true;
auto common = g.adjacency[n] & added_nodes;
for (auto n2 = common.find_first();
n2 != dynamic_bitset<>::npos; n2 = common.find_next(n2)) {
reserved[n2].set(cc);
}
added_nodes.reset(n);
if (cc >= c_bound) {
c_bound = cc + 1;
}
}
}
}
}
return c_bound == bound;
}
void store(const ConflictGraph &g, const string &filename) const {
if (verify(g)) {
ofstream myfile(filename);
if (myfile.is_open()) {
myfile << bound << endl;
auto n=clique_set.find_first();
if (n != dynamic_bitset<>::npos)
myfile << n;
for(; n != dynamic_bitset<>::npos; n = clique_set.find_next(n)) {
myfile << "," << n;
}
myfile << endl;
n=component.find_first();
if (n != dynamic_bitset<>::npos)
myfile << n;
for(; n != dynamic_bitset<>::npos; n = component.find_next(n)) {
myfile << "," << n;
}
myfile << endl;
myfile.close();
}
} else {
cout << "Invalid Myciel" << endl;
}
}
/**
* Loads a clique stored in a file.
*/
static Myciel import(const string &filename, const ConflictGraph& g) {
fstream fl;
fl.open(filename, ios::in);
vector<node_t> clique;
int line_no = 0;
node_t bound = 0;
dynamic_bitset<> clique_set(g.getNumNodes());
dynamic_bitset<> component(g.getNumNodes());
if (fl.is_open()) {
string ln;
while (getline(fl, ln)) {
// Trim
ln.erase(ln.begin(), std::find_if(ln.begin(), ln.end(),
std::not1(std::ptr_fun<int, int>(std::isspace))));
if (ln.length() > 0) {
istringstream iss(ln);
string tok;
for (long i = 0; getline(iss, tok, ','); i++) {
if (line_no == 0)
bound = stoul(tok);
else if (line_no == 1)
clique_set.set(stoul(tok));
else
component.set(stoul(tok));
}
}
line_no++;
}
}
return Myciel(clique_set, component, bound);
}
};
class Clique {
public:
static bool isClique(const vector<unsigned int> &clique, const ConflictGraph &g) {
dynamic_bitset<> currentNbs(g.getNumNodes());
for (const auto n: clique) {
currentNbs.set(n);
}
for (const auto cNode: clique) {
if ((g.adjacency[cNode] & currentNbs).count() != clique.size() - 1)
return false;
}
return true;
}
static void storeClique(const vector<unsigned int> &clique, const ConflictGraph &g, const string &filename) {
if (isClique(clique, g)) {
ofstream myfile(filename);
if (myfile.is_open()) {
myfile << clique[0];
for (int i = 1; i < clique.size(); i++) {
myfile << "," << clique[i];
}
myfile << endl;
myfile.close();
}
} else {
cout << "Invalid clique" << endl;
}
}
/**
* Loads a clique stored in a file.
*/
static vector<node_t> importClique(const string &filename) {
fstream fl;
fl.open(filename, ios::in);
vector<node_t> clique;
if (fl.is_open()) {
string ln;
while (getline(fl, ln)) {
if (ln.length() > 0) {
// Trim
ln.erase(ln.begin(), std::find_if(ln.begin(), ln.end(),
std::not1(std::ptr_fun<int, int>(std::isspace))));
istringstream iss(ln);
string tok;
for (long i = 0; getline(iss, tok, ','); i++) {
node_t cc = stoul(tok);
clique.push_back(cc);
}
}
}
}
return clique;
}
/**
* Finds maximal cliques in a greedy fashion. Processes the nodes from largest degree to smallest.
*/
static void findCliqueGreedy(const ConflictGraph &g, vector<node_t> &start, node_t lb, node_t myciel_lb = 0, bool extend_myciel = false, const string &filename = "", const string &filename_myciel = "", bool useRankOrder=false) {
BucketQueue q(g.getNumNodes());
color_t best_myciel = 0;
vector<node_t> nodes;
nodes.reserve(g.getNumNodes());
vector<node_t> degeneracy;
degeneracy.reserve(g.getNumNodes());
unordered_map<node_t, node_t> ranks;
ranks.reserve(g.getNumNodes());
auto done = dynamic_bitset<>(g.getNumNodes());
for(int i=0; i < g.getNumNodes(); i++) {
auto degree = g.getDegree(i);
q.add(degree, i);
degeneracy.push_back(degree);
}
node_t c_degeneracy = 0;
while(! q.empty()) {
auto element = q.next();
nodes.emplace_back(get<1>(element));
if (get<0>(element) > c_degeneracy)
c_degeneracy = get<0>(element);
done[get<1>(element)] = true;
degeneracy[get<1>(element)] = c_degeneracy;
auto newNb = g.adjacency[get<1>(element)] - done;
for (auto nb = newNb.find_first(); nb != dynamic_bitset<>::npos; nb = newNb.find_next(nb)) {
q.change(degeneracy[nb], degeneracy[nb]-1, nb);
degeneracy[nb] -= 1;
}
}
reverse(nodes.begin(), nodes.end());
for (node_t i=0; i < nodes.size(); i++) {
ranks.emplace(nodes[i], i);
}
int c_best = static_cast<int>(start.size());
nodes.reserve(g.getNumNodes());
vector<node_t> cClique;
dynamic_bitset<> currentNbs(g.getNumNodes());
for (auto n: nodes) {
if (degeneracy[n] < start.size())
return;
currentNbs = g.adjacency[n];
cClique.push_back(n);
bool replace_first = false;
bool is_first = true;
while (currentNbs.any()) {
dynamic_bitset<>::size_type c_max_nbs = 0;
node_t c_max_rank = 0;
node_t c_max_node = INVALID_NODE;
for (auto n2 = currentNbs.find_first();
n2 != dynamic_bitset<>::npos; n2 = currentNbs.find_next(n2)) {
if (useRankOrder) {
if (c_max_node == INVALID_NODE || c_max_rank < ranks[n2]) {
c_max_node = static_cast<node_t>(n2);
c_max_rank = ranks[n2];
}
} else if ((extend_myciel && degeneracy[n2] >= start.size()) || degeneracy[n2] > start.size()){
auto currentCommon = (currentNbs & g.adjacency[n2]).count();
if (c_max_node == INVALID_NODE || replace_first || currentCommon > c_max_nbs || (currentCommon == c_max_nbs && c_max_rank < ranks[n2])) {
c_max_nbs = currentCommon;
c_max_node = static_cast<node_t>(n2);
c_max_rank = ranks[n2];
// Avoid same opening moves
replace_first = (is_first && degeneracy[n2] < degeneracy[n]);
is_first = false;
}
}
}
if (c_max_node == INVALID_NODE)
break;
currentNbs &= g.adjacency[c_max_node];
if (cClique.size() + currentNbs.count() <= c_best && !extend_myciel)
break;
cClique.push_back(c_max_node);
}
if (extend_myciel && !currentNbs.any() && !cClique.empty()) {
auto myc = Myciel(g, cClique);
if (myc.bound > best_myciel) {
best_myciel = myc.bound;
if (best_myciel > myciel_lb && !filename_myciel.empty()) {
myciel_lb = best_myciel;
myc.store(g, filename_myciel);
}
}
}
if (cClique.size() > c_best) {
c_best = static_cast<int>(cClique.size());
start.clear();
start.insert(start.begin(), cClique.begin(), cClique.end());
if (cClique.size() > lb) {
if (!filename.empty()) {
storeClique(start, g, filename);
}
}
}
cClique.clear();
}
}
static void findCliqueGreedy2(const ConflictGraph &g, vector<node_t>& start, node_t lb, node_t myciel_lb = 0, const bool extend_myciel = false, const string &filename = "", const string& filename_myciel = "", bool reducedNumber = true) {
BucketQueue q(g.getNumNodes());
vector<node_t> nodes;
nodes.reserve(g.getNumNodes());
vector<node_t> degeneracy(g.getNumNodes());
unordered_map<node_t, node_t> ranks;
ranks.reserve(g.getNumNodes());
vector<tuple<node_t, node_t>> degrees;
degrees.reserve(g.getNumNodes());
auto done = dynamic_bitset<>(g.getNumNodes());
for(node_t i=0; i < g.getNumNodes(); i++) {
auto degree = g.getDegree(i);
degrees.emplace_back(degree, i);
}
sort(degrees.rbegin(), degrees.rend());
unordered_map<node_t, node_t> degree_map;
degree_map.reserve(g.getNumNodes());
for(node_t i=0; i < g.getNumNodes(); i++) {
degree_map[get<1>(degrees[i])] = i;
}
for(node_t i=0; i < g.getNumNodes(); i++) {
q.add(get<0>(degrees[i]), i);
degeneracy[get<1>(degrees[i])] = get<0>(degrees[i]);
}
node_t c_degeneracy = 0;
while(! q.empty()) {
auto element = q.next();
auto n = get<1>(degrees[get<1>(element)]);
nodes.emplace_back(n);
if (get<0>(element) > c_degeneracy)
c_degeneracy = get<0>(element);
done[n] = true;
degeneracy[n] = c_degeneracy;
auto newNb = g.adjacency[n] - done;
for (auto nb = newNb.find_first(); nb != dynamic_bitset<>::npos; nb = newNb.find_next(nb)) {
q.change(degeneracy[nb], degeneracy[nb]-1, degree_map[nb]);
degeneracy[nb] -= 1;
}
}
reverse(nodes.begin(), nodes.end());
vector<dynamic_bitset<>> cliques;
cliques.reserve(g.getNumNodes());
for (auto n: nodes) {
bool found = false;
for(auto& cc: cliques) {
if (cc.is_subset_of(g.adjacency[n])) {
cc.set(n);
found = reducedNumber;
}
}
if (! found) {
cliques.emplace_back(g.getNumNodes());
cliques.back().set(n);
}
}
node_t maxClique = 0;
node_t maxMyciel = 0;
for(auto& c: cliques) {
auto cnt = c.count();
if (extend_myciel && cnt > maxClique * 4 / 3) {
auto m = Myciel(g, c);
if (m.bound > maxMyciel) {
maxMyciel = m.bound;
if (maxMyciel > myciel_lb && !filename_myciel.empty()) {
m.store(g, filename_myciel);
myciel_lb = maxMyciel;
}
}
}
if (cnt > maxClique) {
maxClique = cnt;
if (cnt > lb) {
lb = cnt;
start.clear();
start.reserve(cnt);
for (auto cn=c.find_first(); cn != dynamic_bitset<>::npos; cn = c.find_next(cn))
start.push_back(cn);
if (!filename.empty()) {
storeClique(start, g, filename);
}
}
}
}
}
static void findCliqueSatPartial(const ConflictGraph &g, vector<unsigned int> &start, const string &filename = "",
const int timeout = 10) {
vector<pair<int, int>> nodes;
nodes.reserve(g.getNumNodes());
// Order by highest degree first
for (int n = 0; n < g.getNumNodes(); n++) {
nodes.emplace_back(g.adjacency[n].count(), n);
}
sort(nodes.rbegin(), nodes.rend());
// Stores the variables
vector<Glucose30::Var> vars;
vector<Glucose30::Lit> lits;
unordered_map<unsigned int, unsigned int> rNodeMap;
vector<unsigned int> nodeMap;
// Find maximum cliques in turn
for (auto &cEntry: nodes) {
if (get<0>(cEntry) < start.size())
return;
int n = get<1>(cEntry);
auto cNb = g.adjacency[n];
auto neighbors = cNb.count();
Glucose30::Solver solver;
solver.setIncrementalMode();
vars.clear();
lits.clear();
nodeMap.clear();
rNodeMap.clear();
if (vars.size() < neighbors + 1) {
vars.reserve(neighbors + 1);
lits.reserve(neighbors + 1);
nodeMap.reserve(neighbors + 1);
rNodeMap.reserve(neighbors + 1);
}
vars.push_back(solver.newVar());
nodeMap.push_back(n);
rNodeMap.emplace(n, nodeMap.size() - 1);
solver.addClause(Glucose30::mkLit(vars.back(), true));
// Reduce the number of possible vertices to a safe number, by greedily adding vertices to the clique
// until the shared neighborhood becomes manageable.
while (neighbors > 15000) {
dynamic_bitset<>::size_type c_max = 0;
unsigned int c_best = 0;
for (auto n2 = cNb.find_first(); n2 != dynamic_bitset<>::npos; n2 = cNb.find_next(n2)) {
auto new_count = (cNb & g.adjacency[n2]).count();
if (new_count > c_max) {
c_max = new_count;
c_best = n2;
}
}
assert(c_max > 0);
vars.push_back(solver.newVar());
nodeMap.push_back(c_best);
rNodeMap.emplace(c_best, nodeMap.size() - 1);
solver.addClause(Glucose30::mkLit(vars.back(), true));
cNb &= g.adjacency[c_best];
neighbors = cNb.count();
}
for (auto n2 = cNb.find_first(); n2 != dynamic_bitset<>::npos; n2 = cNb.find_next(n2)) {
// All non-edges
auto newNb = g.adjacency[n] - g.adjacency[n2];
// If the shared neighbors are lower than the current lower bound, cannot be part of better clique...
if (neighbors - newNb.count() + 2 < start.size()) {
continue;
}
vars.push_back(solver.newVar());
nodeMap.push_back(n2);
rNodeMap.emplace(n2, nodeMap.size() - 1);
lits.push_back(Glucose30::mkLit(vars.back()));
for (auto n3 = newNb.find_first();
n2 != dynamic_bitset<>::npos && n3 < n2; n3 = newNb.find_next(n3)) {
auto it = rNodeMap.find(n3);
if (it != rNodeMap.end()) {
solver.addClause(Glucose30::mkLit(vars.back()), Glucose30::mkLit(vars[it->second]));
}
}
}
auto rhs = SatEncode::createTotalizer(solver, lits, lits.size());
if (!start.empty())
solver.addClause(Glucose30::mkLit(rhs->at(vars.size() - 1 - start.size()).x / 2, true));
bool solved = true;
while (solved) {
if (timeout > 0) {
bool running = true;
solver.clearInterrupt();
auto th = boost::thread([&]() {
boost::this_thread::sleep(boost::posix_time::seconds(timeout));
solver.interrupt();
});
solved = solver.solve();
th.interrupt();
} else {
solved = solver.solve();
}
if (solved) {
start.clear();
for (int i = 0; i < vars.size(); i++) {
if (solver.model[vars[i]] == g3l_False)
start.push_back(nodeMap[i]);
}
if (!filename.empty())
storeClique(start, g, filename);
cout << "Found clique of size " << start.size() << endl;
solver.addClause(Glucose30::mkLit(rhs->at(vars.size() - 1 - start.size()).x / 2, true));
}
}
cout << "Finished node " << n << endl;
}
}
static void findCliqueSat(const ConflictGraph &g, vector<unsigned int> &start, const string &filename = "",
const int timeout = 10) {
// Stores the variables
vector<Glucose30::Var> vars;
vector<Glucose30::Lit> lits;
vars.reserve(g.getNumNodes());
lits.reserve(g.getNumNodes());
Glucose30::Solver solver;
solver.setIncrementalMode();
vars.clear();
lits.clear();
// Find maximum cliques in turn
for (node_t n=0; n < g.getNumNodes(); n++) {
vars.push_back(solver.newVar());
lits.push_back(Glucose30::mkLit(vars.back()));
}
for(node_t n=0; n < g.getNumNodes(); n++) {
auto inv = ~g.adjacency[n];
for (auto n2=inv.find_next(n); n2 != dynamic_bitset<>::npos; n2 = inv.find_next(n2)) {
solver.addClause(lits[n], lits[n2]);
}
}
auto rhs = SatEncode::createTotalizer(solver, lits, lits.size());
if (!start.empty())
solver.addClause(Glucose30::mkLit(rhs->at(vars.size() - 1 - start.size()).x / 2, true));
bool solved = true;
while (solved) {
if (timeout > 0) {
bool running = true;
auto th = boost::thread([&]() {
boost::this_thread::sleep(boost::posix_time::seconds(timeout));
solver.interrupt();
});
solver.clearInterrupt();
solved = solver.solve();
th.interrupt();
} else {
solved = solver.solve();
}
if (solved) {
start.clear();
for (int i = 0; i < vars.size(); i++) {
if (solver.model[vars[i]] == g3l_False)
start.push_back(i);
}
if (!filename.empty())
storeClique(start, g, filename);
cout << "Found clique of size " << start.size() << endl;
solver.addClause(Glucose30::mkLit(rhs->at(vars.size() - 1 - start.size()).x / 2, true));
}
}
}
};
}
#endif //CPP_CLIQUE_H