
#include "coloquinte/detailed.hxx"
#include "coloquinte/circuit_helper.hxx"

#include "coloquinte/optimization_subproblems.hxx"
#include "coloquinte/union_find.hxx"
#include "coloquinte/piecewise_linear.hxx"

#include <cassert>

#include <iostream>

namespace coloquinte{
namespace dp{

namespace{

struct minmax{
    int_t min, max;
    minmax(){}
    minmax(int_t f, int_t s) : min(f), max(s){}
    void merge(minmax const o){
        min = std::min(min, o.min);
        max = std::max(max, o.max);
    }
    void merge(int_t const o){
        merge(minmax(o, o));
    }
};

struct order_gettr{
    index_t cell_ind, seq_order;
    bool operator<(order_gettr const o) const{ return cell_ind < o.cell_ind; }
    bool operator<(index_t const o) const{ return cell_ind < o; }
    order_gettr(index_t c, index_t i) : cell_ind(c), seq_order(i) {}
};

std::vector<order_gettr> get_sorted_ordered_cells(std::vector<index_t> const & cells){
    std::vector<order_gettr> ret;
    for(index_t i=0; i<cells.size(); ++i){
        ret.push_back(order_gettr(cells[i],i));
    }
    std::sort(ret.begin(), ret.end());
    return ret;
}

std::vector<index_t> get_unique_nets(netlist const & circuit, std::vector<index_t> const & cells){
    std::vector<index_t> involved_nets;
    for(index_t c : cells){
        for(netlist::pin_t p : circuit.get_cell(c)){
            involved_nets.push_back(p.net_ind);
        }
    }
    // Uniquify the nets
    std::sort(involved_nets.begin(), involved_nets.end());
    involved_nets.resize(std::distance(involved_nets.begin(), std::unique(involved_nets.begin(), involved_nets.end())));
    return involved_nets;
}

struct Hnet_group{
    struct Hpin{
        index_t cell_index; // Not indexes in the circuit!!! Rather in the internal algorithm
        minmax offset;
        bool operator<(Hpin const o) const{ return cell_index < o.cell_index; }
    };
    struct Hnet{
        bool has_ext_pins;
        minmax ext_pins;
        int_t weight;

        Hnet(){
            has_ext_pins = false;
            ext_pins = minmax(std::numeric_limits<int_t>::max(), 0);
            weight = 1;
        }
    };

    std::vector<index_t> net_limits;
    std::vector<Hnet> nets;

    std::vector<Hpin> pins;
    std::vector<int_t> cell_widths;

    Hnet_group(){
        net_limits.push_back(0);
    }

    void add_net(std::vector<pin_1D> const added_pins, int_t weight){
        Hnet cur_net;
        cur_net.weight = weight;
        std::vector<Hpin> new_pins;
        for(auto const p : added_pins){
            if(p.movable){
                Hpin new_pin;
                new_pin.cell_index = p.cell_ind;
                new_pin.offset = minmax(p.offs, p.offs);
                new_pins.push_back(new_pin);
            }
            else{
                cur_net.has_ext_pins = true;

                cur_net.ext_pins.merge(p.pos);
            }
        }
        std::sort(new_pins.begin(), new_pins.end());

        if(not new_pins.empty()){ // Possible when generating from a Steiner topology
            // Uniquify just in case there are several pins on the net on a single cell
            index_t j=0;
            auto prev_pin = new_pins[0];
            for(auto it = new_pins.begin()+1; it != new_pins.end(); ++it){
                if(it->cell_index == prev_pin.cell_index){
                    prev_pin.offset.merge(it->offset);
                }
                else{
                    new_pins[j] = prev_pin;
                    ++j;
                    prev_pin = *it;
                }
            }
            new_pins[j]=prev_pin;
            new_pins.resize(j+1);
            nets.push_back(cur_net);
            net_limits.push_back(net_limits.back() + new_pins.size());
            pins.insert(pins.end(), new_pins.begin(), new_pins.end());
        }
    }

