///*****************************************************************//**
// * \file   SubsetRowInequalities.h
// * \brief  Finds and adds subset row inequalities
// * 
// * \author Matteo Petris
// * \date   March 2023
// *********************************************************************/
//#ifndef STABILITYINEQUALITIES_H
//#define STABILITYINEQUALITIES_H
//
//#include "Header.h"
//#include "CutGeneratorAbstract.h"
//#include "RestrictedMasterProblemIKEP.h"
//#include "PricingProblemIKEP.h"
//#include "ColumnGeneration.h"
//#include "StabilityConstraint.h"
//#include "InstanceIKEP.h"
//
///**
// * \class CliqueInequalities.
// * \brief  Finds and adds cliques inequalities to the node on the conflicting columns containing at least two subjects in common.
// */
//class StabilityInequalities : public CutGeneratorAbstract<RestrictedMasterProblemIKEP, PricingProblemIKEP, ColumnGeneration<RestrictedMasterProblemIKEP, PricingProblemIKEP>, GRBConstr> {
//
//protected:
//
//    const InstanceIKEP& instance;
//    vector<double> subsetValues;
//    double min_violation;
//    double max_violation;
//    int number;
//
//public:
//
//    /**
//     * Constructor with pricing problem.
//     *
//     * \param instance: Instance&
//     */
//    StabilityInequalities(const vector<double>& subsetValues) :
//        CutGeneratorAbstract(), instance(instance) {
//        this->subsetValues = subsetValues;
//        this->number = 0;
//    }
//
//    /**
//     * \brief Destructor.
//     *
//     */
//    virtual ~StabilityInequalities(){}
//
//    int get_number() {
//        return this->number;
//    }
//
//
//    /**
//    * \brief Evaluates if the handler (the algorithm) can handle (solve) the cutting procedure based on the code of request.
//    *
//    * \param request : string
//    * \return True if the concrete algorithm can find the cuts, false otherwise.
//    */
//    bool can_cut(const string& request) const override {
//        return request == "STABILITYINEQUALITIES" || request == "WHATEVER";
//    }
//
//
//    /**
//     * Tries to find subset row inequality cuts to add to the node. Stores the new cuts if any in the node's cut_constraints.
//     *
//     * \param node: Node<class RestrictedMasterProblem, class PricingSubProblem, class ColumnGeneration>&
//     */
//    bool cut(Node<RestrictedMasterProblemIKEP, PricingProblemIKEP, ColumnGeneration<RestrictedMasterProblemIKEP, PricingProblemIKEP>, GRBConstr>& node) override {
//
//        if (can_cut(node.get_restricted_master_problem()->get_cut_request())) {
//            ////Consider the conflict_graph induced by the solution variables (having y_k > 0)
//            ////Construct the induced conflict graph restricted to nodes with 0 < y_k < 1
//            clock_t start = clock();
//
//            //node.get_restricted_master_problem()->write_solution_value_into_text(0);
//
//            /****************************************DETECT FRACTIONAL COLUMNS AND VERTICES TO CONSIDER IN THE SSI*************************************************************************/
//            vector<int> positiveColumns;
//            vector<int> fractional_vertices;
//            unordered_set<int> fractional_vertices_set;
//            for (int k = 0; k < node.get_restricted_master_problem()->get_columns().size(); k++) {
//                double val = node.get_restricted_master_problem()->get_solution().at(k);
//                if (val > ::EPSILON) {
//                    positiveColumns.push_back(k);
//                    const ExchangeIKEP* exchange = &node.get_restricted_master_problem()->get_columns().at(k);
//                    for (int i = 1; i < exchange->getSequence().size(); i++) {
//                        int id = exchange->getSequence().at(i);
//                        if (fractional_vertices_set.find(id) == fractional_vertices_set.end()) {
//                            fractional_vertices.push_back(id);
//                            fractional_vertices_set.insert(id);
//                        }
//                    }
//                }
//            }
//
//            /*******************************************************************BUILD SSI SUBSETS*************************************************************************/
//            vector<vector<int>> subsets;
//            vector<int> subsets_pos;
//            for (int i = 0; i < fractional_vertices.size(); i++) {
//                const Vertex* v_i = &this->vertices.at(fractional_vertices.at(i));
//                for (int j = i + 1; j < fractional_vertices.size(); j++) {
//                    const Vertex* v_j = &this->vertices.at(fractional_vertices.at(j));
//                    for (int k = j + 1; k < fractional_vertices.size(); k++) {
//                        const Vertex* v_k = &this->vertices.at(fractional_vertices.at(k));
//                        vector<int> subset = { v_i->getId(),v_j->getId(),v_k->getId() };
//                        /*vector<int> subset_bit(wordsNumber, 0);
//                        subset_bit.at(v_i->getWord()) += v_i->getBitKey();
//                        subset_bit.at(v_j->getWord()) += v_j->getBitKey();
//                        subset_bit.at(v_k->getWord()) += v_k->getBitKey();*/
//                        subsets_pos.push_back(subsets.size());
//                        subsets.push_back(subset);
//                        //subsets_bit.push_back(subset_bit);
//                    }
//                }
//            }
//
//            /*******************************************************************SEPARATE SSI BY ENUMERATION*************************************************************************/
//            //unsigned seed = chrono::system_clock::now().time_since_epoch().count();
//            //shuffle(subsets_pos.begin(), subsets_pos.end(), default_random_engine(seed));
//            //vector<pair<double, int>> violated_subset_row;
//            //int violation_above = 0;
//            //clock_t start1 = clock();
//            //clock_t end1 = clock();
//            //double duration1 = (float)(end1 - start1) / CLOCKS_PER_SEC;
//            //int counter = 0;
//            //while ((duration1 < ::MAXTIMESEPARATIONROUND) && (violation_above < this->maxNumberPerIteration) && counter < subsets_pos.size()) {
//
//            //    int sId = subsets_pos.at(counter);
//            //    counter++;
//
//            //    double lhs = 0.0;
//            //    for (int k = 0; k < positiveColumns.size(); k++) {
//            //        int col = positiveColumns.at(k);
//            //        const Exchange* exchange = &node.get_restricted_master_problem()->get_columns().at(col);
//            //        //double rows_contribution = compute_subsetRow_contribution(exchange, this->subsets.at(i));
//            //        //cout << exchange->toString() << endl;
//            //        double rows_contribution = 0.0;
//            //        for (int j = 0; j < subsets.at(sId).size(); j++) {
//            //            int id = subsets.at(sId).at(j);
//            //            rows_contribution += (exchange->getVisit(id) * this->multipliers.at(j));
//            //            /*rows_contribution += static_cast<double>(exchange->getVisit(id)) *
//            //                static_cast<double>(this->multipliers.at(j));*/
//            //        }
//            //        rows_contribution /= this->gcd;
//            //        rows_contribution = floor(rows_contribution + ::EPSILON);
//
//            //        lhs += (rows_contribution * node.get_restricted_master_problem()->get_solution().at(col));
//
//            //        /*cout << col << "\t" << rows_contribution << "\t" << node.get_restricted_master_problem()->get_solution().at(col) << "\n";
//            //        if (node.get_restricted_master_problem()->get_solution().at(col) > EPSILON && rows_contribution > EPSILON) {
//            //            cout << exchange->toString() << endl;
//            //        }*/
//            //    }
//            //    /*if (lhs > ::EPSILON) {
//            //        for (int j = 0; j < this->subsets.at(i).size(); j++) {
//            //            cout << this->subsets.at(i).at(j) << " ";
//            //        }
//            //        cout << "\t" << lhs << "\n";
//            //        for (int j = 0; j < cols.size(); j++) {
//            //            cout << node.get_restricted_master_problem()->get_columns().at(cols.at(j)).toString() << "\n";
//            //            cout << "CONTR " << contrib.at(j)  << "\n";
//            //        }
//            //        cout << "\n";
//            //    }*/
//            //    double violation = lhs - rhs;
//            //    if (::greater(violation, ::VIOLATIONEPSILON)) {
//            //        violated_subset_row.push_back(make_pair(violation, sId));
//            //        violation_above++;
//            //    }
//
//
//            //    end1 = clock();
//            //    duration1 = (float)(end1 - start1) / CLOCKS_PER_SEC;
//
//            //}
//
//            //cout << "NEW SEPARATION ROUND\n";
//
//            vector<pair<double, int>> violated_subset_row;
//            int violation_above = 0;
//            for (int i = 0; i < subsets.size(); i++) {
//                double lhs = 0.0;
//                /*vector<int> cols;
//                vector<double> contrib;*/
//                /*bool check = false;
//                if (this->subsets.at(i).at(0) == 78 && this->subsets.at(i).at(1) == 100 && this->subsets.at(i).at(2) == 122) {
//                    check = true;
//                }*/
//               //cout << "SUBSET\t" << subsets.at(i).at(0) << "\t" << subsets.at(i).at(1) << "\t" << subsets.at(i).at(2) << endl;
//                for (int k = 0; k < positiveColumns.size(); k++) {
//                    int col = positiveColumns.at(k);
//                    const Exchange* exchange = &node.get_restricted_master_problem()->get_columns().at(col);
//                    //double rows_contribution = compute_subsetRow_contribution(exchange, this->subsets.at(i));
//                    //cout << exchange->toString() << endl;
//                    double rows_contribution = 0.0;
//                    for (int j = 0; j < subsets.at(i).size(); j++) {
//                        int id = subsets.at(i).at(j);
//                        rows_contribution += (exchange->getVisit(id) * this->multipliers.at(j));
//                        /*rows_contribution += static_cast<double>(exchange->getVisit(id)) *
//                            static_cast<double>(this->multipliers.at(j));*/
//                    }
//                    rows_contribution /= this->gcd;
//                    rows_contribution = floor(rows_contribution + ::EPSILON);
//
//                    lhs += (rows_contribution * node.get_restricted_master_problem()->get_solution().at(col));
//                    
//                    /*cout << col << "\t" << rows_contribution << "\t" << node.get_restricted_master_problem()->get_solution().at(col) << "\n";
//                    if (node.get_restricted_master_problem()->get_solution().at(col) > EPSILON && rows_contribution > EPSILON) {
//                        cout << exchange->toString() << endl;
//                    }*/
//                }
//                //if (lhs - rhs > ::EPSILON) {
//                //    for (int j = 0; j < subsets.at(i).size(); j++) {
//                //        cout << subsets.at(i).at(j) << " ";
//                //    }
//                //    cout << "\t" << lhs << "\n";
//                //    /*for (int j = 0; j < cols.size(); j++) {
//                //        cout << node.get_restricted_master_problem()->get_columns().at(cols.at(j)).toString() << "\n";
//                //        cout << "CONTR " << contrib.at(j)  << "\n";
//                //    }*/
//                //    cout << "\n";
//                //}
//                double violation = lhs - rhs;
//                if (::greater(violation, ::VIOLATIONEPSILON)) {
//                    violated_subset_row.push_back(make_pair(violation, i));
//                    violation_above++;
//                }
//                if (violation_above >= this->maxNumberPerIteration) {
//                    break;
//                }
//            }
//
//            sort(violated_subset_row.rbegin(), violated_subset_row.rend());
//            //Add the cut constraint to the node as well as to the RMP
//            ofstream subsetrow_violated;
//            this->number = violated_subset_row.size();
//            string file_name = ::solutionPath + ::instanceName + "_subsetrow_violated.txt";
//            subsetrow_violated.open(file_name, ios::app);
//            int insertedSSI = 0;
//            double minViolation = ::OMEGA;
//            double maxViolation = -::OMEGA;
//            int wordsNumber = this->vertices.at(1).getNgSet().size();
//            for (int i = 0; i < violated_subset_row.size(); i++) {
//                int pos = violated_subset_row.at(i).second;
//                string subsetToString;
//                for (int j = 0; j < subsets.at(pos).size(); j++) {
//                    subsetToString += "_" + to_string(subsets.at(pos).at(j));
//                    subsetrow_violated << subsets.at(pos).at(j) << " ";
//                }
//                double viol = violated_subset_row.at(i).first;
//                subsetrow_violated << "\t" << viol << "\n";
//                minViolation = min(minViolation, viol);
//                maxViolation = max(maxViolation, viol);
//
//                //build the subset encoded in bits
//                vector<int> subset_bit(wordsNumber, 0);
//                for (int j = 0; j < subsets.at(pos).size(); j++) {
//                    const Vertex* v = &this->vertices.at(subsets.at(pos).at(j));
//                    subset_bit.at(v->getWord()) += v->getBitKey();
//                }
//
//                node.add_cut_constraint(new SubsetRowConstraint<GRBConstr>(rhs, "<=", subsets.at(pos), subset_bit, this->multipliers, this->gcd, subsetToString, "SUBSETROW"));
//                node.get_cut_constraints().back()->set_birth(node.get_restricted_master_problem()->get_solving_rmp_iteration());
//                node.get_cut_constraints().back()->set_become_effective(node.get_restricted_master_problem()->get_solving_rmp_iteration());
//                node.get_restricted_master_problem()->add_cut_constraint(node.get_cut_constraints().back(), node.get_cut_constraints().size() - 1, true);
//                insertedSSI++;
//                if (insertedSSI >= this->maxNumberPerIteration) {
//                    break;
//                }
//            }
//            /*if (::isToPrint) {
//                cout << "\n\nCUTS ADDED\t\t" << setprecision(9) << minViolation << "\t" << setprecision(9) << maxViolation << endl;
//            }*/
//            subsetrow_violated << "MIN VIOL " << minViolation << " MAX VIOL " << maxViolation << "\n\n";
//            subsetrow_violated << "\n\n\n";
//            subsetrow_violated.close();
//            clock_t end = clock();
//            double duration = (float)(end - start) / CLOCKS_PER_SEC;
//            node.set_cut_generation_time_spent(node.get_cut_generation_time_spent() + duration);
//            node.set_nb_cuts_added(static_cast<int>(node.get_nb_cuts_added() + insertedSSI));
//
//            if (insertedSSI > 0) {
//                 //Add new CutFamilyInromation object and initilize it with already known info before finding violated cuts
//                information.push_back(new CutFamilyInformation());
//                information.back()->set_name_cutter("SubsetRowInequalities");
//                information.back()->set_cut_iteration(cut_iteration);
//                information.back()->set_lower_bound_before_adding_cuts(::rounderToNDecimals(node.get_dual_bound(), 10));
//                if (node.get_id() == 0) {
//                    information.back()->set_dual_bound_before_adding_cuts_at_root(::rounderToNDecimals(node.get_dual_bound(), 10));
//                }
//                information.back()->set_number_columns_before_adding_cuts(node.get_nb_columns_added());
//                //Update nb violated cuts found in information
//                information.back()->set_number_violated_cuts(insertedSSI);
//                information.back()->set_time(duration);
//                information.back()->set_minimum_violation(rounderToNDecimals(minViolation, 3));
//                information.back()->set_maximum_violation(rounderToNDecimals(maxViolation, 3));
//                node.get_restricted_master_problem()->attach(information.back());
//                return (insertedSSI > 0); //Return true if the algorithm found violated SSi
//            }
//            else
//                return CutGeneratorAbstract<RestrictedMasterProblem, PricingProblem, ColumnGeneration<RestrictedMasterProblem, PricingProblem>, GRBConstr>::cut(node);
//        }
//        else
//            return CutGeneratorAbstract<RestrictedMasterProblem, PricingProblem, ColumnGeneration<RestrictedMasterProblem, PricingProblem>, GRBConstr>::cut(node);
//
//    }
//
//    double compute_subsetRow_contribution(const Exchange* e, const vector<int>& subset) const {
//        double rows_contribution = 0.0;
//        for (int j = 0; j < subset.size(); j++) {
//            int id = subset.at(j);
//            int prod = e->getVisit(id)* this->multipliers.at(j);
//            rows_contribution += prod;
//            /*rows_contribution += static_cast<double>(exchange->getVisit(id)) *
//                static_cast<double>(this->multipliers.at(j));*/
//        }
//        rows_contribution /= static_cast<double>(this->gcd);
//        rows_contribution = floor(rows_contribution + ::EPSILON);
//        return rows_contribution;
//    }
//
//};
//
//#endif //STABILITYINEQUALITIES_H