|
27 | 27 | #include <vector>
|
28 | 28 |
|
29 | 29 | #include "absl/algorithm/container.h"
|
| 30 | +#include "absl/container/flat_hash_map.h" |
| 31 | +#include "absl/container/flat_hash_set.h" |
30 | 32 | #include "absl/log/check.h"
|
31 | 33 | #include "absl/strings/str_format.h"
|
32 | 34 | #include "absl/strings/string_view.h"
|
|
36 | 38 | #include "ortools/base/logging.h"
|
37 | 39 | #include "ortools/base/map_util.h"
|
38 | 40 | #include "ortools/base/mathutil.h"
|
| 41 | +#include "ortools/base/strong_vector.h" |
39 | 42 | #include "ortools/base/types.h"
|
40 | 43 | #include "ortools/constraint_solver/constraint_solver.h"
|
41 | 44 | #include "ortools/glop/parameters.pb.h"
|
@@ -1039,15 +1042,12 @@ DimensionSchedulingStatus DimensionCumulOptimizerCore::OptimizeAndPack(
|
1039 | 1042 | packing_parameters.set_use_preprocessing(true);
|
1040 | 1043 | solver->SetParameters(packing_parameters.SerializeAsString());
|
1041 | 1044 | }
|
1042 |
| - DimensionSchedulingStatus status = DimensionSchedulingStatus::OPTIMAL; |
1043 |
| - if (Optimize(next_accessor, dimension_travel_info_per_route, solver, |
| 1045 | + DimensionSchedulingStatus status = |
| 1046 | + Optimize(next_accessor, dimension_travel_info_per_route, solver, |
1044 | 1047 | /*cumul_values=*/nullptr, /*break_values=*/nullptr,
|
1045 | 1048 | /*resource_indices_per_group=*/nullptr, &cost,
|
1046 | 1049 | /*transit_cost=*/nullptr,
|
1047 |
| - /*clear_lp=*/false, /*optimize_resource_assignment=*/false) == |
1048 |
| - DimensionSchedulingStatus::INFEASIBLE) { |
1049 |
| - status = DimensionSchedulingStatus::INFEASIBLE; |
1050 |
| - } |
| 1050 | + /*clear_lp=*/false, /*optimize_resource_assignment=*/false); |
1051 | 1051 | if (status != DimensionSchedulingStatus::INFEASIBLE) {
|
1052 | 1052 | std::vector<int> vehicles(dimension()->model()->vehicles());
|
1053 | 1053 | std::iota(vehicles.begin(), vehicles.end(), 0);
|
@@ -2147,6 +2147,114 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
|
2147 | 2147 | }
|
2148 | 2148 | }
|
2149 | 2149 |
|
| 2150 | + // TODO(user): find why adding these constraints make CPSAT slower. |
| 2151 | + if (!solver->IsCPSATSolver()) { |
| 2152 | + for (const auto& [limit, min_break_duration] : |
| 2153 | + dimension_->GetBreakDistanceDurationOfVehicle(vehicle)) { |
| 2154 | + int64_t min_num_breaks = 0; |
| 2155 | + if (limit > 0) { |
| 2156 | + min_num_breaks = |
| 2157 | + std::max<int64_t>(0, CapSub(total_fixed_transit, 1) / limit); |
| 2158 | + } |
| 2159 | + if (CapSub(current_route_min_cumuls_.back(), |
| 2160 | + current_route_max_cumuls_.front()) > limit) { |
| 2161 | + min_num_breaks = std::max<int64_t>(min_num_breaks, 1); |
| 2162 | + } |
| 2163 | + if (num_breaks < min_num_breaks) return false; |
| 2164 | + if (min_num_breaks == 0) continue; |
| 2165 | + |
| 2166 | + // Adds an LP relaxation of interbreak constraints. |
| 2167 | + // For all 0 <= pl < pr < path_size, for k > 0, |
| 2168 | + // if sum_{p in [pl, pr)} fixed_transit[p] > k * limit, |
| 2169 | + // then sum_{p in [pl, pr)} slack[p] >= k * min_break_duration. |
| 2170 | + // |
| 2171 | + // Moreover, if end_min[pr] - start_max[pl] > limit, |
| 2172 | + // the sum_{p in [pl, pr)} slack[p] >= min_break_duration. |
| 2173 | + // |
| 2174 | + // We want to apply the constraints above, without the ones that are |
| 2175 | + // dominated: |
| 2176 | + // - do not add the same constraint for k' < k, keep the largest k. |
| 2177 | + // - do not add the constraint for both (pl', pr') and (pl, pr) |
| 2178 | + // if [pl', pr') is a subset of [pl, pr), keep the smallest interval. |
| 2179 | + // TODO(user): reduce the number of constraints further; |
| 2180 | + // for instance if the constraint holds for (k, pl, pr) and (k', pl', pr') |
| 2181 | + // with pr <= pr', then no need to add the constraint for (k+k', pl, pr'). |
| 2182 | + // |
| 2183 | + // We need fast access to sum_{p in [pl, pr)} fixed_transit[p]. |
| 2184 | + // This will be sum_transits[pr] - sum_transits[pl]. Note that |
| 2185 | + // sum_transits[0] = 0, sum_transits[path_size-1] = total_fixed_transit. |
| 2186 | + std::vector<int64_t> sum_transits(path_size); |
| 2187 | + { |
| 2188 | + sum_transits[0] = 0; |
| 2189 | + for (int pos = 1; pos < path_size; ++pos) { |
| 2190 | + sum_transits[pos] = sum_transits[pos - 1] + fixed_transit[pos - 1]; |
| 2191 | + } |
| 2192 | + } |
| 2193 | + // To add the slack sum constraints, we need slack sum variables. |
| 2194 | + // Those are created lazily in a sparse vector, then only those useful |
| 2195 | + // variables are linked to slack variables after slack sum constraints |
| 2196 | + // have been added. |
| 2197 | + std::vector<int> slack_sum_vars(path_size, -1); |
| 2198 | + // Given a number of breaks k, an interval of path positions [pl, pr), |
| 2199 | + // returns true if the interbreak constraint triggers for k breaks. |
| 2200 | + // TODO(user): find tighter end_min/start_max conditions. |
| 2201 | + // Mind that a break may be longer than min_break_duration. |
| 2202 | + auto trigger = [&](int k, int pl, int pr) -> bool { |
| 2203 | + if (k == 1) { |
| 2204 | + const int64_t span_lb = |
| 2205 | + current_route_min_cumuls_[pr] - current_route_max_cumuls_[pl]; |
| 2206 | + if (span_lb > limit) return true; |
| 2207 | + } |
| 2208 | + return sum_transits[pr] - sum_transits[pl] > k * limit; |
| 2209 | + }; |
| 2210 | + int min_sum_var_index = path_size; |
| 2211 | + int max_sum_var_index = -1; |
| 2212 | + for (int k = 1; k <= min_num_breaks; ++k) { |
| 2213 | + int pr = 0; |
| 2214 | + for (int pl = 0; pl < path_size - 1; ++pl) { |
| 2215 | + pr = std::max(pr, pl + 1); |
| 2216 | + // Increase pr until transit(pl, pr) > k * limit. |
| 2217 | + while (pr < path_size && !trigger(k, pl, pr)) ++pr; |
| 2218 | + if (pr == path_size) break; |
| 2219 | + // Reduce [pl, pr) from the left. |
| 2220 | + while (pl < pr && trigger(k, pl + 1, pr)) ++pl; |
| 2221 | + if (slack_sum_vars[pl] == -1) { |
| 2222 | + slack_sum_vars[pl] = solver->CreateNewPositiveVariable(); |
| 2223 | + min_sum_var_index = std::min(min_sum_var_index, pl); |
| 2224 | + } |
| 2225 | + if (slack_sum_vars[pr] == -1) { |
| 2226 | + slack_sum_vars[pr] = solver->CreateNewPositiveVariable(); |
| 2227 | + max_sum_var_index = std::max(max_sum_var_index, pr); |
| 2228 | + } |
| 2229 | + // If k is the largest for this interval, add the constraint. |
| 2230 | + // The call trigger(k', pl, pr) may hold for both k and k+1 with an |
| 2231 | + // irreducible interval [pl, pr] when there is a transit > limit at |
| 2232 | + // the beginning and at the end of the sub-route. sum_slacks[pr] - |
| 2233 | + // sum_slacks[pl] >= k * min_break_duration. |
| 2234 | + if (k < min_num_breaks && trigger(k + 1, pl, pr)) continue; |
| 2235 | + solver->AddLinearConstraint( |
| 2236 | + k * min_break_duration, kint64max, |
| 2237 | + {{slack_sum_vars[pr], 1}, {slack_sum_vars[pl], -1}}); |
| 2238 | + } |
| 2239 | + } |
| 2240 | + if (min_sum_var_index < max_sum_var_index) { |
| 2241 | + slack_sum_vars[min_sum_var_index] = solver->AddVariable(0, 0); |
| 2242 | + int prev_index = min_sum_var_index; |
| 2243 | + for (int pos = min_sum_var_index + 1; pos <= max_sum_var_index; ++pos) { |
| 2244 | + if (slack_sum_vars[pos] == -1) continue; |
| 2245 | + // slack_sum_var[pos] = |
| 2246 | + // slack_sum_var[prev_index] + sum_{p in [prev_index, pos)} slack[p]. |
| 2247 | + const int ct = solver->AddLinearConstraint( |
| 2248 | + 0, 0, |
| 2249 | + {{slack_sum_vars[pos], 1}, {slack_sum_vars[prev_index], -1}}); |
| 2250 | + for (int p = prev_index; p < pos; ++p) { |
| 2251 | + solver->SetCoefficient(ct, lp_slacks[p], -1); |
| 2252 | + } |
| 2253 | + prev_index = pos; |
| 2254 | + } |
| 2255 | + } |
| 2256 | + } |
| 2257 | + } |
2150 | 2258 | if (!solver->IsCPSATSolver()) return true;
|
2151 | 2259 | if (!dimension_->GetBreakDistanceDurationOfVehicle(vehicle).empty()) {
|
2152 | 2260 | // If there is an optional interval, the following model would be wrong.
|
@@ -2266,7 +2374,7 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
|
2266 | 2374 | }
|
2267 | 2375 |
|
2268 | 2376 | return true;
|
2269 |
| -} |
| 2377 | +} // NOLINT(readability/fn_size) |
2270 | 2378 |
|
2271 | 2379 | namespace {
|
2272 | 2380 | bool AllValuesContainedExcept(const IntVar& var, absl::Span<const int> values,
|
|
0 commit comments