516 lines
20 KiB
C++
516 lines
20 KiB
C++
|
|
#include "coloquinte/optimization_subproblems.hxx"
|
|
|
|
#include <stdexcept>
|
|
|
|
namespace coloquinte{
|
|
|
|
std::vector<capacity_t> transport_1D(std::vector<t1D_elt> sources, std::vector<t1D_elt> sinks){
|
|
/* Description of the algorithm:
|
|
*
|
|
* For each cell, put it in its optimal region or the last region where a cell is if there is no space in it
|
|
* Push all changes in the derivative of the cost function to a priority queue; those changes occur
|
|
* when evicting the preceding cell from a region (most such changes are 0 and not considered, hence the complexity)
|
|
* when moving to a non-full region
|
|
* While the new cell overlaps with a new region, get the new slope (derivative) at this point
|
|
* and push all preceding cell until this region is freed or the slope becomes 0 (in which case the new region is now occupied)
|
|
*/
|
|
|
|
struct bound{
|
|
capacity_t pos;
|
|
int_t slope_diff;
|
|
bool operator<(bound const o) const{ return pos < o.pos; }
|
|
};
|
|
|
|
std::priority_queue<bound> bounds;
|
|
std::vector<capacity_t> constraining_pos;
|
|
std::vector<capacity_t> prev_cap(1, 0), prev_dem(1, 0);
|
|
for(auto const s : sinks){
|
|
prev_cap.push_back(s.second + prev_cap.back());
|
|
}
|
|
for(auto const s : sources){
|
|
prev_dem.push_back(s.second + prev_dem.back());
|
|
}
|
|
// The sinks have enough capacity to hold the whole demand
|
|
assert(prev_cap.back() >= prev_dem.back());
|
|
|
|
const capacity_t min_abs_pos = 0, max_abs_pos = prev_cap.back() - prev_dem.back();
|
|
assert(min_abs_pos <= max_abs_pos);
|
|
|
|
auto push_bound = [&](capacity_t p, int_t s){
|
|
assert(s >= 0);
|
|
if(p > min_abs_pos){
|
|
bound B;
|
|
B.pos = p;
|
|
B.slope_diff = s;
|
|
bounds.push(B);
|
|
}
|
|
};
|
|
|
|
// Distance to the right - distance to the left
|
|
auto get_slope = [&](index_t src, index_t boundary){
|
|
assert(boundary+1 < sinks.size());
|
|
assert(src < sources.size());
|
|
return std::abs((float)(sources[src].first - sinks[boundary+1].first)) - std::abs((float)(sources[src].first - sinks[boundary].first));
|
|
};
|
|
|
|
capacity_t cur_abs_pos = min_abs_pos;
|
|
index_t opt_r=0, next_r=0, first_free_r=0;
|
|
|
|
for(index_t i=0; i<sources.size(); ++i){
|
|
// Update the optimal region
|
|
while(opt_r+1 < sinks.size() and (sinks[opt_r].first + sinks[opt_r+1].first)/2 < sources[i].first){
|
|
++opt_r;
|
|
}
|
|
// Update the next region
|
|
index_t prev_next_r = next_r;
|
|
while(next_r < sinks.size() and sinks[next_r].first <= sources[i].first){
|
|
++next_r;
|
|
}
|
|
|
|
index_t dest_reg = std::max(first_free_r, opt_r);
|
|
assert(dest_reg < sinks.size());
|
|
|
|
if(i>0){
|
|
// Push bounds due to changing the source crossing the boundary j/j+1
|
|
// Linear amortized complexity accross all sources (next_r grows)
|
|
// get_slope(i-1, j) - get_slope(i, j) == 0 if j >= next_r
|
|
// get_slope(i-1, j) - get_slope(i, j) == 0 if j < prev_next_r-1
|
|
|
|
for(index_t j=std::max(prev_next_r,1u)-1; j<std::min(first_free_r, next_r+1); ++j){
|
|
assert(get_slope(i,j) <= get_slope(i-1,j));
|
|
push_bound(prev_cap[j+1] - prev_dem[i], get_slope(i-1, j) - get_slope(i,j));
|
|
}
|
|
}
|
|
// Add the bounds due to crossing the boundaries alone
|
|
for(index_t j=first_free_r; j<opt_r; ++j){
|
|
assert(get_slope(i,j) <= 0);
|
|
push_bound(prev_cap[j+1] - prev_dem[i], -get_slope(i, j));
|
|
}
|
|
|
|
first_free_r = std::max(first_free_r, opt_r);
|
|
capacity_t this_abs_pos = std::max(cur_abs_pos, prev_cap[first_free_r] - prev_dem[i]); // Just after the previous cell or at the beginning of the destination region
|
|
|
|
while(first_free_r+1 < sinks.size() and this_abs_pos > std::max(prev_cap[first_free_r+1] - prev_dem[i+1], min_abs_pos)){ // Absolute position that wouldn't make the cell fit in the region, and we are not in the last region yet
|
|
capacity_t end_pos = std::max(prev_cap[first_free_r+1] - prev_dem[i+1], min_abs_pos);
|
|
|
|
int_t add_slope = get_slope(i, first_free_r);
|
|
int_t slope = add_slope;
|
|
|
|
while(not bounds.empty() and slope >= 0 and bounds.top().pos > end_pos){
|
|
this_abs_pos = bounds.top().pos;
|
|
slope -= bounds.top().slope_diff;
|
|
bounds.pop();
|
|
}
|
|
if(slope >= 0){ // We still push: the cell completely escapes the region
|
|
this_abs_pos = end_pos;
|
|
push_bound(end_pos, add_slope-slope);
|
|
}
|
|
else{ // Ok, absorbed the whole slope: push what remains and we still occupy the next region
|
|
push_bound(this_abs_pos, -slope);
|
|
++first_free_r;
|
|
}
|
|
}
|
|
cur_abs_pos = this_abs_pos;
|
|
constraining_pos.push_back(this_abs_pos);
|
|
}
|
|
|
|
assert(constraining_pos.size() == sources.size());
|
|
if(not constraining_pos.empty()){
|
|
// Calculate the final constraining_pos
|
|
constraining_pos.back() = std::min(max_abs_pos, constraining_pos.back());
|
|
}
|
|
|
|
std::partial_sum(constraining_pos.rbegin(), constraining_pos.rend(), constraining_pos.rbegin(), [](capacity_t a, capacity_t b)->capacity_t{ return std::min(a, b); });
|
|
|
|
for(index_t i=0; i<constraining_pos.size(); ++i){
|
|
constraining_pos[i] += prev_dem[i];
|
|
}
|
|
|
|
return constraining_pos;
|
|
}
|
|
|
|
namespace{ // Anonymous namespace to hide the transportation structures
|
|
|
|
class current_allocation{
|
|
static const index_t null_ind;
|
|
|
|
// Internal data structures
|
|
|
|
// Priority queue element to determine the source to be used between regions
|
|
struct movable_source{
|
|
index_t source;
|
|
float_t cost;
|
|
bool operator<(movable_source const o) const{
|
|
return cost > o.cost // Sorted by cost
|
|
|| (cost == o.cost && source < o.source); // And by index to limit the number of fractional elements between two regions
|
|
}
|
|
movable_source(index_t s, float_t c) : source(s), cost(c) {}
|
|
};
|
|
|
|
// Member data
|
|
|
|
// The current state
|
|
std::vector<std::vector<capacity_t> > sr_allocations; // For each region, for each source, the capacity allocated by the region
|
|
std::vector<std::vector<float_t> > sr_costs; // The costs from a region to a source
|
|
std::vector<capacity_t> s_demands; // The demands of the sources
|
|
std::vector<capacity_t> r_capacities; // The remaining capacities of the regions
|
|
|
|
// Shortest path data
|
|
std::vector<float_t> r_costs; // The costs of allocating to a region
|
|
std::vector<index_t> r_parents; // The parents of the regions i.e. the regions where we push sources first (or null_ind)
|
|
std::vector<index_t> r_sources; // The source involved in these edges
|
|
std::vector<capacity_t> arc_capacities; // The capacities of the edges to the parents, or of the region if no parent
|
|
|
|
// Best edges data
|
|
std::vector<std::vector<std::priority_queue<movable_source> > > best_interregions_costs; // What is the best source to move to go from region k1 to region k2?
|
|
index_t dijkstra_cnt;
|
|
|
|
|
|
// Helper functions
|
|
|
|
// Number of regions
|
|
index_t region_cnt() const{
|
|
assert(sr_costs.size() == sr_allocations.size());
|
|
return sr_costs.size();
|
|
}
|
|
|
|
// Update the edge between two regions
|
|
void update_edge(index_t r1, index_t r2);
|
|
// Add a source to all heaps of a region; returns if we need to update a path
|
|
bool add_source_to_heaps(index_t r, index_t source);
|
|
// Initialize the heaps of a region
|
|
void create_heaps(index_t reg);
|
|
|
|
// Run the shortest path algorithm to update the cost of each region
|
|
void dijkstra_update();
|
|
|
|
// Update the edge and returns if we need to rerun Dijkstra
|
|
bool push_edge(index_t reg, capacity_t flow);
|
|
// Updates a full path when pushing an element; returns if we need to rerun Dijkstra
|
|
bool push_path(index_t pushed_reg, capacity_t demanded, capacity_t & flow);
|
|
|
|
public:
|
|
// Add a new source to the transportation problem; should be done in decreasing order of demand to keep low complexity
|
|
void add_source(index_t elt_ind);
|
|
|
|
current_allocation(std::vector<capacity_t> caps, std::vector<capacity_t> demands, std::vector<std::vector<float_t> > costs)
|
|
:
|
|
sr_allocations(caps.size()),
|
|
sr_costs(costs),
|
|
s_demands(demands),
|
|
r_capacities(caps),
|
|
r_costs(caps.size(), 0.0),
|
|
r_parents(caps.size(), null_ind),
|
|
r_sources(caps.size(), null_ind),
|
|
arc_capacities(caps),
|
|
best_interregions_costs(caps.size(), std::vector<std::priority_queue<movable_source> >(caps.size())),
|
|
dijkstra_cnt(0)
|
|
{
|
|
assert(caps.size() > 0);
|
|
assert(costs.size() == caps.size());
|
|
dijkstra_update();
|
|
}
|
|
|
|
std::vector<std::vector<capacity_t> > get_allocations() const{ return sr_allocations; }
|
|
index_t get_iterations_cnt() const { return dijkstra_cnt; }
|
|
};
|
|
|
|
const index_t current_allocation::null_ind = std::numeric_limits<index_t>::max();
|
|
|
|
void current_allocation::update_edge(index_t r1, index_t r2){
|
|
while(not best_interregions_costs[r1][r2].empty() and sr_allocations[r1][best_interregions_costs[r1][r2].top().source] == 0){
|
|
best_interregions_costs[r1][r2].pop();
|
|
}
|
|
|
|
if(not best_interregions_costs[r1][r2].empty()){
|
|
// There is an edge
|
|
movable_source cur = best_interregions_costs[r1][r2].top();
|
|
float_t new_cost = r_costs[r2] + cur.cost;
|
|
if(new_cost < r_costs[r1]){
|
|
r_costs[r1] = cur.cost;
|
|
r_sources[r1] = cur.source;
|
|
r_parents[r1] = r2;
|
|
arc_capacities[r1] = sr_allocations[r1][cur.source];
|
|
}
|
|
}
|
|
}
|
|
|
|
bool current_allocation::add_source_to_heaps(index_t r, index_t source){
|
|
bool need_rerun = false;
|
|
for(index_t i=0; i<region_cnt(); ++i){
|
|
if(i == r) continue;
|
|
best_interregions_costs[r][i].push(
|
|
movable_source(source,
|
|
sr_costs[i][source] - sr_costs[r][source]
|
|
)
|
|
);
|
|
while(sr_allocations[r][best_interregions_costs[r][i].top().source] == 0){
|
|
best_interregions_costs[r][i].pop();
|
|
}
|
|
need_rerun = (best_interregions_costs[r][i].top().source == source) or need_rerun;
|
|
}
|
|
return need_rerun;
|
|
}
|
|
|
|
void current_allocation::create_heaps(index_t reg){
|
|
// Get all relevant elements
|
|
std::vector<std::vector<movable_source> > interregion_costs(region_cnt());
|
|
for(index_t i=0; i<sr_allocations[reg].size(); ++i){
|
|
if(sr_allocations[reg][i] > 0){
|
|
for(index_t oreg=0; oreg<region_cnt(); ++oreg){
|
|
if(oreg == reg) continue;
|
|
interregion_costs[oreg].push_back(
|
|
movable_source(
|
|
i,
|
|
sr_costs[oreg][i] - sr_costs[reg][i]
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
// Create the heaps
|
|
for(index_t oreg=0; oreg<region_cnt(); ++oreg){
|
|
best_interregions_costs[reg][oreg] = std::priority_queue<movable_source>(interregion_costs[oreg].begin(), interregion_costs[oreg].end());
|
|
}
|
|
}
|
|
|
|
// Returns if the path has been modified so that we would need to rerun Dijkstra
|
|
bool current_allocation::push_edge(index_t reg, capacity_t flow){
|
|
index_t cur_source = r_sources[reg];
|
|
|
|
// Does this edge allocates a new source in the destination region? If yes, update the corresponding heaps
|
|
bool already_present = sr_allocations[r_parents[reg]][cur_source] > 0;
|
|
|
|
// Deallocating from the first region is handled by the get_edge function: just substract the flow
|
|
sr_allocations[ reg ][cur_source] -= flow;
|
|
sr_allocations[r_parents[reg]][cur_source] += flow;
|
|
|
|
assert(sr_allocations[reg][cur_source] >= 0); // The source to be pushed was indeed present in the region
|
|
assert(r_capacities[reg] == 0); // The region is full, which explains why we need to push
|
|
assert(flow <= arc_capacities[reg]); // The flow is not bigger than what can be sent
|
|
|
|
arc_capacities[reg] = sr_allocations[reg][cur_source]; // Just update the capacity if it turns out that we don't need to run Dijkstra
|
|
|
|
if(arc_capacities[reg] == 0){
|
|
// The source may have been deleted from a region: rerun Dijkstra at the end
|
|
return true;
|
|
}
|
|
else if(not already_present and r_capacities[r_parents[reg]] == 0){
|
|
// A new source is allocated to a full region: rerun Dijkstra at the end if it changed the heap's top
|
|
return add_source_to_heaps(r_parents[reg], cur_source);
|
|
}
|
|
else{
|
|
// The edge is still present with the same cost and non-zero updated capacity
|
|
// The path still exists: no need to rerun Dijkstra yet
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void current_allocation::dijkstra_update(){
|
|
// Simple case of the regions with remaining capacity
|
|
std::vector<int> visited(region_cnt(), 0);
|
|
index_t visited_cnt = 0;
|
|
for(index_t i=0; i<region_cnt(); ++i){
|
|
r_sources[i] = null_ind;
|
|
r_parents[i] = null_ind;
|
|
if(r_capacities[i] > 0){
|
|
r_costs[i] = 0.0;
|
|
arc_capacities[i] = r_capacities[i];
|
|
|
|
visited[i] = 1;
|
|
++visited_cnt;
|
|
}
|
|
else{
|
|
r_costs[i] = std::numeric_limits<float_t>::infinity();
|
|
arc_capacities[i] = 0;
|
|
}
|
|
}
|
|
// if(visited_cnt <= 0) throw std::runtime_error("Capacity problem: no region has been marked as reachable\n");
|
|
if(visited_cnt == region_cnt()){ return; }
|
|
// Get the costs for every non-visited region
|
|
for(index_t i=0; i<region_cnt(); ++i) if(visited[i] == 0){ // For every region that is not visited yet
|
|
for(index_t j=0; j<region_cnt(); ++j) if(visited[j] == 1){ // For every already visited region
|
|
// Get the best interregion cost
|
|
update_edge(i,j);
|
|
}
|
|
}
|
|
while(visited_cnt < region_cnt()){
|
|
// Find the region with the lowest cost to visit; mark it visited
|
|
index_t best_reg = null_ind;
|
|
float_t best_cost = std::numeric_limits<float_t>::infinity();
|
|
for(index_t i=0; i<region_cnt(); ++i) if(visited[i] == 0){ // For every region that is not visited yet
|
|
if(r_costs[i] < best_cost){
|
|
best_cost = r_costs[i];
|
|
best_reg = i;
|
|
}
|
|
}
|
|
if(best_reg == null_ind) break; // Some regions are unreachable, typically because they have zero capacity at the beginning
|
|
visited[best_reg] = 1;
|
|
++visited_cnt;
|
|
// Update the cost for every unvisited region
|
|
for(index_t i=0; i<region_cnt(); ++i) if(visited[i] == 0){ // For every region that is not visited yet
|
|
update_edge(i, best_reg);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool current_allocation::push_path(index_t pushed_reg, capacity_t demanded, capacity_t & flow){
|
|
// Get the final flow sent, which is smaller than the capacities on the path
|
|
flow = demanded;
|
|
for(index_t reg = pushed_reg; reg != null_ind; reg = r_parents[reg]){
|
|
flow = std::min(flow, arc_capacities[reg]);
|
|
}
|
|
|
|
bool rerun_dijkstra = false;
|
|
// Update the path between the regions
|
|
index_t reg = pushed_reg;
|
|
for(; r_parents[reg] != null_ind; reg = r_parents[reg]){
|
|
assert(r_capacities[reg] == 0);
|
|
rerun_dijkstra = push_edge(reg, flow) or rerun_dijkstra;
|
|
}
|
|
|
|
assert(r_capacities[reg] > 0);
|
|
assert(arc_capacities[reg] == r_capacities[reg]);
|
|
assert(r_capacities[reg] >= flow);
|
|
|
|
// Update the capacities at the end
|
|
r_capacities[reg] -= flow;
|
|
arc_capacities[reg] -= flow;
|
|
|
|
// The last region on the path is the one that satisfies the demand
|
|
if(r_capacities[reg] == 0){ // If we just consumed the available capacity, it becomes useful to move sources off this region: build the heap
|
|
create_heaps(reg);
|
|
rerun_dijkstra = true;
|
|
}
|
|
|
|
assert(flow > 0);
|
|
|
|
// If an edge changes cost or a region is full,
|
|
// we need to update the costs, parents, sources and arc_capacities using a Dijkstra
|
|
// but later
|
|
return rerun_dijkstra;
|
|
}
|
|
|
|
void current_allocation::add_source(index_t elt_ind){ //capacity_t demand, std::vector<float_t> const & costs){
|
|
for(index_t i=0; i<region_cnt(); ++i){
|
|
sr_allocations[i].push_back(0);
|
|
}
|
|
|
|
bool need_rerun = false;
|
|
capacity_t demand = s_demands[elt_ind];
|
|
|
|
while(demand > 0){
|
|
// In case we modified the structures earlier
|
|
if(need_rerun){
|
|
dijkstra_update();
|
|
need_rerun = false;
|
|
}
|
|
|
|
++ dijkstra_cnt;
|
|
index_t best_reg = null_ind;
|
|
float_t best_cost = std::numeric_limits<float_t>::infinity();
|
|
for(index_t reg=0; reg<region_cnt(); ++reg){
|
|
// Find the region which gets the source
|
|
if(r_costs[reg] + sr_costs[reg][elt_ind] < best_cost){
|
|
best_reg = reg;
|
|
best_cost = r_costs[reg] + sr_costs[reg][elt_ind];
|
|
}
|
|
}
|
|
if(best_reg == null_ind){ throw std::runtime_error("No reachable region found\n"); }
|
|
|
|
capacity_t flow = 0;
|
|
// Tells us whether we need to update the data structures
|
|
need_rerun = push_path(best_reg, demand, flow);
|
|
demand -= flow;
|
|
|
|
// Lazily store the change
|
|
sr_allocations[best_reg][elt_ind] += flow;
|
|
}
|
|
|
|
// Set the source's demand
|
|
for(index_t i=0; i<region_cnt(); ++i){
|
|
if(r_capacities[i] == 0 and sr_allocations[i][elt_ind] > 0){
|
|
need_rerun = add_source_to_heaps(i, elt_ind) or need_rerun;
|
|
}
|
|
}
|
|
// We leave a clean set with correct paths for the next iteration
|
|
if(need_rerun)
|
|
dijkstra_update();
|
|
}
|
|
|
|
} // End anonymous namespace
|
|
|
|
std::vector<std::vector<capacity_t> > transport_generic(std::vector<capacity_t> const & capacities, std::vector<capacity_t> const & demands, std::vector<std::vector<float_t> > const & costs){
|
|
current_allocation transporter(capacities, demands, costs);
|
|
|
|
for(index_t i=0; i<demands.size(); ++i){
|
|
transporter.add_source(i);
|
|
}
|
|
|
|
return transporter.get_allocations();
|
|
}
|
|
|
|
bool place_convex_single_row(std::vector<int_t> const & widths, std::vector<std::pair<int_t, int_t> > const & ranges, std::vector<cell_bound> bounds, std::vector<int_t> const & const_slopes, std::vector<int_t> & positions){
|
|
std::sort(bounds.begin(), bounds.end());
|
|
|
|
struct bound{
|
|
int_t abs_pos;
|
|
int_t slope_diff;
|
|
|
|
bool operator<(bound const o) const{ return abs_pos < o.abs_pos; }
|
|
bound(int_t p, int_t s) : abs_pos(p), slope_diff(s) {}
|
|
};
|
|
std::priority_queue<bound> prio_queue;
|
|
|
|
std::vector<int_t> prev_widths(widths.size()+1, 0);
|
|
std::partial_sum(widths.begin(), widths.end(), std::next(prev_widths.begin()));
|
|
|
|
std::vector<int_t> constraining_pos(widths.size());
|
|
|
|
int_t lower_lim = std::numeric_limits<int_t>::min();
|
|
for(index_t i=0, j=0; i<widths.size(); ++i){
|
|
int_t old_width = prev_widths[i];
|
|
int_t new_width = prev_widths[i+1];
|
|
|
|
lower_lim = std::max(ranges[i].first - old_width, lower_lim);
|
|
int_t upper_lim = ranges[i].second - new_width;
|
|
|
|
for(; j<bounds.size() and bounds[j].c == i; ++j){
|
|
prio_queue.push(bound(bounds[j].pos - old_width, bounds[j].slope));
|
|
}
|
|
|
|
if(upper_lim < lower_lim){ // Infeasible
|
|
return false;
|
|
}
|
|
int_t cur_slope = const_slopes[i];
|
|
int_t cur_pos = upper_lim;
|
|
|
|
while(not prio_queue.empty() and (cur_slope > 0 or prio_queue.top().abs_pos > upper_lim)){
|
|
cur_slope -= prio_queue.top().slope_diff;
|
|
cur_pos = prio_queue.top().abs_pos;
|
|
prio_queue.pop();
|
|
}
|
|
int_t final_abs_pos = std::max(std::min(cur_pos, upper_lim), lower_lim);
|
|
constraining_pos[i] = final_abs_pos;
|
|
if(cur_slope < 0){
|
|
prio_queue.push(bound(final_abs_pos, -cur_slope));
|
|
}
|
|
}
|
|
|
|
positions.resize(constraining_pos.size());
|
|
std::partial_sum(constraining_pos.rbegin(), constraining_pos.rend(), positions.rbegin(), [](int_t a, int_t b)->int_t{ return std::min(a,b); });
|
|
for(index_t i=0; i<positions.size(); ++i){
|
|
positions[i] += prev_widths[i];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool place_noncvx_single_row(std::vector<int_t> const & widths, std::vector<std::pair<int_t, int_t> > const & ranges, std::vector<int> const & flippables, std::vector<cell_bound> bounds, std::vector<int_t> const & const_slopes, std::vector<int_t> & positions, std::vector<int> & flippings){
|
|
flippings = std::vector<int>(positions.size(), 0);
|
|
return place_convex_single_row(widths, ranges, bounds, const_slopes, positions);
|
|
}
|
|
|
|
} // Namespace coloquinte
|
|
|