    std::int64_t get_cost(std::vector<int_t> const & pos) const{
        std::int64_t cost=0;
        for(index_t n=0; n<nets.size(); ++n){
            auto cur_net = nets[n];

            minmax mm(std::numeric_limits<int_t>::max(), std::numeric_limits<int_t>::min());
            if(cur_net.has_ext_pins){
                mm = cur_net.ext_pins;
            }

            assert(net_limits[n+1] > net_limits[n]);
            for(index_t p=net_limits[n]; p<net_limits[n+1]; ++p){
                int_t cur_pos = pos[pins[p].cell_index];
                mm.merge( minmax(cur_pos + pins[p].offset.min, cur_pos + pins[p].offset.max) );
            }
            cost += static_cast<std::int64_t>(cur_net.weight) * (mm.max - mm.min);
        }
        return cost;
    }

    std::int64_t get_cost(std::vector<int_t> const & pos, std::vector<int> const & flip) const{
        std::int64_t cost=0;
        for(index_t n=0; n<nets.size(); ++n){
            auto cur_net = nets[n];

            minmax mm(std::numeric_limits<int_t>::max(), std::numeric_limits<int_t>::min());
            if(cur_net.has_ext_pins){
                mm = cur_net.ext_pins;
            }

            assert(net_limits[n+1] > net_limits[n]);
            for(index_t p=net_limits[n]; p<net_limits[n+1]; ++p){
                int_t cur_pos = pos[pins[p].cell_index];
                bool flipped  = flip[pins[p].cell_index];
                int_t wdth    = cell_widths[pins[p].cell_index];
                mm.merge( flipped ? 
                    minmax(cur_pos + wdth - pins[p].offset.max, cur_pos + wdth - pins[p].offset.min)
                  : minmax(cur_pos + pins[p].offset.min, cur_pos + pins[p].offset.max)
                );
            }
            cost += static_cast<std::int64_t>(cur_net.weight) * (mm.max - mm.min);
        }
        return cost;
    }

};

Hnet_group get_B2B_netgroup(netlist const & circuit, detailed_placement const & pl, std::vector<index_t> const & cells){

    std::vector<order_gettr> cells_in_row = get_sorted_ordered_cells(cells);
    std::vector<index_t> involved_nets = get_unique_nets(circuit, cells);

    Hnet_group ret;
    for(index_t c : cells)
        ret.cell_widths.push_back(circuit.get_cell(c).size.x);

    for(index_t n : involved_nets){
        std::vector<pin_1D> cur_pins = get_pins_1D(circuit, pl.plt_, n).x;
        for(pin_1D & p : cur_pins){
            auto it = std::lower_bound(cells_in_row.begin(), cells_in_row.end(), p.cell_ind);
            if(it != cells_in_row.end() and it->cell_ind == p.cell_ind){
                p.cell_ind = it->seq_order;
            }
            else{ // Found a pin which remains fixed for this round
                p.movable = false;
            }
        }
        ret.add_net(cur_pins, circuit.get_net(n).weight);
    }

    return ret;
}

Hnet_group get_RSMT_netgroup(netlist const & circuit, detailed_placement const & pl, std::vector<index_t> const & cells){

    std::vector<order_gettr> cells_in_row = get_sorted_ordered_cells(cells);
    std::vector<index_t> involved_nets = get_unique_nets(circuit, cells);

    Hnet_group ret;
    for(index_t c : cells)
        ret.cell_widths.push_back(circuit.get_cell(c).size.x);

    for(index_t n : involved_nets){
        auto vpins = get_pins_2D(circuit, pl.plt_, n);
        for(auto & p : vpins){
            auto it = std::lower_bound(cells_in_row.begin(), cells_in_row.end(), p.cell_ind);
            if(it != cells_in_row.end() and it->cell_ind == p.cell_ind){
                p.cell_ind = it->seq_order;
            }
            else{
                p.movable = false;
            }
        }

        std::vector<point<int_t> > pin_locations;
        for(auto p : vpins)
            pin_locations.push_back(p.pos);
        auto const Htopo = get_RSMT_topology(pin_locations, 8).x;

        // In the horizontal topology, we transform the parts of the tree that are on the row into HPWL subnets
        // Two pins sharing an edge are in the same subnet if one of them is on the row: use union-find
        union_find UF(vpins.size());
        for(auto E : Htopo){
            if( vpins[E.first].movable or vpins[E.second].movable){
                UF.merge(E.first, E.second);
            }
        }

        std::vector<std::vector<pin_1D> > connex_comps(vpins.size());
        for(index_t i=0; i<vpins.size(); ++i){
            connex_comps[UF.find(i)].push_back(vpins[i].x());;
        }

        int_t weight = circuit.get_net(n).weight;
        for(index_t i=0; i<vpins.size(); ++i){
            if(not connex_comps[i].empty()){
                ret.add_net(connex_comps[i], weight);
            }
        }
    }

    return ret;
}

// Optimizes an ordered sequence of standard cells on the same row, returns the cost and the corresponding positions
inline std::int64_t optimize_convex_sequence(Hnet_group const & nets, std::vector<index_t> const & permutation, std::vector<int_t> & positions, std::vector<std::pair<int_t, int_t> > const & cell_ranges){
    // Get the widths of the cells in row order
    std::vector<int_t> loc_widths(permutation.size());
    std::vector<std::pair<int_t, int_t> > loc_ranges(permutation.size());
    for(index_t i=0; i<permutation.size(); ++i){
         loc_widths[permutation[i]] = nets.cell_widths[i];
         loc_ranges[permutation[i]] = cell_ranges[i];
    }

    std::vector<cell_bound> bounds;
    std::vector<int_t> right_slopes(permutation.size(), 0);
    for(index_t n=0; n<nets.nets.size(); ++n){
        index_t fst_c=std::numeric_limits<index_t>::max(), lst_c=0;
        int_t fst_pin_offs=0, lst_pin_offs=0;
        assert(nets.net_limits[n+1] > nets.net_limits[n]);
        auto cur_net = nets.nets[n];
        for(index_t p=nets.net_limits[n]; p<nets.net_limits[n+1]; ++p){
            // Permutation: index in the Hnet_group to index in the row
            index_t cur_cell = permutation[nets.pins[p].cell_index];
            if(cur_cell < fst_c){
                fst_c = cur_cell;
                fst_pin_offs = nets.pins[p].offset.min;
            }
            if(cur_cell >= lst_c){
                lst_c = cur_cell;
                lst_pin_offs = nets.pins[p].offset.max;
            }
        }
        if(cur_net.has_ext_pins){
            bounds.push_back(cell_bound(fst_c, cur_net.ext_pins.min - fst_pin_offs, cur_net.weight));
            bounds.push_back(cell_bound(lst_c, cur_net.ext_pins.max - lst_pin_offs, cur_net.weight));

            right_slopes[lst_c] += cur_net.weight;
        }
        else{
            right_slopes[lst_c] += cur_net.weight;
            right_slopes[fst_c] -= cur_net.weight;
        }
    }

    bool feasible = place_convex_single_row(loc_widths, loc_ranges, bounds, right_slopes, positions);

    auto permuted_positions = positions;
    for(index_t i=0; i<permutation.size(); ++i){
        permuted_positions[i] = positions[permutation[i]];
    }
    if(feasible)
        return nets.get_cost(permuted_positions);
    else
        return std::numeric_limits<std::int64_t>::max(); // Infeasible: return a very big cost
}

// TODO: take modified order relative to the obstacles into account
inline std::int64_t optimize_noncvx_sequence(Hnet_group const & nets, std::vector<index_t> const & permutation, std::vector<int_t> & positions, std::vector<int> & flippings, std::vector<int> const & flippability, std::vector<std::pair<int_t, int_t> > const & cell_ranges){
    // Get the widths of the cells in row order
    std::vector<int_t> loc_widths(permutation.size());
    std::vector<int> loc_flipps(permutation.size());
    std::vector<std::pair<int_t, int_t> > loc_ranges(permutation.size());
    for(index_t i=0; i<permutation.size(); ++i){
         loc_widths[permutation[i]] = nets.cell_widths[i];
         loc_ranges[permutation[i]] = cell_ranges[i];
         loc_flipps[permutation[i]] = flippability[i];
    }

    int_t min_limit = std::numeric_limits<int_t>::min();
    for(index_t i=0; i<loc_ranges.size(); ++i){
        min_limit = std::max(loc_ranges[i].first, min_limit);
        loc_ranges[i].first = min_limit;
        min_limit += loc_widths[i];
    }
    int_t max_limit = std::numeric_limits<int_t>::max();
    for(index_t i=loc_ranges.size(); i>0; --i){
        max_limit = std::min(loc_ranges[i-1].second, max_limit);
        max_limit -= loc_widths[i-1];
        loc_ranges[i-1].second = max_limit;
    }

    for(index_t i=0; i<loc_ranges.size(); ++i){
        if(loc_ranges[i].first > loc_ranges[i].second){
            return std::numeric_limits<std::int64_t>::max(); // Infeasible: return a very big cost
        }
    }

    std::vector<piecewise_linear_function> unflipped_cost_functions, flipped_cost_functions;
    for(index_t i=0; i<loc_ranges.size(); ++i){
        auto cur = piecewise_linear_function(loc_ranges[i].first, loc_ranges[i].second);
        unflipped_cost_functions.push_back(cur);
        flipped_cost_functions.push_back(cur);
    }

    for(index_t n=0; n<nets.nets.size(); ++n){
        index_t fst_c=std::numeric_limits<index_t>::max(), lst_c=0;
        int_t fst_pin_offs_mn=0, lst_pin_offs_mn=0,
              fst_pin_offs_mx=0, lst_pin_offs_mx=0;

        assert(nets.net_limits[n+1] > nets.net_limits[n]);
        auto cur_net = nets.nets[n];
        for(index_t p=nets.net_limits[n]; p<nets.net_limits[n+1]; ++p){
            // Permutation: index in the Hnet_group to index in the row
            index_t cur_cell = permutation[nets.pins[p].cell_index];
            if(cur_cell < fst_c){
                fst_c = cur_cell;
                fst_pin_offs_mn = nets.pins[p].offset.min;
                fst_pin_offs_mx = nets.pins[p].offset.max;
            }
            if(cur_cell >= lst_c){
                lst_c = cur_cell;
                lst_pin_offs_mn = nets.pins[p].offset.min;
                lst_pin_offs_mx = nets.pins[p].offset.max;
            }
        }
        if(cur_net.has_ext_pins){
            unflipped_cost_functions[fst_c].add_bislope(-cur_net.weight, 0, cur_net.ext_pins.min - fst_pin_offs_mn);
            unflipped_cost_functions[lst_c].add_bislope(0,  cur_net.weight, cur_net.ext_pins.max - lst_pin_offs_mx);
            flipped_cost_functions[fst_c].add_bislope(-cur_net.weight, 0, cur_net.ext_pins.min - loc_widths[fst_c] + fst_pin_offs_mx);
            flipped_cost_functions[lst_c].add_bislope(0,  cur_net.weight, cur_net.ext_pins.max - loc_widths[lst_c] + lst_pin_offs_mn);
        }
        else{
            unflipped_cost_functions[fst_c].add_monotone(-cur_net.weight, -fst_pin_offs_mn);
            unflipped_cost_functions[lst_c].add_monotone( cur_net.weight, -lst_pin_offs_mx);
            flipped_cost_functions[fst_c].add_monotone(-cur_net.weight, fst_pin_offs_mx - loc_widths[fst_c] );
            flipped_cost_functions[lst_c].add_monotone( cur_net.weight, lst_pin_offs_mn - loc_widths[lst_c] );
        }
    }

    std::vector<piecewise_linear_function> prev_mins, merged_costs;
    for(index_t i=0; i<loc_ranges.size(); ++i){
        merged_costs.push_back(loc_flipps[i] ?
            piecewise_linear_function::minimum(unflipped_cost_functions[i], flipped_cost_functions[i])
          : unflipped_cost_functions[i]
        );

        if(i>0){
            prev_mins.push_back(prev_mins.back().previous_min_of_sum(merged_costs.back(), loc_widths[i-1]));
        }
        else{
            prev_mins.push_back(merged_costs.back().previous_min());
        }
    }

    for(auto const M : prev_mins){
        for(index_t i=0; i+1<M.point_values.size(); ++i){
            assert(M.point_values[i].second >= M.point_values[i+1].second);
        }
    }

    flippings.resize(cell_ranges.size(), 0); positions.resize(cell_ranges.size(), 0);

    int_t pos = std::numeric_limits<int_t>::max();
    for(index_t i=loc_ranges.size(); i>0; --i){
        // Find the best position and flipping for each cell
        pos = prev_mins[i-1].last_before(std::min(pos - loc_widths[i-1], loc_ranges[i-1].second) );
        positions[i-1] = pos;

        if(loc_flipps[i-1] and flipped_cost_functions[i-1].value_at(pos) < unflipped_cost_functions[i-1].value_at(pos)){
            flippings[i-1] = 1;
        }
    }

    for(index_t i=0; i<loc_ranges.size(); ++i){
        assert(positions[i] >= loc_ranges[i].first);
        assert(positions[i] <= loc_ranges[i].second);
    }
    for(index_t i=0; i+1<loc_ranges.size(); ++i){
        assert(positions[i] + loc_widths[i] <= positions[i+1]);
    }

    auto permuted_positions = positions;
    auto permuted_flippings = flippings;
    for(index_t i=0; i<permutation.size(); ++i){
        permuted_positions[i] = positions[permutation[i]];
        permuted_flippings[i] = flippings[permutation[i]];
    }

    return nets.get_cost(permuted_positions, permuted_flippings);
}

std::vector<std::pair<int_t, int_t> > get_cell_ranges(netlist const & circuit, detailed_placement const & pl, std::vector<index_t> const & cells){
    std::vector<std::pair<int_t, int_t> > lims;

    for(index_t i=0; i+1<cells.size(); ++i){
        assert(pl.plt_.positions_[cells[i]].x + circuit.get_cell(cells[i]).size.x <= pl.plt_.positions_[cells[i+1]].x);
    }

    // Extreme limits, except macros are allowed to be beyond the limit of the placement area
    int_t lower_lim = pl.get_limit_positions(circuit, cells.front()).first;
    int_t upper_lim = pl.get_limit_positions(circuit, cells.back()).second;

    for(index_t OSRP_cell : cells){
        auto attr = circuit.get_cell(OSRP_cell).attributes;
        auto cur_lim = std::pair<int_t, int_t>(lower_lim, upper_lim);
        int_t pos = pl.plt_.positions_[OSRP_cell].x;
        if( (attr & XMovable) == 0 or pl.cell_height(OSRP_cell) != 1){
            cur_lim = std::pair<int_t, int_t>(pos, pos + circuit.get_cell(OSRP_cell).size.x);
        }
	    else{
            assert(pos >= lower_lim);
            assert(pos + circuit.get_cell(OSRP_cell).size.x <= upper_lim);
        }
        lims.push_back(cur_lim);
    }

    return lims;
}

template<bool NON_CONVEX, bool RSMT>
void OSRP_generic(netlist const & circuit, detailed_placement & pl){
    for(index_t r=0; r<pl.row_cnt(); ++r){
        // Complete optimization on a row, comprising possible obstacles

        std::vector<index_t> cells;
        std::vector<int> flippability;

        // Get the movable cells, if we can flip them, and the obstacles on the row
        for(index_t OSRP_cell = pl.get_first_cell_on_row(r); OSRP_cell != null_ind; OSRP_cell = pl.get_next_cell_on_row(OSRP_cell, r)){
            auto attr = circuit.get_cell(OSRP_cell).attributes;
            cells.push_back(OSRP_cell);
            flippability.push_back( (attr & XFlippable) != 0 ? 1 : 0);
        }

        if(not cells.empty()){
            std::vector<std::pair<int_t, int_t> > lims = get_cell_ranges(circuit, pl, cells); // Limit positions for each cell

            Hnet_group nets = RSMT ?
                get_RSMT_netgroup(circuit, pl, cells)
             :  get_B2B_netgroup(circuit, pl, cells);

            std::vector<index_t> no_permutation(cells.size());
            for(index_t i=0; i<cells.size(); ++i) no_permutation[i] = i;

            std::vector<int_t> final_positions;
            if(NON_CONVEX){
                std::vector<int> flipped;
                optimize_noncvx_sequence(nets, no_permutation, final_positions, flipped, flippability, lims);
                for(index_t i=0; i<cells.size(); ++i){
                    bool old_orient = pl.plt_.orientations_[cells[i]].x;
                    pl.plt_.orientations_[cells[i]].x = flipped[i] ? not old_orient : old_orient;
                }
            }
            else{
                optimize_convex_sequence(nets, no_permutation, final_positions, lims);
            }

            // Update the positions and orientations
            for(index_t i=0; i<cells.size(); ++i){
                pl.plt_.positions_[cells[i]].x = final_positions[i];
            }
        }
    } // Iteration on the rows

    pl.selfcheck();
}

template<bool NON_CONVEX, bool RSMT>
void swaps_row_generic(netlist const & circuit, detailed_placement & pl, index_t range){
    assert(range >= 2);

    for(index_t r=0; r<pl.row_cnt(); ++r){
        index_t OSRP_cell = pl.get_first_cell_on_row(r);

        while(OSRP_cell != null_ind){
            std::vector<index_t> cells;
            std::vector<std::pair<int_t, int_t> > lims;
            std::vector<int> flippables;

            for(index_t nbr_cells=0;
                    OSRP_cell != null_ind
                and nbr_cells < range;
                OSRP_cell = pl.get_next_cell_on_row(OSRP_cell, r), ++nbr_cells
            ){
                cells.push_back(OSRP_cell);
                flippables.push_back( (circuit.get_cell(OSRP_cell).attributes & XFlippable) != 0);
            }

            if(not cells.empty()){
                std::vector<std::pair<int_t, int_t> > lims = get_cell_ranges(circuit, pl, cells); // Limit positions for each cell

                Hnet_group nets = RSMT ?
                    get_RSMT_netgroup(circuit, pl, cells)
                 :  get_B2B_netgroup(circuit, pl, cells);

                std::int64_t best_cost = std::numeric_limits<std::int64_t>::max();
                std::vector<int_t> positions(cells.size());
                std::vector<int>   flippings(cells.size());
                std::vector<int_t> best_positions(cells.size());
                std::vector<int>   best_flippings(cells.size());

                std::vector<index_t> permutation(cells.size());
                for(index_t i=0; i<cells.size(); ++i) permutation[i] = i;
                std::vector<index_t> best_permutation;

                // Check every possible permutation of the cells
                do{
                    std::int64_t cur_cost = NON_CONVEX ?
                        optimize_noncvx_sequence(nets, permutation, positions, flippings, flippables, lims) :
                        optimize_convex_sequence(nets, permutation, positions, lims);
                    if(cur_cost <= best_cost){
                        best_cost = cur_cost;
                        best_permutation = permutation;
                        best_flippings = flippings;
                        best_positions = positions;
                    }
                }while(std::next_permutation(permutation.begin(), permutation.end()));

                std::vector<index_t> new_cell_order(cells.size());
                // Update the positions and the topology
                for(index_t i=0; i<cells.size(); ++i){
                    index_t r_ind = best_permutation[i]; // In the row from in the Hnet_group
                    new_cell_order[r_ind] = cells[i];
                    pl.plt_.positions_[cells[i]].x = best_positions[r_ind];
                    if(NON_CONVEX){
                        bool old_orient = pl.plt_.orientations_[cells[i]].x;
                        pl.plt_.orientations_[cells[i]].x = best_flippings[r_ind] ? not old_orient : old_orient;
                    }
                }

                pl.reorder_cells(cells, new_cell_order, r);
                cells = new_cell_order;

                assert(best_cost < std::numeric_limits<std::int64_t>::max());
            }
    
            if(OSRP_cell != null_ind){
                assert(cells.size() == range);
                OSRP_cell = cells[range/2];
            }
        } // Iteration on the entire row
    } // Iteration on the rows

    pl.selfcheck();
}
} // End anonymous namespace

void OSRP_convex_HPWL(netlist const & circuit, detailed_placement & pl){ OSRP_generic< false, false>(circuit, pl); }
void OSRP_convex_RSMT(netlist const & circuit, detailed_placement & pl){ OSRP_generic< false, true >(circuit, pl); }
void OSRP_noncvx_HPWL(netlist const & circuit, detailed_placement & pl){ OSRP_generic< true , false>(circuit, pl); }
void OSRP_noncvx_RSMT(netlist const & circuit, detailed_placement & pl){ OSRP_generic< true , true >(circuit, pl); }
void swaps_row_convex_HPWL(netlist const & circuit, detailed_placement & pl, index_t range){ swaps_row_generic< false, false>(circuit, pl, range); }
void swaps_row_convex_RSMT(netlist const & circuit, detailed_placement & pl, index_t range){ swaps_row_generic< false, true >(circuit, pl, range); }
void swaps_row_noncvx_HPWL(netlist const & circuit, detailed_placement & pl, index_t range){ swaps_row_generic< true , false>(circuit, pl, range); }
void swaps_row_noncvx_RSMT(netlist const & circuit, detailed_placement & pl, index_t range){ swaps_row_generic< true , true >(circuit, pl, range); }

} // namespace dp
} // namespace coloquinte


