Compare commits
4 Commits
test
...
measure_ba
Author | SHA1 | Date |
---|---|---|
Richard Wong | 6c14e739ae | |
Richard Wong | be49c6cfe0 | |
Richard Wong | 8794837369 | |
Richard Wong | 446ba9ed1e |
|
@ -1 +1,2 @@
|
||||||
This branch tests the idea of using fuzzing to choose the direction of mutation
|
The objective of this branch is to measure the number of removed edges between
|
||||||
|
preserved and mutated parts of the DFS tree.
|
||||||
|
|
|
@ -33,6 +33,12 @@ enum node_type {
|
||||||
AE_VIRTUAL_ROOT = 3
|
AE_VIRTUAL_ROOT = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum back_edge_type {
|
||||||
|
RETAINED = 0,
|
||||||
|
MUTATED_REMOVE = 1,
|
||||||
|
NON_MUTATED_REMOVE = 2
|
||||||
|
};
|
||||||
|
|
||||||
class node
|
class node
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -56,12 +62,11 @@ public:
|
||||||
node* adj(int i);
|
node* adj(int i);
|
||||||
void set_adj_list(vector<node*> vec);
|
void set_adj_list(vector<node*> vec);
|
||||||
void DFS_visit(vector<node*> &dfsList, int &index);
|
void DFS_visit(vector<node*> &dfsList, int &index);
|
||||||
void guided_DFS_visit(vector<node *> &dfsList,
|
void guided_DFS_visit(vector<node*> &dfsList,
|
||||||
vector<node *> &node_list,
|
vector<node*> &node_list,
|
||||||
int &return_index,
|
int &return_index,
|
||||||
vector<int> rev_post_order,
|
vector<int> rev_post_order);
|
||||||
int prev_node);
|
void mutated_DFS_visit(vector<node*> &dfsList,
|
||||||
void mutated_DFS_visit(vector<node*> &dfsList,
|
|
||||||
vector<node*> &node_list,
|
vector<node*> &node_list,
|
||||||
int &index,
|
int &index,
|
||||||
int &traversal_index,
|
int &traversal_index,
|
||||||
|
@ -156,19 +161,22 @@ public:
|
||||||
maximal_planar_subgraph_finder();
|
maximal_planar_subgraph_finder();
|
||||||
~maximal_planar_subgraph_finder();
|
~maximal_planar_subgraph_finder();
|
||||||
int find_mps(string input_file);
|
int find_mps(string input_file);
|
||||||
int compute_removed_edge_size(string input_file, vector<int> post_order);
|
int compute_removed_edge_size(string input_file, vector<int> post_order, int mutate_point);
|
||||||
|
void print_removed_edge_stats(string input_file, vector<int> post_order, int mutate_point);
|
||||||
vector<int> generate_post_order(string input_file);
|
vector<int> generate_post_order(string input_file);
|
||||||
vector<int> generate_mutated_post_order(string input_file, vector<int> post_order);
|
|
||||||
vector<int> generate_guided_post_order(string input_file, vector<int> post_order);
|
vector<int> generate_guided_post_order(string input_file, vector<int> post_order);
|
||||||
|
vector<int> generate_mutated_post_order(string input_file, vector<int> post_order, int mutate_point);
|
||||||
|
void set_mutate_point(int mutate_point);
|
||||||
|
int get_mutate_point();
|
||||||
node* get_new_node(node_type t);
|
node* get_new_node(node_type t);
|
||||||
void read_from_gml(string input_file);
|
void read_from_gml(string input_file);
|
||||||
int output_removed_edge_size();
|
void output_print_removed_edge_stats();
|
||||||
|
int output_int_removed_edge_size();
|
||||||
vector<int> return_post_order();
|
vector<int> return_post_order();
|
||||||
void postOrderTraversal();
|
void postOrderTraversal();
|
||||||
|
void mutatedPostOrderTraversal(vector<int> post_order, int mutate_point);
|
||||||
void guidedPostOrderTraversal(vector<int> post_order);
|
void guidedPostOrderTraversal(vector<int> post_order);
|
||||||
void mutatedPostOrderTraversal(vector<int> post_order);
|
void set_post_order(vector<int> post_order);
|
||||||
// void set_post_order(vector<int> post_order);
|
|
||||||
void print_post_order();
|
|
||||||
void sort_adj_list();
|
void sort_adj_list();
|
||||||
void determine_edges();
|
void determine_edges();
|
||||||
void back_edge_traversal();
|
void back_edge_traversal();
|
||||||
|
@ -194,8 +202,9 @@ private:
|
||||||
vector<pair<node*, node*> > _edge_list; // Edges in DFS-tree. These edges must be contained in the maximal planar subgraph that we found.
|
vector<pair<node*, node*> > _edge_list; // Edges in DFS-tree. These edges must be contained in the maximal planar subgraph that we found.
|
||||||
vector<node*> _post_order_list; //The sorted version (increasing with post-order-index) of _node_list.
|
vector<node*> _post_order_list; //The sorted version (increasing with post-order-index) of _node_list.
|
||||||
vector<pair<node*, node*> > _back_edge_list; // Edges other than that in DFS-tree. (The first node's index is higher than the second's.)
|
vector<pair<node*, node*> > _back_edge_list; // Edges other than that in DFS-tree. (The first node's index is higher than the second's.)
|
||||||
vector<bool> _is_back_edge_eliminate; //Record that if the back-edge has been eliminated or not.
|
vector<back_edge_type> _is_back_edge_eliminate; //Record that if the back-edge has been eliminated or not.
|
||||||
vector<node*> _new_node_list; //Newly added nodes.
|
vector<node*> _new_node_list; //Newly added nodes.
|
||||||
|
int _mutate_point; // store the mutate_point
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -31,4 +31,4 @@ $(BIN_DIR) $(OBJ_DIR):
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -r $(BIN_DIR) $(OBJ_DIR)
|
rm -r $(OBJ_DIR)
|
||||||
|
|
|
@ -11,52 +11,60 @@
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <ogdf/fileformats/GraphIO.h>
|
#include <ogdf/fileformats/GraphIO.h>
|
||||||
#define START_TEMP 100
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
int compute_removed_edge_size(string input_file, vector<int> post_order);
|
|
||||||
|
|
||||||
|
// functions defined in mps_test.cpp
|
||||||
|
int compute_removed_edge_size(string input_file, vector<int> post_order, int mutate_point);
|
||||||
|
void print_removed_edge_stats(string input_file, vector<int> post_order, int mutate_point);
|
||||||
vector<int> generate_post_order(string input_file);
|
vector<int> generate_post_order(string input_file);
|
||||||
vector<int> generate_mutated_post_order(string input_file, vector<int> post_order);
|
|
||||||
vector<int> generate_guided_post_order(string input_file, vector<int> post_order);
|
vector<int> generate_guided_post_order(string input_file, vector<int> post_order);
|
||||||
|
vector<int> generate_mutated_post_order_at_x(string input_file, vector<int> post_order, int mutate_point);
|
||||||
|
|
||||||
|
|
||||||
vector<int> repeated_mutation(string input_file, int k_max) {
|
void vector_printer(vector<int> state) {
|
||||||
// generate first post order
|
for (int i = 0; i < state.size(); ++i) {
|
||||||
std::cout << "generate first post order" << std::endl;
|
std::cout << state[i] << ",";
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void measure_removed_edges(string input_file, int k_max, int mutate_point) {
|
||||||
|
// generate first state
|
||||||
vector<int> state_old = generate_post_order(input_file);
|
vector<int> state_old = generate_post_order(input_file);
|
||||||
vector<int> state_new;
|
vector<int> state_new;
|
||||||
int num_removed_edges;
|
|
||||||
|
|
||||||
|
int removed_old = compute_removed_edge_size(input_file, state_old, mutate_point);
|
||||||
|
int removed_new;
|
||||||
|
|
||||||
for (int k = 0; k < k_max; ++k) {
|
for (int k = 0; k < k_max; ++k) {
|
||||||
// rotate it first
|
std::cout << "first post order" << std::endl;
|
||||||
std::cout << "cycle:" << k << std::endl;
|
vector_printer(state_old);
|
||||||
std::cout << "rotate the dfs tree" << std::endl;
|
|
||||||
state_new = generate_guided_post_order(input_file, state_old);
|
// run mutation on canonical representation directly
|
||||||
// then the next traversal will rotate it back
|
state_new = generate_mutated_post_order_at_x(input_file, state_old, mutate_point);
|
||||||
std::cout << "mutate the dfs tree" << std::endl;
|
// rotate output of mutated state to canonical representation
|
||||||
state_new = generate_mutated_post_order(input_file, state_new);
|
|
||||||
// num_removed_edges = compute_removed_edge_size(input_file, state_new);
|
|
||||||
// first time will rotate the tree
|
|
||||||
std::cout << "rotate the dfs tree" << std::endl;
|
|
||||||
state_new = generate_guided_post_order(input_file, state_new);
|
state_new = generate_guided_post_order(input_file, state_new);
|
||||||
// second time will rotate back the rotated tree
|
// tree produced should be the same as tree from mutation
|
||||||
std::cout << "print the mutated tree again" << std::endl;
|
removed_new = compute_removed_edge_size(input_file, state_new, mutate_point);
|
||||||
state_new = generate_guided_post_order(input_file, state_new);
|
|
||||||
std::cout << std::endl;
|
std::cout << "mutated post order" << std::endl;
|
||||||
|
vector_printer(state_new);
|
||||||
|
|
||||||
|
|
||||||
|
// std::cout << "removed edges in old: " << removed_old << std::endl;
|
||||||
|
// std::cout << "removed edges in new: " << removed_new << std::endl;
|
||||||
|
print_removed_edge_stats(input_file, state_new, mutate_point);
|
||||||
|
|
||||||
}
|
}
|
||||||
return state_new;
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_correctness(string input_file) {
|
|
||||||
vector<int> state_old = generate_post_order(input_file);
|
|
||||||
int num_removed_edges;
|
|
||||||
num_removed_edges = compute_removed_edge_size(input_file, state_old);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int get_graph_size(string input_file) {
|
int get_graph_size(string input_file) {
|
||||||
ogdf::Graph G;
|
ogdf::Graph G;
|
||||||
|
|
||||||
|
@ -76,17 +84,10 @@ int get_graph_size(string input_file) {
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
string input_file = argv[1];
|
string input_file = argv[1];
|
||||||
int k_max = std::stoi(argv[2]);
|
int k_max = std::stoi(argv[2]);
|
||||||
|
int mutate_point = std::stoi(argv[3]);
|
||||||
|
|
||||||
// generate order here
|
// generate order here
|
||||||
vector<int> post_order = repeated_mutation(input_file, k_max);
|
measure_removed_edges(input_file, k_max, mutate_point);
|
||||||
// test_correctness(input_file);
|
|
||||||
|
|
||||||
// // print final order and number of edges
|
|
||||||
// // print post_order
|
|
||||||
// std::copy(post_order.begin(), post_order.end(), std::ostream_iterator<int>(std::cout, ","));
|
|
||||||
// std::cout << std::endl;
|
|
||||||
// int removed_edges = compute_removed_edge_size(input_file, post_order);
|
|
||||||
// std::cout << "Number of removed edges: " << removed_edges << std::endl;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,7 @@
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
|
||||||
#include "mps.h"
|
#include "mps.h"
|
||||||
#include <iterator>
|
#include <cassert>
|
||||||
|
|
||||||
// #define DEBUG
|
|
||||||
|
|
||||||
// constructor can be made empty
|
// constructor can be made empty
|
||||||
maximal_planar_subgraph_finder::maximal_planar_subgraph_finder() {}
|
maximal_planar_subgraph_finder::maximal_planar_subgraph_finder() {}
|
||||||
|
@ -22,6 +20,17 @@ maximal_planar_subgraph_finder::get_new_node(node_type t) {
|
||||||
return _new_node_list[_new_node_list.size()-1];
|
return _new_node_list[_new_node_list.size()-1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setter and getter for mutate_point
|
||||||
|
void
|
||||||
|
maximal_planar_subgraph_finder::set_mutate_point(int mutate_point) {
|
||||||
|
_mutate_point = mutate_point;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
maximal_planar_subgraph_finder::get_mutate_point() {
|
||||||
|
return _mutate_point;
|
||||||
|
}
|
||||||
|
|
||||||
vector<int>
|
vector<int>
|
||||||
maximal_planar_subgraph_finder::return_post_order() {
|
maximal_planar_subgraph_finder::return_post_order() {
|
||||||
vector<int> post_order;
|
vector<int> post_order;
|
||||||
|
@ -31,11 +40,11 @@ maximal_planar_subgraph_finder::return_post_order() {
|
||||||
return post_order;
|
return post_order;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Determine the post-order-list by a DFS-traversal.
|
//Determine the post-order-list by a DFS-traversal.
|
||||||
void
|
void
|
||||||
maximal_planar_subgraph_finder::postOrderTraversal() {
|
maximal_planar_subgraph_finder::postOrderTraversal() {
|
||||||
node::init_mark();
|
node::init_mark();
|
||||||
// always start with node 0
|
|
||||||
int postOrderID = 0;
|
int postOrderID = 0;
|
||||||
for (int i = 0; i < _node_list.size(); ++i) {
|
for (int i = 0; i < _node_list.size(); ++i) {
|
||||||
if (!_node_list[i]->is_marked()) {
|
if (!_node_list[i]->is_marked()) {
|
||||||
|
@ -45,9 +54,7 @@ maximal_planar_subgraph_finder::postOrderTraversal() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Determine the post-order-list by a DFS-traversal.
|
//Determine the post-order-list by a DFS-traversal.
|
||||||
// take in a post-order argument then traces the graph in the same order
|
|
||||||
// return is by reference via _post_order_list
|
|
||||||
void
|
void
|
||||||
maximal_planar_subgraph_finder::guidedPostOrderTraversal(vector<int> post_order) {
|
maximal_planar_subgraph_finder::guidedPostOrderTraversal(vector<int> post_order) {
|
||||||
node::init_mark();
|
node::init_mark();
|
||||||
|
@ -61,33 +68,31 @@ maximal_planar_subgraph_finder::guidedPostOrderTraversal(vector<int> post_order)
|
||||||
int end_condition = _node_list.size();
|
int end_condition = _node_list.size();
|
||||||
int start = rev_post_order[0];
|
int start = rev_post_order[0];
|
||||||
int i = start;
|
int i = start;
|
||||||
|
|
||||||
int prev_node = INT_MAX;
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (((start > 0) && (i == (start - 1))) || ((start == 0 ) && (i == end_condition - 1)))
|
if (((start > 0) && (i == (start - 1))) || ((start == 0 ) && (i == end_condition - 1)))
|
||||||
{
|
{
|
||||||
if (!_node_list[i]->is_marked())
|
if (!_node_list[i]->is_marked())
|
||||||
{
|
{
|
||||||
_node_list[i]->guided_DFS_visit(_post_order_list, _node_list, postOrderID, rev_post_order, prev_node);
|
_node_list[i]->guided_DFS_visit(_post_order_list, _node_list, postOrderID, rev_post_order);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// std::cout << _node_list[i]->node_id() << ", " << !_node_list[i]->is_marked() << std::endl;
|
||||||
if (!_node_list[i]->is_marked())
|
if (!_node_list[i]->is_marked())
|
||||||
{
|
{
|
||||||
_node_list[i]->guided_DFS_visit(_post_order_list, _node_list, postOrderID, rev_post_order, prev_node);
|
_node_list[i]->guided_DFS_visit(_post_order_list, _node_list, postOrderID, rev_post_order);
|
||||||
}
|
}
|
||||||
i = (i + 1) % end_condition;
|
i = (i + 1) % end_condition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Determine the post-order-list by a DFS-traversal.
|
//Determine the post-order-list by a DFS-traversal.
|
||||||
// take in a post-order argument then traces the graph in the same order
|
|
||||||
// return is by reference via _post_order_list
|
|
||||||
void
|
void
|
||||||
maximal_planar_subgraph_finder::mutatedPostOrderTraversal(vector<int> post_order) {
|
maximal_planar_subgraph_finder::mutatedPostOrderTraversal(vector<int> post_order, int mutate_point) {
|
||||||
node::init_mark();
|
node::init_mark();
|
||||||
|
|
||||||
|
// reverse post_order because reversed post_order is the traversal of the DFS tree from the starting node
|
||||||
vector<int> rev_post_order;
|
vector<int> rev_post_order;
|
||||||
for (int i = post_order.size() - 1; i >= 0; --i) {
|
for (int i = post_order.size() - 1; i >= 0; --i) {
|
||||||
rev_post_order.push_back(post_order[i]);
|
rev_post_order.push_back(post_order[i]);
|
||||||
|
@ -96,30 +101,16 @@ maximal_planar_subgraph_finder::mutatedPostOrderTraversal(vector<int> post_order
|
||||||
int traversal_index = 0;
|
int traversal_index = 0;
|
||||||
|
|
||||||
// introduce random selection
|
// introduce random selection
|
||||||
std::random_device rd;
|
|
||||||
std::mt19937 rng(rd());
|
|
||||||
// Define the range [0, n]
|
// Define the range [0, n]
|
||||||
int n = _node_list.size() - 1; // Change 'n' to your desired upper bound
|
int n = _node_list.size() - 1; // Change 'n' to your desired upper bound
|
||||||
// Create a uniform distribution for the range [0, n]
|
// assert(mutate_point < n);
|
||||||
std::uniform_int_distribution<int> distribution(0, n);
|
|
||||||
// Generate a random number between 0 and n (inclusive)
|
|
||||||
int mutate_point = distribution(rng);
|
|
||||||
// std::cout << "the mutate point: " << mutate_point << std::endl;
|
|
||||||
|
|
||||||
// set loop variables
|
// set loop variables
|
||||||
int start = rev_post_order[0];
|
int start = rev_post_order[0];
|
||||||
int i = start;
|
int i = start;
|
||||||
|
|
||||||
// if mutate_point = 0
|
|
||||||
if (mutate_point == 0) {
|
|
||||||
// generate another point
|
|
||||||
start = distribution(rng);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int end_condition = _node_list.size();
|
int end_condition = _node_list.size();
|
||||||
// this loop assumes start is not from 0
|
|
||||||
// if starting index is not 0, it just increments and loops around until it encounters the element before it
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (((start > 0) && (i == (start - 1))) || ((start == 0 ) && (i == end_condition - 1)))
|
if (((start > 0) && (i == (start - 1))) || ((start == 0 ) && (i == end_condition - 1)))
|
||||||
|
@ -130,6 +121,7 @@ maximal_planar_subgraph_finder::mutatedPostOrderTraversal(vector<int> post_order
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// std::cout << _node_list[i]->node_id() << ", " << !_node_list[i]->is_marked() << std::endl;
|
||||||
if (!_node_list[i]->is_marked())
|
if (!_node_list[i]->is_marked())
|
||||||
{
|
{
|
||||||
_node_list[i]->mutated_DFS_visit(_post_order_list, _node_list, postOrderID, traversal_index, rev_post_order, mutate_point);
|
_node_list[i]->mutated_DFS_visit(_post_order_list, _node_list, postOrderID, traversal_index, rev_post_order, mutate_point);
|
||||||
|
@ -138,25 +130,15 @@ maximal_planar_subgraph_finder::mutatedPostOrderTraversal(vector<int> post_order
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
maximal_planar_subgraph_finder::print_post_order() {
|
|
||||||
int current_index;
|
|
||||||
for (int i = 0; i < _post_order_list.size(); ++i) {
|
|
||||||
current_index = _post_order_list[i]->node_id();
|
|
||||||
std::cout << current_index << ",";
|
|
||||||
}
|
|
||||||
std::cout << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// this function is not used anywhere
|
|
||||||
//Set the post-order-list via given list
|
//Set the post-order-list via given list
|
||||||
// void
|
void
|
||||||
// maximal_planar_subgraph_finder::set_post_order(vector<int> post_order) {
|
maximal_planar_subgraph_finder::set_post_order(vector<int> post_order) {
|
||||||
// for (int i = 0; i < _node_list.size(); ++i) {
|
for (int i = 0; i < _node_list.size(); ++i) {
|
||||||
// _node_list[i]->set_post_order_index(post_order[i]);
|
_node_list[i]->set_post_order_index(post_order[i]);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
//Sort the adj-list of every node increasingly according to post-order-index.
|
//Sort the adj-list of every node increasingly according to post-order-index.
|
||||||
void
|
void
|
||||||
|
@ -187,7 +169,7 @@ maximal_planar_subgraph_finder::determine_edges() {
|
||||||
if (_post_order_list[i]->adj(j)->post_order_index() > i) break;
|
if (_post_order_list[i]->adj(j)->post_order_index() > i) break;
|
||||||
if (_post_order_list[i]->adj(j)->get_1st_label() == i) continue;
|
if (_post_order_list[i]->adj(j)->get_1st_label() == i) continue;
|
||||||
_back_edge_list.push_back(pair<node*, node*> (_post_order_list[i], _post_order_list[i]->adj(j)));
|
_back_edge_list.push_back(pair<node*, node*> (_post_order_list[i], _post_order_list[i]->adj(j)));
|
||||||
_is_back_edge_eliminate.push_back(false);
|
_is_back_edge_eliminate.push_back(RETAINED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < _post_order_list.size(); ++i) {
|
for (int i = 0; i < _post_order_list.size(); ++i) {
|
||||||
|
@ -198,13 +180,35 @@ maximal_planar_subgraph_finder::determine_edges() {
|
||||||
//The main part of the whole algorithm: Back-edge-traversal
|
//The main part of the whole algorithm: Back-edge-traversal
|
||||||
void
|
void
|
||||||
maximal_planar_subgraph_finder::back_edge_traversal() {
|
maximal_planar_subgraph_finder::back_edge_traversal() {
|
||||||
node* i_node = 0;
|
node* i_node = nullptr;
|
||||||
node* current_node = 0;
|
node* current_node = nullptr;
|
||||||
|
int dfs_mutate_point = get_mutate_point();
|
||||||
|
int node_list_last_index = _node_list.size() - 1;
|
||||||
|
// dfs_mutate_point starts counting from the DFS head
|
||||||
|
// post_order starts from the last leaf of the DFS tree
|
||||||
|
// hence we start counting from the post_order last index
|
||||||
|
int post_order_mutate_point = node_list_last_index - dfs_mutate_point;
|
||||||
|
// std::cout << "post_order_mutate_point: " << post_order_mutate_point << std::endl;
|
||||||
|
// back_edge first node is higher than the second
|
||||||
for (int i = 0; i < _back_edge_list.size(); ++i) {
|
for (int i = 0; i < _back_edge_list.size(); ++i) {
|
||||||
|
// current node is the lower
|
||||||
current_node = _back_edge_list[i].second;
|
current_node = _back_edge_list[i].second;
|
||||||
i_node = _back_edge_list[i].first;
|
i_node = _back_edge_list[i].first;
|
||||||
if (!back_edge_traversal(current_node, i_node->post_order_index())) _is_back_edge_eliminate[i] = true;
|
// back_edge_traversal returns true if it should be included
|
||||||
|
// false otherwise
|
||||||
|
if (!back_edge_traversal(current_node, i_node->post_order_index())) {
|
||||||
|
// if current_node is higher than post_order_mutate_point
|
||||||
|
// then it is the preserved section
|
||||||
|
if (current_node->post_order_index() > post_order_mutate_point) {
|
||||||
|
std::cout << "(" << current_node->node_id() << "[" << current_node->post_order_index() << "]"
|
||||||
|
<< "," << i_node->node_id() << "[" << i_node->post_order_index() << "]" << ") ";
|
||||||
|
_is_back_edge_eliminate[i] = NON_MUTATED_REMOVE;
|
||||||
|
} else {
|
||||||
|
_is_back_edge_eliminate[i] = MUTATED_REMOVE;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
//sub-function for the for-loop of back_edge_traversal().
|
//sub-function for the for-loop of back_edge_traversal().
|
||||||
|
|
|
@ -4,23 +4,29 @@
|
||||||
|
|
||||||
#include "mps.h"
|
#include "mps.h"
|
||||||
#include <ogdf/fileformats/GraphIO.h>
|
#include <ogdf/fileformats/GraphIO.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#define DEBUG
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
// Finding MPS
|
// Finding MPS
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
|
||||||
// programs to call from main:
|
// functions to call from main:
|
||||||
|
|
||||||
int find_mps(string input_file) {
|
int find_mps(string input_file) {
|
||||||
maximal_planar_subgraph_finder m;
|
maximal_planar_subgraph_finder m;
|
||||||
return m.find_mps(input_file);
|
return m.find_mps(input_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
int compute_removed_edge_size(string input_file, vector<int> post_order) {
|
int compute_removed_edge_size(string input_file, vector<int> post_order, int mutate_point) {
|
||||||
maximal_planar_subgraph_finder m;
|
maximal_planar_subgraph_finder m;
|
||||||
return m.compute_removed_edge_size(input_file, post_order);
|
return m.compute_removed_edge_size(input_file, post_order, mutate_point);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_removed_edge_stats(string input_file, vector<int> post_order, int mutate_point) {
|
||||||
|
maximal_planar_subgraph_finder m;
|
||||||
|
m.print_removed_edge_stats(input_file, post_order, mutate_point);
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<int> generate_post_order(string input_file) {
|
vector<int> generate_post_order(string input_file) {
|
||||||
|
@ -28,90 +34,78 @@ vector<int> generate_post_order(string input_file) {
|
||||||
return m.generate_post_order(input_file);
|
return m.generate_post_order(input_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<int> generate_mutated_post_order(string input_file, vector<int> post_order) {
|
|
||||||
maximal_planar_subgraph_finder m;
|
|
||||||
return m.generate_mutated_post_order(input_file, post_order);
|
|
||||||
}
|
|
||||||
|
|
||||||
vector<int> generate_guided_post_order(string input_file, vector<int> post_order) {
|
vector<int> generate_guided_post_order(string input_file, vector<int> post_order) {
|
||||||
maximal_planar_subgraph_finder m;
|
maximal_planar_subgraph_finder m;
|
||||||
return m.generate_guided_post_order(input_file, post_order);
|
return m.generate_guided_post_order(input_file, post_order);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vector<int> generate_mutated_post_order_at_x(string input_file, vector<int> post_order, int mutate_point) {
|
||||||
|
maximal_planar_subgraph_finder m;
|
||||||
|
return m.generate_mutated_post_order(input_file, post_order, mutate_point);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------
|
|
||||||
|
// immediate functions called by functions called from main
|
||||||
|
|
||||||
int maximal_planar_subgraph_finder::find_mps(string input_file) {
|
int maximal_planar_subgraph_finder::find_mps(string input_file) {
|
||||||
read_from_gml(input_file);
|
read_from_gml(input_file);
|
||||||
postOrderTraversal();
|
postOrderTraversal();
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
print_post_order();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
sort_adj_list();
|
sort_adj_list();
|
||||||
determine_edges();
|
determine_edges();
|
||||||
back_edge_traversal();
|
back_edge_traversal();
|
||||||
return output_removed_edge_size();
|
return output_int_removed_edge_size();
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<int> maximal_planar_subgraph_finder::generate_post_order(string input_file) {
|
vector<int> maximal_planar_subgraph_finder::generate_post_order(string input_file) {
|
||||||
read_from_gml(input_file);
|
read_from_gml(input_file);
|
||||||
|
set_mutate_point(INT_MAX); // essentially removed mutate_point
|
||||||
postOrderTraversal();
|
postOrderTraversal();
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
std::cout << "standard post order traversal" << std::endl;
|
|
||||||
print_post_order();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return return_post_order();
|
return return_post_order();
|
||||||
}
|
}
|
||||||
|
|
||||||
// result of this will be used as input to "compute_removed_edge_size"
|
|
||||||
vector<int> maximal_planar_subgraph_finder::generate_mutated_post_order(string input_file, vector<int> post_order) {
|
|
||||||
read_from_gml(input_file);
|
|
||||||
mutatedPostOrderTraversal(post_order);
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
std::cout << "mutated post order traversal" << std::endl;
|
|
||||||
print_post_order();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return return_post_order();
|
|
||||||
}
|
|
||||||
|
|
||||||
// result of this will be used as input to "compute_removed_edge_size"
|
|
||||||
vector<int> maximal_planar_subgraph_finder::generate_guided_post_order(string input_file, vector<int> post_order) {
|
vector<int> maximal_planar_subgraph_finder::generate_guided_post_order(string input_file, vector<int> post_order) {
|
||||||
read_from_gml(input_file);
|
read_from_gml(input_file);
|
||||||
|
set_mutate_point(INT_MAX); // essentially remove mutate point
|
||||||
guidedPostOrderTraversal(post_order);
|
guidedPostOrderTraversal(post_order);
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
std::cout << "guided post order traversal" << std::endl;
|
|
||||||
print_post_order();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return return_post_order();
|
return return_post_order();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
vector<int> maximal_planar_subgraph_finder::generate_mutated_post_order(string input_file, vector<int> post_order, int mutate_point) {
|
||||||
|
read_from_gml(input_file);
|
||||||
|
set_mutate_point(INT_MAX);
|
||||||
|
mutatedPostOrderTraversal(post_order, mutate_point);
|
||||||
|
return return_post_order();
|
||||||
|
}
|
||||||
|
|
||||||
int maximal_planar_subgraph_finder::compute_removed_edge_size(string input_file, vector<int> post_order) {
|
|
||||||
|
int maximal_planar_subgraph_finder::compute_removed_edge_size(string input_file, vector<int> post_order, int mutate_point) {
|
||||||
read_from_gml(input_file);
|
read_from_gml(input_file);
|
||||||
|
set_mutate_point(mutate_point);
|
||||||
guidedPostOrderTraversal(post_order);
|
guidedPostOrderTraversal(post_order);
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
std::cout << "guided post order traversal" << std::endl;
|
|
||||||
print_post_order();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
sort_adj_list();
|
sort_adj_list();
|
||||||
determine_edges();
|
determine_edges();
|
||||||
back_edge_traversal();
|
back_edge_traversal();
|
||||||
return output_removed_edge_size();
|
return output_int_removed_edge_size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void maximal_planar_subgraph_finder::print_removed_edge_stats(string input_file, vector<int> post_order, int mutate_point) {
|
||||||
|
read_from_gml(input_file);
|
||||||
|
set_mutate_point(mutate_point);
|
||||||
|
guidedPostOrderTraversal(post_order);
|
||||||
|
|
||||||
|
sort_adj_list();
|
||||||
|
determine_edges();
|
||||||
|
back_edge_traversal();
|
||||||
|
output_print_removed_edge_stats();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
// Imput, output
|
// Input, output
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,6 +124,14 @@ void maximal_planar_subgraph_finder::read_from_gml(string input_file) {
|
||||||
_node_list[i]->set_id(i);
|
_node_list[i]->set_id(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// vector<ogdf::edge> unique_edges;
|
||||||
|
// // we want to get unique edge only
|
||||||
|
// for (ogdf::edge e : G.edges) {
|
||||||
|
// // Check if the edge is already in the array
|
||||||
|
// if (std::find(unique_edges.begin(), unique_edges.end(), e) == unique_edges.end())
|
||||||
|
// unique_edges.push_back(e);
|
||||||
|
// }
|
||||||
|
|
||||||
// create edges
|
// create edges
|
||||||
for (ogdf::edge e : G.edges) {
|
for (ogdf::edge e : G.edges) {
|
||||||
ogdf::node source = e->source();
|
ogdf::node source = e->source();
|
||||||
|
@ -142,10 +144,47 @@ void maximal_planar_subgraph_finder::read_from_gml(string input_file) {
|
||||||
|
|
||||||
|
|
||||||
// count the number of removed edges
|
// count the number of removed edges
|
||||||
int maximal_planar_subgraph_finder::output_removed_edge_size() {
|
void maximal_planar_subgraph_finder::output_print_removed_edge_stats() {
|
||||||
|
int mutated_sum = 0;
|
||||||
|
int preserved_sum = 0;
|
||||||
|
|
||||||
|
vector<pair<node*, node*>> mutated_removed_edges;
|
||||||
|
vector<pair<node*, node*>> preserved_removed_edges;
|
||||||
|
|
||||||
|
for (int i = 0; i < _back_edge_list.size(); ++i) {
|
||||||
|
if (_is_back_edge_eliminate[i] == MUTATED_REMOVE) {
|
||||||
|
mutated_removed_edges.push_back(_back_edge_list[i]);
|
||||||
|
++mutated_sum;
|
||||||
|
}
|
||||||
|
if (_is_back_edge_eliminate[i] == NON_MUTATED_REMOVE) {
|
||||||
|
preserved_removed_edges.push_back(_back_edge_list[i]);
|
||||||
|
++preserved_sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print the edges
|
||||||
|
// std::cout << "mutated removed edges: " << std::endl;
|
||||||
|
// for (int i = 0; i < mutated_removed_edges.size(); ++i) {
|
||||||
|
// std::cout << mutated_removed_edges[i].first->node_id() << ", " <<
|
||||||
|
// mutated_removed_edges[i].second->node_id() << std::endl;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// std::cout << "preserved removed edges: " << std::endl;
|
||||||
|
// for (int i = 0; i < preserved_removed_edges.size(); ++i) {
|
||||||
|
// std::cout << preserved_removed_edges[i].first->node_id() << ", " <<
|
||||||
|
// preserved_removed_edges[i].second->node_id() << std::endl;
|
||||||
|
// }
|
||||||
|
|
||||||
|
std::cout << "<- sum of removed edges -> " << std::endl;
|
||||||
|
std::cout << "mutated portion: " << mutated_sum << std::endl;
|
||||||
|
std::cout << "preserved portion: " << preserved_sum << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int maximal_planar_subgraph_finder::output_int_removed_edge_size() {
|
||||||
int sum = 0;
|
int sum = 0;
|
||||||
for (int i = 0; i < _back_edge_list.size(); ++i) {
|
for (int i = 0; i < _back_edge_list.size(); ++i) {
|
||||||
if (_is_back_edge_eliminate[i]) ++sum;
|
if (_is_back_edge_eliminate[i] != RETAINED) ++sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
#include "mps.h"
|
#include "mps.h"
|
||||||
|
|
||||||
// #define DEBUG
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
// CONSTRUCTOR
|
// CONSTRUCTOR
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
@ -55,6 +53,8 @@ node* node::adj(int i) {return _adj_list[i];}
|
||||||
|
|
||||||
void node::set_adj_list(vector<node*> vec) {_adj_list = vec;}
|
void node::set_adj_list(vector<node*> vec) {_adj_list = vec;}
|
||||||
|
|
||||||
|
// original DFS visit implementation
|
||||||
|
// it just uses _adj_list directly as list of neighbors
|
||||||
void node::DFS_visit(vector<node*> &dfsList, int &index) {
|
void node::DFS_visit(vector<node*> &dfsList, int &index) {
|
||||||
mark();
|
mark();
|
||||||
for (int i = 0; i < _adj_list.size(); ++i) {
|
for (int i = 0; i < _adj_list.size(); ++i) {
|
||||||
|
@ -63,78 +63,19 @@ void node::DFS_visit(vector<node*> &dfsList, int &index) {
|
||||||
_adj_list[i]->DFS_visit(dfsList, index);
|
_adj_list[i]->DFS_visit(dfsList, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// head recursion: function call before returning result
|
||||||
set_post_order_index(index);
|
set_post_order_index(index);
|
||||||
dfsList.push_back(this);
|
dfsList.push_back(this);
|
||||||
++index;
|
++index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void node::guided_DFS_visit(vector<node *> &dfsList,
|
void node::guided_DFS_visit(vector<node *> &dfsList,
|
||||||
vector<node *> &node_list,
|
vector<node *> &node_list,
|
||||||
int &return_index,
|
int &return_index,
|
||||||
vector<int> rev_post_order,
|
vector<int> rev_post_order)
|
||||||
int prev_node)
|
|
||||||
{
|
{
|
||||||
|
mark();
|
||||||
|
|
||||||
mark();
|
|
||||||
|
|
||||||
// purpose of this block: create list of neighbors ordered in the order they appear in rev_post_order
|
|
||||||
// we want to select neighbors that match the rev_post_order at the specific traversal_index
|
|
||||||
|
|
||||||
// create an unordered set to efficiently check for presence of an element
|
|
||||||
std::unordered_set<int> neighbor_set;
|
|
||||||
for (int i = 0; i < _adj_list.size(); ++i) {
|
|
||||||
neighbor_set.insert(_adj_list[i]->node_id());
|
|
||||||
}
|
|
||||||
// when an element in rev_post_order is found in neighbor_set, we add that to neighbor_list
|
|
||||||
// this produces a neighbor_list that follows the order by which they occur in the rev_post_order
|
|
||||||
// it is ok if the neighbor was already visited before,
|
|
||||||
// it would've been marked and will be subsequently ignored
|
|
||||||
vector<node *> neighbor_list;
|
|
||||||
for (int i = 0; i < rev_post_order.size(); ++i) {
|
|
||||||
if (neighbor_set.find(rev_post_order[i]) != neighbor_set.end()) {
|
|
||||||
// only add if newly encountered
|
|
||||||
if (!node_list[rev_post_order[i]]->is_marked()) {
|
|
||||||
neighbor_list.push_back(node_list[rev_post_order[i]]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
std::cout << "current node:" << this->node_id() << std::endl;
|
|
||||||
std::cout << "prev node:" << prev_node << std::endl;
|
|
||||||
for (int i = 0; i < neighbor_list.size(); ++i) {
|
|
||||||
std::cout << neighbor_list[i]->node_id() << "(" << neighbor_list[i]->is_marked() << ")" << ",";
|
|
||||||
}
|
|
||||||
std::cout << std::endl;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i < neighbor_list.size(); ++i) {
|
|
||||||
if (!neighbor_list[i]->is_marked()) {
|
|
||||||
neighbor_list[i]->_parent = this;
|
|
||||||
neighbor_list[i]->guided_DFS_visit(dfsList, node_list, return_index, rev_post_order, this->node_id());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set_post_order_index(return_index);
|
|
||||||
dfsList.push_back(this);
|
|
||||||
++return_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void node::mutated_DFS_visit(vector<node*> &dfsList,
|
|
||||||
vector<node*> &node_list,
|
|
||||||
int &return_index,
|
|
||||||
int &traversal_index,
|
|
||||||
vector<int> rev_post_order,
|
|
||||||
int mutate_point)
|
|
||||||
{
|
|
||||||
|
|
||||||
// mark current node
|
|
||||||
mark();
|
|
||||||
|
|
||||||
// purpose of this block: create list of neighbors ordered in the order they appear in rev_post_order
|
// purpose of this block: create list of neighbors ordered in the order they appear in rev_post_order
|
||||||
// we want to select neighbors that match the rev_post_order at the specific traversal_index
|
// we want to select neighbors that match the rev_post_order at the specific traversal_index
|
||||||
|
|
||||||
|
@ -154,18 +95,57 @@ void node::mutated_DFS_visit(vector<node*> &dfsList,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < neighbor_list.size(); ++i) {
|
||||||
|
if (!neighbor_list[i]->is_marked()) {
|
||||||
|
neighbor_list[i]->_parent = this;
|
||||||
|
neighbor_list[i]->guided_DFS_visit(dfsList, node_list, return_index, rev_post_order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// head recursion
|
||||||
|
set_post_order_index(return_index);
|
||||||
|
dfsList.push_back(this);
|
||||||
|
++return_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void node::mutated_DFS_visit(vector<node*> &dfsList,
|
||||||
|
vector<node*> &node_list,
|
||||||
|
int &return_index,
|
||||||
|
int &traversal_index,
|
||||||
|
vector<int> rev_post_order,
|
||||||
|
int mutate_point) {
|
||||||
|
mark();
|
||||||
|
|
||||||
|
// purpose of this block: create list of neighbors ordered in the order they appear in rev_post_order
|
||||||
|
// we want to select neighbors that match the rev_post_order at the specific traversal_index
|
||||||
|
|
||||||
|
// create an unordered set to efficiently check for presence of an element
|
||||||
|
std::unordered_set<int> neighbor_set;
|
||||||
|
for (int i = 0; i < _adj_list.size(); ++i) {
|
||||||
|
neighbor_set.insert(_adj_list[i]->node_id());
|
||||||
|
}
|
||||||
|
// when an element in rev_post_order is found in neighbor_set, we add that to neighbor_list
|
||||||
|
// this produces a neighbor_list that follows the order by which they occur in the rev_post_order
|
||||||
|
// it is ok if the neighbor was already visited before,
|
||||||
|
// it would've been marked and will be subsequently ignored
|
||||||
|
vector<node *> neighbor_list;
|
||||||
|
for (int i = 0; i < rev_post_order.size(); ++i) {
|
||||||
|
if (neighbor_set.find(rev_post_order[i]) != neighbor_set.end()) {
|
||||||
|
neighbor_list.push_back(node_list[rev_post_order[i]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// since we increment the index before this line, the current index is "index - 1"
|
// introduce mutation at mutate_point
|
||||||
// if the current index matches the mutate_point, then we know this is the cycle to mutate
|
|
||||||
if (traversal_index == mutate_point) {
|
if (traversal_index == mutate_point) {
|
||||||
// Create a random number generator and seed it
|
// Create a random number generator and seed it
|
||||||
// std::cout << "mutated at index: " << index - 1<< "and at mutate point: " << mutate_point << std::endl;
|
// std::cout << "mutated at index: " << index - 1<< "and at mutate point: " << mutate_point << std::endl;
|
||||||
std::random_device rd;
|
std::random_device rd;
|
||||||
std::mt19937 rng(rd());
|
std::mt19937 rng(rd());
|
||||||
|
|
||||||
// Use std::shuffle to shuffle the elements in the vector
|
// Use std::shuffle to shuffle the elements in the vector
|
||||||
std::shuffle(neighbor_list.begin(), neighbor_list.end(), rng);
|
std::shuffle(neighbor_list.begin(), neighbor_list.end(), rng);
|
||||||
}
|
}
|
||||||
|
|
||||||
// increment traversal index after checking
|
// increment traversal index after checking
|
||||||
// next node will receive incremented index
|
// next node will receive incremented index
|
||||||
|
@ -180,11 +160,14 @@ void node::mutated_DFS_visit(vector<node*> &dfsList,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set_post_order_index(return_index);
|
// head recursion like the initial dfs visit implementation
|
||||||
|
set_post_order_index(return_index);
|
||||||
dfsList.push_back(this);
|
dfsList.push_back(this);
|
||||||
++return_index;
|
++return_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
// PARENT-CHILDREN
|
// PARENT-CHILDREN
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
|
Loading…
Reference in New Issue