|
| 1 | +#include "balancing.hpp" |
| 2 | +#include "Highs.h" |
| 3 | +#include <cmath> |
| 4 | + |
| 5 | +namespace jres::constraints { |
| 6 | + |
| 7 | +static const double kCostFairness = 10.0; |
| 8 | + |
| 9 | +void add_role_coupling_incentive( |
| 10 | + Highs* highs, |
| 11 | + const std::vector<jres::internal::TeamMember>& pool, |
| 12 | + const std::map<std::pair<std::string, int>, int>& driverVars, |
| 13 | + const std::map<std::pair<std::string, int>, int>& spotterVars, |
| 14 | + size_t numStints, |
| 15 | + double weight) |
| 16 | +{ |
| 17 | + if (std::abs(weight) < 1e-6) return; |
| 18 | + |
| 19 | + for (const auto &p : pool) { |
| 20 | + for (size_t s = 0; s < numStints - 1; ++s) { |
| 21 | + bool hasDriver = driverVars.count({p.name, (int)s}); |
| 22 | + bool hasSpotter = spotterVars.count({p.name, (int)s + 1}); |
| 23 | + |
| 24 | + if (hasDriver && hasSpotter) { |
| 25 | + int d_var = driverVars.at({p.name, (int)s}); |
| 26 | + int s_var = spotterVars.at({p.name, (int)s + 1}); |
| 27 | + |
| 28 | + // If there is a transition from driving (stint s) to spotting (stint s+1), reward it. |
| 29 | + |
| 30 | + int coupling_var = highs->getNumCol(); |
| 31 | + highs->addVar(0.0, 1.0); |
| 32 | + highs->changeColIntegrality(coupling_var, HighsVarType::kInteger); |
| 33 | + highs->changeColCost(coupling_var, -weight); |
| 34 | + |
| 35 | + // z <= d_var |
| 36 | + highs->addRow(-kHighsInf, 0.0, 2, std::vector<int>{coupling_var, d_var}.data(), std::vector<double>{1.0, -1.0}.data()); |
| 37 | + // z <= s_var |
| 38 | + highs->addRow(-kHighsInf, 0.0, 2, std::vector<int>{coupling_var, s_var}.data(), std::vector<double>{1.0, -1.0}.data()); |
| 39 | + } |
| 40 | + } |
| 41 | + } |
| 42 | +} |
| 43 | + |
| 44 | +void add_balancing_constraints( |
| 45 | + Highs &highs, |
| 46 | + const std::vector<jres::internal::TeamMember> &participants, |
| 47 | + const jres::internal::SolverInput& input, |
| 48 | + const std::map<std::pair<std::string, int>, int>& workVars, |
| 49 | + double avgStints) |
| 50 | +{ |
| 51 | + for (const auto &p : participants) { |
| 52 | + std::vector<int> stint_indices; |
| 53 | + std::vector<double> stint_values; |
| 54 | + |
| 55 | + std::map<int, double> varCounts; |
| 56 | + for (size_t s = 0; s < input.stints.size(); ++s) { |
| 57 | + if (workVars.count({p.name, (int)s})) { |
| 58 | + int v = workVars.at({p.name, (int)s}); |
| 59 | + varCounts[v] += 1.0; |
| 60 | + } |
| 61 | + } |
| 62 | + for(auto const& [v, count] : varCounts) { |
| 63 | + stint_indices.push_back(v); |
| 64 | + stint_values.push_back(count); |
| 65 | + } |
| 66 | + |
| 67 | + if (stint_indices.empty()) continue; |
| 68 | + |
| 69 | + int total_stints_var = highs.getNumCol(); |
| 70 | + highs.addVar(0.0, kHighsInf); |
| 71 | + stint_indices.push_back(total_stints_var); |
| 72 | + stint_values.push_back(-1.0); |
| 73 | + highs.addRow(0.0, 0.0, (int)stint_indices.size(), stint_indices.data(), stint_values.data()); |
| 74 | + |
| 75 | + int over_avg_var = highs.getNumCol(); |
| 76 | + highs.addVar(0.0, kHighsInf); |
| 77 | + int under_avg_var = highs.getNumCol(); |
| 78 | + highs.addVar(0.0, kHighsInf); |
| 79 | + |
| 80 | + std::vector<int> idx_over = {over_avg_var, total_stints_var}; |
| 81 | + std::vector<double> val_over = {1.0, -1.0}; |
| 82 | + highs.addRow(0.0, kHighsInf, 2, idx_over.data(), val_over.data()); |
| 83 | + highs.changeRowBounds(highs.getNumRow() - 1, -avgStints, kHighsInf); |
| 84 | + |
| 85 | + std::vector<int> idx_under = {under_avg_var, total_stints_var}; |
| 86 | + std::vector<double> val_under = {1.0, 1.0}; |
| 87 | + highs.addRow(0.0, kHighsInf, 2, idx_under.data(), val_under.data()); |
| 88 | + highs.changeRowBounds(highs.getNumRow() - 1, avgStints, kHighsInf); |
| 89 | + |
| 90 | + highs.changeColCost(over_avg_var, kCostFairness); |
| 91 | + highs.changeColCost(under_avg_var, kCostFairness); |
| 92 | + } |
| 93 | +} |
| 94 | + |
| 95 | +} // namespace jres::constraints |
0 commit comments