#include "coloquinte/optimization_subproblems.hxx" #include namespace coloquinte{ std::vector transport_1D(std::vector sources, std::vector 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 bounds; std::vector constraining_pos; std::vector 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; i0){ // 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::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 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 > sr_allocations; // For each region, for each source, the capacity allocated by the region std::vector > sr_costs; // The costs from a region to a source std::vector s_demands; // The demands of the sources std::vector r_capacities; // The remaining capacities of the regions // Shortest path data std::vector r_costs; // The costs of allocating to a region std::vector r_parents; // The parents of the regions i.e. the regions where we push sources first (or null_ind) std::vector r_sources; // The source involved in these edges std::vector arc_capacities; // The capacities of the edges to the parents, or of the region if no parent // Best edges data std::vector > > 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 caps, std::vector demands, std::vector > 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 >(caps.size())), dijkstra_cnt(0) { assert(caps.size() > 0); assert(costs.size() == caps.size()); dijkstra_update(); } std::vector > 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::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 > interregion_costs(region_cnt()); for(index_t i=0; i 0){ for(index_t oreg=0; oreg(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 visited(region_cnt(), 0); index_t visited_cnt = 0; for(index_t i=0; 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::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::infinity(); for(index_t i=0; i 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 const & costs){ for(index_t i=0; i 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::infinity(); for(index_t reg=0; reg 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 > transport_generic(std::vector const & capacities, std::vector const & demands, std::vector > const & costs){ current_allocation transporter(capacities, demands, costs); for(index_t i=0; i const & widths, std::vector > const & ranges, std::vector bounds, std::vector const & const_slopes, std::vector & 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 prio_queue; std::vector prev_widths(widths.size()+1, 0); std::partial_sum(widths.begin(), widths.end(), std::next(prev_widths.begin())); std::vector constraining_pos(widths.size()); int_t lower_lim = std::numeric_limits::min(); for(index_t i=0, j=0; i 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 const & widths, std::vector > const & ranges, std::vector const & flippables, std::vector bounds, std::vector const & const_slopes, std::vector & positions, std::vector & flippings){ flippings = std::vector(positions.size(), 0); return place_convex_single_row(widths, ranges, bounds, const_slopes, positions); } } // Namespace coloquinte