Merge branch 'main' of github.com:google/or-tools

This commit is contained in:
Laurent Perron
2023-10-09 18:07:29 +02:00
16 changed files with 1739 additions and 163 deletions

View File

@@ -34,8 +34,8 @@
#include <string>
#include <vector>
#include "absl/log/check.h"
#include "absl/types/span.h"
#include "ortools/base/logging.h"
namespace operations_research {
@@ -405,7 +405,6 @@ inline std::vector<absl::Span<const T>> SimpleDynamicPartition::GetParts(
starts.clear();
return result;
}
} // namespace operations_research
#endif // OR_TOOLS_ALGORITHMS_DYNAMIC_PARTITION_H_

View File

@@ -116,10 +116,6 @@ PY_CONVERT_HELPER_PTR(LocalSearchFilter);
PY_CONVERT_HELPER_PTR(LocalSearchFilterManager);
PY_CONVERT_HELPER_INTEXPR_AND_INTVAR();
%{
%}
// Actual conversions. This also includes the conversion to std::vector<Class>.
PY_CONVERT(IntVar);
PY_CONVERT(IntExpr);
@@ -2198,6 +2194,4 @@ class PyConstraint(Constraint):
def DebugString(self):
return "PyConstraint"
} // %pythoncode

View File

@@ -877,7 +877,7 @@ class LocalSearchTest(unittest.TestCase):
"""Filter to speed up LS computation."""
def __init__(self, int_vars):
pywrapcp.IntVarLocalSearchFilter.__init__(self, int_vars)
super.__init__(int_vars)
self.__sum = 0
def OnSynchronize(self, delta):

View File

@@ -907,6 +907,13 @@ class RoutingModel {
const std::vector<std::pair<int, int> >&
GetDeliveryIndexPairs(int64_t node_index) const;
// clang-format on
/// Returns whether the node is a pickup (resp. delivery).
bool IsPickup(int64_t node_index) const {
return !GetPickupIndexPairs(node_index).empty();
}
bool IsDelivery(int64_t node_index) const {
return !GetDeliveryIndexPairs(node_index).empty();
}
/// Sets the Pickup and delivery policy of all vehicles. It is equivalent to
/// calling SetPickupAndDeliveryPolicyOfVehicle on all vehicles.

View File

@@ -24,11 +24,9 @@
#include <cstdlib>
#include <deque>
#include <functional>
#include <iterator>
#include <limits>
#include <map>
#include <memory>
#include <numeric>
#include <optional>
#include <set>
#include <string>
@@ -36,6 +34,7 @@
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/base/attributes.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
@@ -532,46 +531,67 @@ CheapestInsertionFilteredHeuristic::CheapestInsertionFilteredHeuristic(
evaluator_(std::move(evaluator)),
penalty_evaluator_(std::move(penalty_evaluator)) {}
namespace {
void ProcessVehicleStartEndCosts(
const RoutingModel& model, int node,
const std::function<void(int64_t, int)>& process_cost,
const Bitset64<int>& vehicle_set, bool ignore_start = false,
bool ignore_end = false) {
const auto start_end_cost = [&model, ignore_start, ignore_end](int64_t node,
int v) {
const int64_t start_cost =
ignore_start ? 0 : model.GetArcCostForVehicle(model.Start(v), node, v);
const int64_t end_cost =
ignore_end ? 0 : model.GetArcCostForVehicle(model.End(v), node, v);
return CapAdd(start_cost, end_cost);
};
// Iterating over an IntVar domain is faster than calling Contains.
// Therefore we iterate on 'vehicles' only if it's smaller than the domain
// size of the VehicleVar.
const IntVar* const vehicle_var = model.VehicleVar(node);
if (vehicle_var->Size() < vehicle_set.size()) {
std::unique_ptr<IntVarIterator> it(vehicle_var->MakeDomainIterator(false));
for (const int v : InitAndGetValues(it.get())) {
if (v < 0 || !vehicle_set[v]) {
continue;
}
process_cost(start_end_cost(node, v), v);
}
} else {
for (const int v : vehicle_set) {
if (!vehicle_var->Contains(v)) continue;
process_cost(start_end_cost(node, v), v);
}
}
}
} // namespace
std::vector<std::vector<CheapestInsertionFilteredHeuristic::StartEndValue>>
CheapestInsertionFilteredHeuristic::ComputeStartEndDistanceForVehicles(
const std::vector<int>& vehicles) {
// TODO(user): consider checking search limits.
const absl::flat_hash_set<int> vehicle_set(vehicles.begin(), vehicles.end());
const RoutingModel& model = *this->model();
std::vector<std::vector<StartEndValue>> start_end_distances_per_node(
model()->Size());
model.Size());
for (int node = 0; node < model()->Size(); node++) {
Bitset64<int> vehicle_set(model.vehicles());
for (int v : vehicles) vehicle_set.Set(v);
for (int node = 0; node < model.Size(); node++) {
if (Contains(node)) continue;
std::vector<StartEndValue>& start_end_distances =
start_end_distances_per_node[node];
start_end_distances.reserve(
std::min(model.VehicleVar(node)->Size(), vehicles.size()));
ProcessVehicleStartEndCosts(
model, node,
[&start_end_distances](int64_t dist, int v) {
start_end_distances.push_back({dist, v});
},
vehicle_set);
const auto add_distance = [this, node, &start_end_distances](int vehicle) {
const int64_t start = model()->Start(vehicle);
const int64_t end = model()->End(vehicle);
// We compute the distance of node to the start/end nodes of the route.
const int64_t distance =
CapAdd(model()->GetArcCostForVehicle(start, node, vehicle),
model()->GetArcCostForVehicle(node, end, vehicle));
start_end_distances.push_back({distance, vehicle});
};
// Iterating over an IntVar domain is faster than calling Contains.
// Therefore we iterate on 'vehicles' only if it's smaller than the domain
// size of the VehicleVar.
const IntVar* const vehicle_var = model()->VehicleVar(node);
if (vehicle_var->Size() < vehicles.size()) {
std::unique_ptr<IntVarIterator> it(
vehicle_var->MakeDomainIterator(false));
for (const int64_t vehicle : InitAndGetValues(it.get())) {
if (vehicle < 0 || !vehicle_set.contains(vehicle)) continue;
add_distance(vehicle);
}
} else {
start_end_distances.reserve(vehicles.size());
for (const int vehicle : vehicles) {
if (!vehicle_var->Contains(vehicle)) continue;
add_distance(vehicle);
}
}
// Sort the distances for the node to all start/ends of available vehicles
// in decreasing order.
std::sort(start_end_distances.begin(), start_end_distances.end(),
@@ -582,7 +602,7 @@ CheapestInsertionFilteredHeuristic::ComputeStartEndDistanceForVehicles(
return start_end_distances_per_node;
}
void CheapestInsertionFilteredHeuristic::AddSeedToQueue(
void CheapestInsertionFilteredHeuristic::AddSeedNodeToQueue(
int node, std::vector<StartEndValue>* start_end_distances, SeedQueue* sq) {
if (start_end_distances->empty()) {
return;
@@ -596,7 +616,7 @@ void CheapestInsertionFilteredHeuristic::AddSeedToQueue(
const uint64_t num_allowed_vehicles = model()->VehicleVar(node)->Size();
const int64_t neg_penalty = CapOpp(model()->UnperformedPenalty(node));
sq->priority_queue.push(
{num_allowed_vehicles, neg_penalty, start_end_value, node});
{num_allowed_vehicles, neg_penalty, start_end_value, true, node});
start_end_distances->pop_back();
}
@@ -608,7 +628,7 @@ void CheapestInsertionFilteredHeuristic::InitializeSeedQueue(
for (int node = 0; node < num_nodes; node++) {
if (Contains(node)) continue;
AddSeedToQueue(node, &start_end_distances_per_node->at(node), sq);
AddSeedNodeToQueue(node, &start_end_distances_per_node->at(node), sq);
}
}
@@ -791,8 +811,8 @@ bool GlobalCheapestInsertionFilteredHeuristic::BuildSolutionInternal() {
}
std::map<int64_t, std::vector<int>> nodes_by_bucket;
for (int node = 0; node < model()->Size(); ++node) {
if (!Contains(node) && model()->GetPickupIndexPairs(node).empty() &&
model()->GetDeliveryIndexPairs(node).empty()) {
if (!Contains(node) && !model()->IsPickup(node) &&
!model()->IsDelivery(node)) {
nodes_by_bucket[GetBucketOfNode(node)].push_back(node);
}
}
@@ -1460,7 +1480,8 @@ int GlobalCheapestInsertionFilteredHeuristic::InsertSeedNode(
while (!priority_queue.empty()) {
if (StopSearch()) return -1;
const Seed& seed = priority_queue.top();
const int seed_node = seed.node;
const int seed_node = seed.index;
DCHECK(seed.is_node_index);
const int seed_vehicle = seed.start_end_value.vehicle;
priority_queue.pop();
@@ -1489,7 +1510,7 @@ int GlobalCheapestInsertionFilteredHeuristic::InsertSeedNode(
// Either the vehicle is already used, or the Commit() wasn't successful.
// In both cases, we insert the next StartEndValue from
// start_end_distances_per_node[seed_node] in the priority queue.
AddSeedToQueue(seed_node, &other_start_end_values, sq);
AddSeedNodeToQueue(seed_node, &other_start_end_values, sq);
}
// No seed node was inserted.
return -1;
@@ -2222,19 +2243,140 @@ LocalCheapestInsertionFilteredHeuristic::
: CheapestInsertionFilteredHeuristic(model, std::move(stop_search),
std::move(evaluator), nullptr,
filter_manager),
update_start_end_distances_per_node_(true),
pair_insertion_strategy_(pair_insertion_strategy),
bin_capacities_(bin_capacities) {}
void LocalCheapestInsertionFilteredHeuristic::Initialize() {
// Avoid recomputing if used in a local search operator.
if (update_start_end_distances_per_node_) {
update_start_end_distances_per_node_ = false;
std::vector<int> all_vehicles(model()->vehicles());
std::iota(std::begin(all_vehicles), std::end(all_vehicles), 0);
start_end_distances_per_node_ =
ComputeStartEndDistanceForVehicles(all_vehicles);
// NOTE(user): Keeping the code in a separate function as opposed to
// inlining here, to allow for future additions to this function.
ComputeInsertionOrder();
}
namespace {
// Returns the opposite of the maximum cost between all pickup/delivery nodes of
// the given pair from their "closest" vehicle.
// For a given pickup/delivery, the cost from the closest vehicle is defined as
// min_{v in vehicles}(ArcCost(start[v]->pickup) + ArcCost(delivery->end[v])).
int64_t GetNegMaxDistanceFromVehicles(const RoutingModel& model,
int pair_index) {
const RoutingModel::IndexPair& pickup_deliveries =
model.GetPickupAndDeliveryPairs()[pair_index];
Bitset64<int> vehicle_set(model.vehicles());
for (int v = 0; v < model.vehicles(); ++v) vehicle_set.Set(v);
// Precompute the cost from vehicle starts to every pickup in the pair.
std::vector<std::vector<int64_t>> pickup_costs(model.Size());
for (int64_t pickup : pickup_deliveries.first) {
std::vector<int64_t>& cost_from_start = pickup_costs[pickup];
cost_from_start.resize(model.vehicles(), -1);
ProcessVehicleStartEndCosts(
model, pickup,
[&cost_from_start](int64_t cost, int v) { cost_from_start[v] = cost; },
vehicle_set, /*ignore_start=*/false, /*ignore_end=*/true);
}
// Precompute the cost from every delivery in the pair to vehicle ends.
std::vector<std::vector<int64_t>> delivery_costs(model.Size());
for (int64_t delivery : pickup_deliveries.second) {
std::vector<int64_t>& cost_to_end = delivery_costs[delivery];
cost_to_end.resize(model.vehicles(), -1);
ProcessVehicleStartEndCosts(
model, delivery,
[&cost_to_end](int64_t cost, int v) { cost_to_end[v] = cost; },
vehicle_set, /*ignore_start=*/true, /*ignore_end=*/false);
}
int64_t max_pair_distance = 0;
for (int64_t pickup : pickup_deliveries.first) {
const std::vector<int64_t>& cost_from_start = pickup_costs[pickup];
for (int64_t delivery : pickup_deliveries.second) {
const std::vector<int64_t>& cost_to_end = delivery_costs[delivery];
int64_t closest_vehicle_distance = std::numeric_limits<int64_t>::max();
for (int v = 0; v < model.vehicles(); v++) {
if (cost_from_start[v] < 0 || cost_to_end[v] < 0) {
// Vehicle not in the pickup and/or delivery's vehicle var domain.
continue;
}
closest_vehicle_distance =
std::min(closest_vehicle_distance,
CapAdd(cost_from_start[v], cost_to_end[v]));
}
max_pair_distance = std::max(max_pair_distance, closest_vehicle_distance);
}
}
return CapOpp(max_pair_distance);
}
} // namespace
void LocalCheapestInsertionFilteredHeuristic::ComputeInsertionOrder() {
if (!insertion_order_.empty()) return;
// We consider pairs and single nodes simultaneously, to make sure the most
// critical ones (fewer allowed vehicles and high penalties) get inserted
// first.
// TODO(user): Explore other metrics to evaluate criticality, more directly
// mixing penalties and allowed vehicles (such as a ratio between the two).
const RoutingModel& model = *this->model();
insertion_order_.reserve(model.Size() +
model.GetPickupAndDeliveryPairs().size());
// Iterating on pickup and delivery pairs
const RoutingModel::IndexPairs& index_pairs =
model.GetPickupAndDeliveryPairs();
for (int pair_index = 0; pair_index < index_pairs.size(); ++pair_index) {
uint64_t num_allowed_vehicles = std::numeric_limits<uint64_t>::max();
int64_t pickup_penalty = 0;
for (int64_t pickup : index_pairs[pair_index].first) {
num_allowed_vehicles =
std::min(num_allowed_vehicles, model.VehicleVar(pickup)->Size());
pickup_penalty =
std::max(pickup_penalty, model.UnperformedPenalty(pickup));
}
int64_t delivery_penalty = 0;
for (int64_t delivery : index_pairs[pair_index].second) {
num_allowed_vehicles =
std::min(num_allowed_vehicles, model.VehicleVar(delivery)->Size());
delivery_penalty =
std::max(delivery_penalty, model.UnperformedPenalty(delivery));
}
insertion_order_.push_back(
{num_allowed_vehicles,
CapOpp(CapAdd(pickup_penalty, delivery_penalty)),
{GetNegMaxDistanceFromVehicles(model, pair_index), 0},
false,
pair_index});
}
Bitset64<int> vehicle_set(model.vehicles());
for (int v = 0; v < model.vehicles(); ++v) vehicle_set.Set(v);
for (int node = 0; node < model.Size(); ++node) {
if (model.IsStart(node) || model.IsEnd(node)) continue;
int64_t min_distance = std::numeric_limits<int64_t>::max();
ProcessVehicleStartEndCosts(
model, node,
[&min_distance](int64_t dist, int) {
min_distance = std::min(min_distance, dist);
},
vehicle_set);
const uint64_t num_allowed_vehicles = model.VehicleVar(node)->Size();
const int64_t neg_penalty = CapOpp(model.UnperformedPenalty(node));
insertion_order_.push_back({num_allowed_vehicles,
neg_penalty,
{CapOpp(min_distance), 0},
true,
node});
}
absl::c_sort(insertion_order_, std::greater<Seed>());
absl::c_reverse(insertion_order_);
}
bool LocalCheapestInsertionFilteredHeuristic::InsertPair(
@@ -2450,56 +2592,13 @@ bool LocalCheapestInsertionFilteredHeuristic::BuildSolutionInternal() {
// Marking if we've tried inserting a node.
visited_.assign(model()->Size(), false);
// Iterating on pickup and delivery pairs
// Multitour needs to know if a node is a pickup, delivery, or single.
// TODO(user): node_is_[pickup/delivery] is not actually required as we
// have access to this information through
// model()->IsPickup/Delivery(), so use this as callback wherever necessary
// instead of these 2 vectors.
const RoutingModel::IndexPairs& index_pairs =
model()->GetPickupAndDeliveryPairs();
// Sort pairs according to number of possible vehicles.
struct PairDomainSize {
uint64_t num_allowed_vehicles;
int64_t neg_penalty;
int pair_index;
bool operator<(const PairDomainSize& other) const {
return std::tie(num_allowed_vehicles, neg_penalty, pair_index) <
std::tie(other.num_allowed_vehicles, other.neg_penalty,
other.pair_index);
}
};
std::vector<PairDomainSize> pair_domain_sizes;
absl::flat_hash_set<int> pair_nodes;
for (int pair_index = 0; pair_index < index_pairs.size(); ++pair_index) {
bool pickup_is_contained = false;
uint64_t domain_size = std::numeric_limits<uint64_t>::max();
int64_t pickup_penalty = 0;
for (int64_t pickup : index_pairs[pair_index].first) {
domain_size = std::min(domain_size, model()->VehicleVar(pickup)->Size());
pickup_penalty =
std::max(pickup_penalty, model()->UnperformedPenalty(pickup));
pickup_is_contained |= Contains(pickup);
}
bool delivery_is_contained = false;
int64_t delivery_penalty = 0;
for (int64_t delivery : index_pairs[pair_index].second) {
domain_size =
std::min(domain_size, model()->VehicleVar(delivery)->Size());
delivery_penalty =
std::max(delivery_penalty, model()->UnperformedPenalty(delivery));
delivery_is_contained |= Contains(delivery);
}
if (pickup_is_contained && delivery_is_contained) {
SetIndexPairVisited(index_pairs[pair_index]);
} else if (!pickup_is_contained && !delivery_is_contained) {
pair_domain_sizes.push_back(
{domain_size, CapOpp(CapAdd(pickup_penalty, delivery_penalty)),
pair_index});
pair_nodes.insert(index_pairs[pair_index].first.begin(),
index_pairs[pair_index].first.end());
pair_nodes.insert(index_pairs[pair_index].second.begin(),
index_pairs[pair_index].second.end());
}
}
std::sort(pair_domain_sizes.begin(), pair_domain_sizes.end());
// Multitour needs to know if a node is a pickup, delivery, or single.
std::vector<bool> node_is_pickup, node_is_delivery;
if (pair_insertion_strategy_ ==
RoutingSearchParameters::BEST_PICKUP_DELIVERY_PAIR_MULTITOUR) {
@@ -2526,37 +2625,34 @@ bool LocalCheapestInsertionFilteredHeuristic::BuildSolutionInternal() {
}
}
// TODO(user): A priority queue is not actually needed here as we only use
// the "Seed" values to determine the static order of insertion of the nodes.
// This can be done with a simple sorted container, which could also contain
// the data for pairs in the same place to determine the overall order of
// insertion.
SeedQueue sq(/*prioritize_farthest_nodes=*/true);
InitializeSeedQueue(&start_end_distances_per_node_, &sq);
auto& node_queue = sq.priority_queue;
// Inserting pairs and single nodes simultaneously, to make sure the most
// critical ones (fewer allowed vehicles and high penalties) get inserted
// first.
// TODO(user): Explore other metrics to evaluate criticality, more directly
// mixing penalties and allowed vehicles (such as a ratio between the two).
int pair_domain_index = 0;
while (pair_domain_index < pair_domain_sizes.size() || !node_queue.empty()) {
bool insert_pair = false;
if (node_queue.empty()) {
insert_pair = true;
} else if (pair_domain_index < pair_domain_sizes.size()) {
const PairDomainSize& pair_domain_size =
pair_domain_sizes[pair_domain_index];
const Seed& seed_node = node_queue.top();
insert_pair =
std::tie(pair_domain_size.num_allowed_vehicles,
pair_domain_size.neg_penalty) <=
std::tie(seed_node.num_allowed_vehicles, seed_node.neg_penalty);
std::vector<bool> ignore_pair_index(index_pairs.size(), false);
for (int pair_index = 0; pair_index < index_pairs.size(); ++pair_index) {
bool pickup_contained = false;
for (int64_t pickup : index_pairs[pair_index].first) {
if (Contains(pickup)) {
pickup_contained = true;
break;
}
}
if (insert_pair) {
const auto index_pair =
index_pairs[pair_domain_sizes[pair_domain_index].pair_index];
bool delivery_contained = false;
for (int64_t delivery : index_pairs[pair_index].second) {
if (Contains(delivery)) {
delivery_contained = true;
break;
}
}
ignore_pair_index[pair_index] = pickup_contained || delivery_contained;
if (pickup_contained && delivery_contained) {
SetIndexPairVisited(index_pairs[pair_index]);
}
}
for (const Seed& seed : insertion_order_) {
const int index = seed.index;
if (!seed.is_node_index) {
if (ignore_pair_index[index]) continue;
const auto& index_pair = index_pairs[index];
switch (pair_insertion_strategy_) {
case RoutingSearchParameters::AUTOMATIC:
case RoutingSearchParameters::BEST_PICKUP_DELIVERY_PAIR:
@@ -2576,23 +2672,21 @@ bool LocalCheapestInsertionFilteredHeuristic::BuildSolutionInternal() {
return MakeUnassignedNodesUnperformed() && Evaluate(true).has_value();
}
SetIndexPairVisited(index_pair);
pair_domain_index++;
} else {
const int node = node_queue.top().node;
node_queue.pop();
if (Contains(node) || visited_[node] || pair_nodes.contains(node)) {
if (Contains(index) || visited_[index] || model()->IsPickup(index) ||
model()->IsDelivery(index)) {
continue;
}
for (const NodeInsertion& insertion :
ComputeEvaluatorSortedPositions(node)) {
ComputeEvaluatorSortedPositions(index)) {
if (StopSearch()) {
return MakeUnassignedNodesUnperformed() && Evaluate(true).has_value();
}
InsertBetween(node, insertion.insert_after,
InsertBetween(index, insertion.insert_after,
Value(insertion.insert_after), insertion.vehicle);
if (Evaluate(/*commit=*/true).has_value()) {
if (bin_capacities_) {
bin_capacities_->AddItemToBin(node, insertion.vehicle);
bin_capacities_->AddItemToBin(index, insertion.vehicle);
}
break;
}

View File

@@ -40,6 +40,7 @@
#include "ortools/constraint_solver/constraint_solver.h"
#include "ortools/constraint_solver/constraint_solveri.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/constraint_solver/routing_parameters.pb.h"
#include "ortools/constraint_solver/routing_utils.h"
namespace operations_research {
@@ -343,13 +344,16 @@ class CheapestInsertionFilteredHeuristic : public RoutingFilteredHeuristic {
uint64_t num_allowed_vehicles;
int64_t neg_penalty;
StartEndValue start_end_value;
int node;
/// Indicates whether this Seed corresponds to a pair or a single node.
/// If false, the 'index' is the pair_index, otherwise it's the node index.
bool is_node_index = true;
int index;
bool operator>(const Seed& other) const {
return std::tie(num_allowed_vehicles, neg_penalty, start_end_value,
node) > std::tie(other.num_allowed_vehicles,
other.neg_penalty, other.start_end_value,
other.node);
is_node_index, index) >
std::tie(other.num_allowed_vehicles, other.neg_penalty,
other.start_end_value, other.is_node_index, other.index);
}
};
// clang-format off
@@ -382,10 +386,14 @@ class CheapestInsertionFilteredHeuristic : public RoutingFilteredHeuristic {
SeedQueue* sq);
// clang-format on
/// Creates and returns a Seed corresponding to the given node/pair_index.
Seed CreateSeed(int index, bool is_pair_index, StartEndValue start_end_value);
/// Adds a Seed corresponding to the given 'node' to sq.priority_queue, based
/// on the last entry in its 'start_end_distances' (from which it's deleted).
void AddSeedToQueue(int node, std::vector<StartEndValue>* start_end_distances,
SeedQueue* sq);
void AddSeedNodeToQueue(int node,
std::vector<StartEndValue>* start_end_distances,
SeedQueue* sq);
/// Inserts 'node' just after 'predecessor', and just before 'successor' on
/// the route of 'vehicle', resulting in the following subsequence:
@@ -1072,6 +1080,10 @@ class LocalCheapestInsertionFilteredHeuristic
void Initialize() override;
private:
/// Computes the order of insertion of the node/pairs in the model based on
/// the "Seed" values (number of allowed vehicles, penalty, distance from
/// vehicle start/ends), and stores them in insertion_order_.
void ComputeInsertionOrder();
/// Computes the possible insertion positions of 'node' and sorts them
/// according to the current cost evaluator.
/// 'node' is a variable index corresponding to a node.
@@ -1107,8 +1119,7 @@ class LocalCheapestInsertionFilteredHeuristic
// Sets all nodes of pair alternatives as visited.
void SetIndexPairVisited(const RoutingModel::IndexPair& index_pair);
bool update_start_end_distances_per_node_;
std::vector<std::vector<StartEndValue>> start_end_distances_per_node_;
std::vector<Seed> insertion_order_;
const RoutingSearchParameters::PairInsertionStrategy pair_insertion_strategy_;
InsertionSequenceContainer insertion_container_;
InsertionSequenceGenerator insertion_generator_;

View File

@@ -17,7 +17,6 @@
#include <algorithm>
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <string>
@@ -26,13 +25,16 @@
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/log/check.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "absl/types/span.h"
#include "lpi/lpi.h"
#include "ortools/base/logging.h"
#include "ortools/base/status_macros.h"
#include "ortools/gscip/gscip.pb.h"
@@ -43,14 +45,26 @@
#include "ortools/util/status_macros.h"
#include "scip/cons_linear.h"
#include "scip/cons_quadratic.h"
#include "scip/def.h"
#include "scip/scip.h"
#include "scip/scip_cons.h"
#include "scip/scip_general.h"
#include "scip/scip_message.h"
#include "scip/scip_numerics.h"
#include "scip/scip_param.h"
#include "scip/scip_prob.h"
#include "scip/scip_sol.h"
#include "scip/scip_solve.h"
#include "scip/scip_solvingstats.h"
#include "scip/scip_var.h"
#include "scip/scipdefplugins.h"
#include "scip/type_cons.h"
#include "scip/type_paramset.h"
#include "scip/type_retcode.h"
#include "scip/type_scip.h"
#include "scip/type_set.h"
#include "scip/type_sol.h"
#include "scip/type_stat.h"
#include "scip/type_var.h"
namespace operations_research {
@@ -304,6 +318,18 @@ bool GScip::InterruptSolve() {
return SCIPinterruptSolve(scip_) == SCIP_OKAY;
}
void GScip::InterruptSolveFromCallback(absl::Status error_status) {
CHECK(!error_status.ok());
{
const absl::MutexLock lock(&callback_status_mutex_);
if (!callback_status_.ok()) {
return;
}
callback_status_ = std::move(error_status);
}
InterruptSolve();
}
absl::Status GScip::CleanUp() {
if (scip_ != nullptr) {
for (SCIP_VAR* variable : variables_) {
@@ -832,6 +858,11 @@ absl::StatusOr<GScipHintResult> GScip::SuggestHint(
absl::StatusOr<GScipResult> GScip::Solve(
const GScipParameters& params, absl::string_view legacy_params,
const GScipMessageHandler message_handler) {
if (InErrorState()) {
return absl::InvalidArgumentError(
"GScip is in an error state due to a previous GScip::Solve()");
}
// A four step process:
// 1. Apply parameters.
// 2. Solve the problem.
@@ -919,6 +950,22 @@ absl::StatusOr<GScipResult> GScip::Solve(
" when writing solve stats."));
}
}
absl::Status callback_status;
{
const absl::MutexLock callback_status_lock(&callback_status_mutex_);
callback_status = util::StatusBuilder(callback_status_)
<< "error in a callback that interrupted the solve";
}
if (!callback_status.ok()) {
const absl::Status status = FreeTransform();
if (status.ok()) {
return callback_status;
}
LOG(ERROR) << "GScip::FreeTransform() failed after interrupting "
"the solve due to an error in a callback: "
<< callback_status;
return status;
}
// Step 3: Extract solution information.
// Some outputs are available unconditionally, and some are only ready if at
// least presolve succeeded.
@@ -1070,6 +1117,11 @@ absl::Status GScip::CheckScipFinite(double d) {
return absl::OkStatus();
}
bool GScip::InErrorState() {
const absl::MutexLock lock(&callback_status_mutex_);
return !callback_status_.ok();
}
#undef RETURN_ERROR_UNLESS
} // namespace operations_research

View File

@@ -26,7 +26,9 @@
// but < inf result in an error. Changing the underlying SCIP's infinity is
// not supported.
// * absl::Status and absl::StatusOr are used to propagate SCIP errors (and on
// a best effort basis, also filter out bad input to gSCIP functions).
// a best effort basis, also filter out bad input to gSCIP functions). In
// constraint handlers, we also use absl::Status and absl::StatusOr for
// error propagation and not SCIP_RETCODE.
//
// A note on error propagation and reliability:
// Many methods on SCIP return an error code. Errors can be triggered by
@@ -55,11 +57,13 @@
#include <string>
#include <vector>
#include "absl/base/thread_annotations.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "absl/types/span.h"
#include "ortools/gscip/gscip.pb.h"
#include "ortools/gscip/gscip_message_handler.h" // IWYU pragma: export
@@ -123,7 +127,8 @@ enum class GScipHintResult;
// A thin wrapper around the SCIP solver that provides C++ bindings that are
// idiomatic for Google. Unless callbacks are used, the SCIP stage is always
// PROBLEM.
// PROBLEM. If any GScip function returns an absl::Status error, then the GScip
// object should be considered to be in an error state.
class GScip {
public:
// Create a new GScip (the constructor is private). The default objective
@@ -140,6 +145,7 @@ class GScip {
// The returned StatusOr will contain an error only if an:
// * An underlying function from SCIP fails.
// * There is an I/O error with managing SCIP output.
// * A user-defined callback function fails.
// The above cases are not mutually exclusive. If the problem is infeasible,
// this will be reflected in the value of GScipResult::gscip_output::status.
absl::StatusOr<GScipResult> Solve(
@@ -362,6 +368,19 @@ class GScip {
absl::StatusOr<std::string> DefaultStringParamValue(
const std::string& parameter_name);
// Returns true if GScip is in an error state. Currently only checks if a
// callback has failed, but in the future it may check for other failures.
bool InErrorState();
// Internal use. Used by constraint handlers to propagate user-returned Status
// errors to GSCIP. Interrupts a solve and passes an error status that
// GScip::Solve() must return after SCIP finishes interrupting the solve. Does
// not do anything if previously called with a (non-OK) status (i.e. the first
// status error is returned by SCIP).
//
// CHECK fails if status is OK.
void InterruptSolveFromCallback(absl::Status error_status);
private:
explicit GScip(SCIP* scip);
// Releases SCIP memory.
@@ -386,6 +405,8 @@ class GScip {
SCIP* scip_;
absl::flat_hash_set<SCIP_VAR*> variables_;
absl::flat_hash_set<SCIP_CONS*> constraints_;
absl::Mutex callback_status_mutex_;
absl::Status callback_status_ ABSL_GUARDED_BY(callback_status_mutex_);
};
// Advanced features below

View File

@@ -0,0 +1,61 @@
// Copyright 2010-2022 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/gscip/gscip_callback_result.h"
#include "scip/type_result.h"
namespace operations_research {
SCIP_RESULT ConvertGScipCallbackResult(const GScipCallbackResult result) {
switch (result) {
case GScipCallbackResult::kDidNotRun:
return SCIP_DIDNOTRUN;
case GScipCallbackResult::kDelayed:
return SCIP_DELAYED;
case GScipCallbackResult::kDidNotFind:
return SCIP_DIDNOTFIND;
case GScipCallbackResult::kFeasible:
return SCIP_FEASIBLE;
case GScipCallbackResult::kInfeasible:
return SCIP_INFEASIBLE;
case GScipCallbackResult::kUnbounded:
return SCIP_UNBOUNDED;
case GScipCallbackResult::kCutOff:
return SCIP_CUTOFF;
case GScipCallbackResult::kSeparated:
return SCIP_SEPARATED;
case GScipCallbackResult::kNewRound:
return SCIP_NEWROUND;
case GScipCallbackResult::kReducedDomain:
return SCIP_REDUCEDDOM;
case GScipCallbackResult::kConstraintAdded:
return SCIP_CONSADDED;
case GScipCallbackResult::kConstraintChanged:
return SCIP_CONSCHANGED;
case GScipCallbackResult::kBranched:
return SCIP_BRANCHED;
case GScipCallbackResult::kSolveLp:
return SCIP_SOLVELP;
case GScipCallbackResult::kFoundSolution:
return SCIP_FOUNDSOL;
case GScipCallbackResult::kSuspend:
return SCIP_SUSPENDED;
case GScipCallbackResult::kSuccess:
return SCIP_SUCCESS;
case GScipCallbackResult::kDelayNode:
return SCIP_DELAYNODE;
}
}
} // namespace operations_research

View File

@@ -0,0 +1,53 @@
// Copyright 2010-2022 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef OR_TOOLS_GSCIP_GSCIP_CALLBACK_RESULT_H_
#define OR_TOOLS_GSCIP_GSCIP_CALLBACK_RESULT_H_
#include "scip/type_result.h"
#include "scip/type_retcode.h"
namespace operations_research {
// Equivalent to type_result.h in SCIP.
enum class GScipCallbackResult {
kDidNotRun = 1, // The method was not executed.
kDelayed = 2, // The method was not executed, should be called again later.
kDidNotFind = 3, // The method was executed, but failed finding anything.
kFeasible = 4, // No infeasibility could be found.
kInfeasible = 5, // An infeasibility was detected.
kUnbounded = 6, // An unboundedness was detected.
kCutOff = 7, // The current node is infeasible and can be cut off.
kSeparated = 8, // The method added a cutting plane.
// The method added a cutting plane and a new separation round should
// immediately start.
kNewRound = 9,
kReducedDomain = 10, // The method reduced the domain of a variable.
kConstraintAdded = 11, // The method added a constraint.
kConstraintChanged = 12, // The method changed a constraint.
kBranched = 13, // The method created a branching.
kSolveLp = 14, // The current node's LP must be solved.
kFoundSolution = 15, // The method found a feasible primal solution.
// The method interrupted its execution, but can continue if needed.
kSuspend = 16,
kSuccess = 17, // The method was successfully executed.
// The processing of the branch-and-bound node should stopped and continued
// later.
kDelayNode = 18
};
SCIP_RESULT ConvertGScipCallbackResult(GScipCallbackResult result);
} // namespace operations_research
#endif // OR_TOOLS_GSCIP_GSCIP_CALLBACK_RESULT_H_

View File

@@ -0,0 +1,586 @@
// Copyright 2010-2022 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/gscip/gscip_constraint_handler.h"
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "ortools/base/logging.h"
#include "ortools/base/status_macros.h"
#include "ortools/gscip/gscip.h"
#include "ortools/gscip/gscip_callback_result.h"
#include "ortools/linear_solver/scip_helper_macros.h"
#include "scip/def.h"
#include "scip/pub_cons.h"
#include "scip/pub_message.h"
#include "scip/pub_var.h"
#include "scip/scip_cons.h"
#include "scip/scip_cut.h"
#include "scip/scip_general.h"
#include "scip/scip_lp.h"
#include "scip/scip_prob.h"
#include "scip/scip_sol.h"
#include "scip/scip_solvingstats.h"
#include "scip/scip_tree.h"
#include "scip/scip_var.h"
#include "scip/struct_cons.h"
#include "scip/struct_tree.h" // IWYU pragma: keep
#include "scip/type_cons.h"
#include "scip/type_lp.h"
#include "scip/type_retcode.h"
#include "scip/type_scip.h"
#include "scip/type_set.h"
#include "scip/type_tree.h"
#include "scip/type_var.h"
struct SCIP_ConshdlrData {
std::unique_ptr<operations_research::internal::UntypedGScipConstraintHandler>
gscip_handler;
operations_research::GScip* gscip = nullptr;
};
struct SCIP_ConsData {
void* data;
};
using GScipHandler =
operations_research::internal::UntypedGScipConstraintHandler;
namespace operations_research {
namespace {
// Enum with supported user-implementable callback functions in the SCIP
// constraint handler. Non-user-implementable functions are not included here
// (e.g. CONSFREE). Same order as in type_cons.h.
enum class ConstraintHandlerCallbackType {
kSepaLp, // CONSSEPALP
kSepaSol, // CONSSEPASOL
kEnfoLp, // CONSENFOLP
// Unsupported: kEnfoRelax, // CONSENFORELAX
kEnfoPs, // CONSENFOPS
kConsCheck, // CONSCHECK
// Unsupported: kConsProp, // CONSPROP
// Unsupported: kConsPresol, // CONSPRESOL
// Unsupported: kConsResProp, // CONSRESPROP
kConsLock // CONSLOCK
};
// Returns the "do nothing" callback result. Used to handle the edge case where
// Enfo* callbacks need to return kFeasible instead of kDidNotRun.
GScipCallbackResult DidNotRunCallbackResult(
const ConstraintHandlerCallbackType callback_type) {
// TODO(user): Add kEnfoRelax when we support it.
if (callback_type == ConstraintHandlerCallbackType::kEnfoLp ||
callback_type == ConstraintHandlerCallbackType::kEnfoPs) {
return GScipCallbackResult::kFeasible;
}
return GScipCallbackResult::kDidNotRun;
}
// In callbacks, SCIP requires the first SCIP_RESULT in a priority list be
// returned when multiple results are applicable. This is a unified order of the
// priorities extracted from type_cons.h. The higher the result, the higher
// priority it is.
int ConstraintHandlerResultPriority(
const GScipCallbackResult result,
const ConstraintHandlerCallbackType callback_type) {
// In type_cons.h, callback results are consistently ordered across all
// constraint handler callback methods except that SCIP_SOLVELP (kSolveLp)
// takes higher priority than SCIP_BRANCHED (kBranched) in CONSENFOLP, and the
// reverse is true for CONSENFORELAX and CONSENFOLP.
switch (result) {
case GScipCallbackResult::kUnbounded:
return 14;
case GScipCallbackResult::kCutOff:
return 13;
case GScipCallbackResult::kSuccess:
return 12;
case GScipCallbackResult::kConstraintAdded:
return 11;
case GScipCallbackResult::kReducedDomain:
return 10;
case GScipCallbackResult::kSeparated:
return 9;
case GScipCallbackResult::kBranched:
return callback_type == ConstraintHandlerCallbackType::kEnfoLp ? 7 : 8;
case GScipCallbackResult::kSolveLp:
return callback_type == ConstraintHandlerCallbackType::kEnfoLp ? 8 : 7;
case GScipCallbackResult::kInfeasible:
return 6;
case GScipCallbackResult::kFeasible:
return 5;
case GScipCallbackResult::kNewRound:
return 4;
case GScipCallbackResult::kDidNotFind:
return 3;
case GScipCallbackResult::kDidNotRun:
return 2;
case GScipCallbackResult::kDelayed:
return 1;
case GScipCallbackResult::kDelayNode:
return 0;
default:
// kConstraintChanged, kFoundSolution, and kSuspend are not used in
// constraint handlers.
return -1;
}
}
constexpr GScipCallbackResult kMinPriority = GScipCallbackResult::kDelayNode;
// Calls the callback function over a span of constraints, returning the highest
// priority callback result, along with a SCIP return code.
absl::StatusOr<GScipCallbackResult> ApplyCallback(
absl::Span<SCIP_Cons*> constraints,
std::function<GScipCallbackResult(void*)> callback_function,
const ConstraintHandlerCallbackType callback_type) {
if (constraints.empty()) {
return DidNotRunCallbackResult(callback_type);
}
GScipCallbackResult callback_result = kMinPriority;
for (SCIP_Cons* cons : constraints) {
if (cons == nullptr) {
return absl::InternalError("Constraint handler has null constraint");
}
SCIP_CONSDATA* consdata = SCIPconsGetData(cons);
if (consdata == nullptr || consdata->data == nullptr) {
return absl::InternalError("Constraint handler has null constraint data");
}
const GScipCallbackResult cons_result = callback_function(consdata->data);
if (ConstraintHandlerResultPriority(cons_result, callback_type) >
ConstraintHandlerResultPriority(callback_result, callback_type)) {
callback_result = cons_result;
}
}
return callback_result;
}
// Calls the callback function over all the constraints of a constraint handler,
// prioritizing the ones SCIP deems more useful. Returns the highest priority
// callback result, along with a SCIP return code.
absl::StatusOr<GScipCallbackResult> ApplyCallback(
SCIP_Cons** constraints, const int num_useful_constraints,
const int total_num_constraints,
std::function<GScipCallbackResult(void*)> callback_function,
const ConstraintHandlerCallbackType callback_type) {
absl::Span<SCIP_Cons*> all_constraints =
absl::MakeSpan(constraints, total_num_constraints);
absl::Span<SCIP_Cons*> useful_constraints =
all_constraints.subspan(0, num_useful_constraints);
ASSIGN_OR_RETURN(
const GScipCallbackResult result,
ApplyCallback(useful_constraints, callback_function, callback_type));
// The first num_useful_constraints are the ones that are more likely to be
// violated. If no violation was found, we consider the remaining
// constraints.
if (result == GScipCallbackResult::kDidNotFind ||
result == GScipCallbackResult::kDidNotRun ||
result == GScipCallbackResult::kFeasible) {
absl::Span<SCIP_Cons*> remaining_constraints =
all_constraints.subspan(num_useful_constraints);
ASSIGN_OR_RETURN(
const GScipCallbackResult remaining_result,
ApplyCallback(remaining_constraints, callback_function, callback_type));
if (remaining_result != GScipCallbackResult::kDidNotFind &&
remaining_result != GScipCallbackResult::kDidNotRun &&
remaining_result != GScipCallbackResult::kFeasible) {
return remaining_result;
}
}
return result;
}
GScipCallbackStats GetCallbackStats(SCIP* scip) {
GScipCallbackStats stats;
stats.num_processed_nodes = SCIPgetNNodes(scip);
stats.num_processed_nodes_total = SCIPgetNTotalNodes(scip);
switch (SCIPgetStage(scip)) {
case SCIP_STAGE_INITPRESOLVE:
case SCIP_STAGE_PRESOLVING:
case SCIP_STAGE_EXITPRESOLVE:
case SCIP_STAGE_SOLVING: {
SCIP_NODE* node = SCIPgetCurrentNode(scip);
stats.current_node_id = node == nullptr ? -1 : node->number;
break;
}
default:
stats.current_node_id = stats.num_processed_nodes;
}
return stats;
}
GScipConstraintOptions CallbackLazyConstraintOptions(const bool local,
const bool dynamic) {
GScipConstraintOptions result;
result.initial = true;
result.separate = true;
result.enforce = true;
result.check = true;
result.propagate = true;
result.local = local;
result.modifiable = false;
result.dynamic = dynamic;
result.removable = true;
result.sticking_at_node = false;
result.keep_alive = false;
return result;
}
} // namespace
double GScipConstraintHandlerContext::VariableValue(SCIP_VAR* variable) const {
return SCIPgetSolVal(gscip_->scip(), current_solution_, variable);
}
absl::StatusOr<GScipCallbackResult> GScipConstraintHandlerContext::AddCut(
const GScipLinearRange& range, const std::string& name,
const GScipCutOptions& options) {
SCIP* scip = gscip_->scip();
SCIP_ROW* row = nullptr;
RETURN_IF_SCIP_ERROR(SCIPcreateEmptyRowConshdlr(
scip, &row, current_handler_, name.data(), range.lower_bound,
range.upper_bound, options.local, options.modifiable, options.removable));
RETURN_IF_SCIP_ERROR(SCIPcacheRowExtensions(scip, row));
if (range.coefficients.size() != range.variables.size()) {
return absl::InternalError(
"GScipLinearRange variables and coefficients do not match in size");
}
for (int i = 0; i < range.variables.size(); ++i) {
RETURN_IF_SCIP_ERROR(SCIPaddVarToRow(scip, row, range.variables.at(i),
range.coefficients.at(i)));
}
RETURN_IF_SCIP_ERROR(SCIPflushRowExtensions(scip, row));
SCIP_Bool infeasible;
RETURN_IF_SCIP_ERROR(SCIPaddRow(scip, row, options.force_cut, &infeasible));
RETURN_IF_SCIP_ERROR(SCIPreleaseRow(scip, &row));
return infeasible ? GScipCallbackResult::kCutOff
: GScipCallbackResult::kSeparated;
}
absl::Status GScipConstraintHandlerContext::AddLazyLinearConstraint(
const GScipLinearRange& range, const std::string& name,
const GScipLazyConstraintOptions& options) {
return gscip_
->AddLinearConstraint(
range, name,
CallbackLazyConstraintOptions(options.local, options.dynamic))
.status();
}
double GScipConstraintHandlerContext::LocalVarLb(SCIP_VAR* var) const {
return SCIPvarGetLbLocal(var);
}
double GScipConstraintHandlerContext::LocalVarUb(SCIP_VAR* var) const {
return SCIPvarGetUbLocal(var);
}
double GScipConstraintHandlerContext::GlobalVarLb(SCIP_VAR* var) const {
return SCIPvarGetLbGlobal(var);
}
double GScipConstraintHandlerContext::GlobalVarUb(SCIP_VAR* var) const {
return SCIPvarGetUbGlobal(var);
}
absl::Status GScipConstraintHandlerContext::SetLocalVarLb(SCIP_VAR* var,
double value) {
return SCIP_TO_STATUS(
SCIPchgVarLbNode(gscip_->scip(), /*node=*/nullptr, var, value));
}
absl::Status GScipConstraintHandlerContext::SetLocalVarUb(SCIP_VAR* var,
double value) {
return SCIP_TO_STATUS(
SCIPchgVarUbNode(gscip_->scip(), /*node=*/nullptr, var, value));
}
absl::Status GScipConstraintHandlerContext::SetGlobalVarLb(SCIP_VAR* var,
double value) {
return SCIP_TO_STATUS(SCIPchgVarLbGlobal(gscip_->scip(), var, value));
}
absl::Status GScipConstraintHandlerContext::SetGlobalVarUb(SCIP_VAR* var,
double value) {
return SCIP_TO_STATUS(SCIPchgVarUbGlobal(gscip_->scip(), var, value));
}
} // namespace operations_research
// ///////////////////////////////////////////////////////////////////////////
// SCIP callback implementation
// //////////////////////////////////////////////////////////////////////////
extern "C" {
// Destructor of the constraint handler to free user data (called when SCIP is
// exiting).
static SCIP_DECL_CONSFREE(ConstraintHandlerFreeC) {
if (scip == nullptr) {
SCIPerrorMessage("SCIP not found in SCIP_DECL_CONSFREE");
return SCIP_ERROR;
}
SCIP_CONSHDLRDATA* scip_handler_data = SCIPconshdlrGetData(conshdlr);
if (scip_handler_data == nullptr) {
SCIPerrorMessage("SCIP handler data not found in SCIP_DECL_CONSFREE");
return SCIP_ERROR;
}
delete scip_handler_data;
SCIPconshdlrSetData(conshdlr, nullptr);
return SCIP_OKAY;
}
static SCIP_DECL_CONSDELETE(ConstraintDataDeleteC) {
if (consdata == nullptr || *consdata == nullptr) {
SCIPerrorMessage("SCIP constraint data not found in SCIP_DECL_CONSDELETE");
return SCIP_ERROR;
}
delete *consdata;
cons->consdata = nullptr;
return SCIP_OKAY;
}
static SCIP_DECL_CONSENFOLP(EnforceLpC) {
SCIP_CONSHDLRDATA* scip_handler_data = SCIPconshdlrGetData(conshdlr);
operations_research::GScip* gscip = scip_handler_data->gscip;
operations_research::GScipCallbackStats stats =
operations_research::GetCallbackStats(gscip->scip());
operations_research::GScipConstraintHandlerContext context(gscip, &stats,
conshdlr, nullptr);
const bool solution_known_infeasible = static_cast<bool>(solinfeasible);
GScipHandler* gscip_handler = scip_handler_data->gscip_handler.get();
auto DoEnforceLp = [=](void* constraint_data) {
return gscip_handler->CallEnforceLp(context, constraint_data,
solution_known_infeasible);
};
const absl::StatusOr<operations_research::GScipCallbackResult> gresult =
operations_research::ApplyCallback(
conss, nusefulconss, nconss, DoEnforceLp,
operations_research::ConstraintHandlerCallbackType::kEnfoLp);
if (!gresult.ok()) {
SCIPerrorMessage(gresult.status().ToString().c_str());
return SCIP_ERROR;
}
*result = operations_research::ConvertGScipCallbackResult(*gresult);
return SCIP_OKAY;
}
static SCIP_DECL_CONSENFOPS(EnforcePseudoSolutionC) {
SCIP_CONSHDLRDATA* scip_handler_data = SCIPconshdlrGetData(conshdlr);
operations_research::GScip* gscip = scip_handler_data->gscip;
operations_research::GScipCallbackStats stats =
operations_research::GetCallbackStats(gscip->scip());
operations_research::GScipConstraintHandlerContext context(gscip, &stats,
conshdlr, nullptr);
const bool solution_known_infeasible = static_cast<bool>(solinfeasible);
const bool solution_infeasible_by_objective =
static_cast<bool>(objinfeasible);
GScipHandler* gscip_handler = scip_handler_data->gscip_handler.get();
auto DoEnforcePseudoSolution = [=](void* constraint_data) {
return gscip_handler->CallEnforcePseudoSolution(
context, constraint_data, solution_known_infeasible,
solution_infeasible_by_objective);
};
const absl::StatusOr<operations_research::GScipCallbackResult> gresult =
operations_research::ApplyCallback(
conss, nusefulconss, nconss, DoEnforcePseudoSolution,
operations_research::ConstraintHandlerCallbackType::kEnfoPs);
if (!gresult.ok()) {
SCIPerrorMessage(gresult.status().ToString().c_str());
return SCIP_ERROR;
}
*result = operations_research::ConvertGScipCallbackResult(*gresult);
return SCIP_OKAY;
}
static SCIP_DECL_CONSCHECK(CheckFeasibilityC) {
SCIP_CONSHDLRDATA* scip_handler_data = SCIPconshdlrGetData(conshdlr);
operations_research::GScip* gscip = scip_handler_data->gscip;
operations_research::GScipCallbackStats stats =
operations_research::GetCallbackStats(gscip->scip());
operations_research::GScipConstraintHandlerContext context(gscip, &stats,
conshdlr, sol);
const bool check_integrality = static_cast<bool>(checkintegrality);
const bool check_lp_rows = static_cast<bool>(checklprows);
const bool print_reason = static_cast<bool>(printreason);
const bool complete = static_cast<bool>(completely);
GScipHandler* gscip_handler = scip_handler_data->gscip_handler.get();
auto DoCheckIsFeasible = [=](void* constraint_data) {
return gscip_handler->CallCheckIsFeasible(context, constraint_data,
check_integrality, check_lp_rows,
print_reason, complete);
};
const absl::StatusOr<operations_research::GScipCallbackResult> gresult =
operations_research::ApplyCallback(
conss, nconss, nconss, DoCheckIsFeasible,
operations_research::ConstraintHandlerCallbackType::kEnfoPs);
if (!gresult.ok()) {
SCIPerrorMessage(gresult.status().ToString().c_str());
return SCIP_ERROR;
}
*result = operations_research::ConvertGScipCallbackResult(*gresult);
return SCIP_OKAY;
}
static SCIP_DECL_CONSSEPALP(SeparateLpC) {
SCIP_CONSHDLRDATA* scip_handler_data = SCIPconshdlrGetData(conshdlr);
operations_research::GScip* gscip = scip_handler_data->gscip;
operations_research::GScipCallbackStats stats =
operations_research::GetCallbackStats(gscip->scip());
operations_research::GScipConstraintHandlerContext context(gscip, &stats,
conshdlr, nullptr);
GScipHandler* gscip_handler = scip_handler_data->gscip_handler.get();
auto DoSeparateLp = [=](void* constraint_data) {
return gscip_handler->CallSeparateLp(context, constraint_data);
};
const absl::StatusOr<operations_research::GScipCallbackResult> gresult =
operations_research::ApplyCallback(
conss, nusefulconss, nconss, DoSeparateLp,
operations_research::ConstraintHandlerCallbackType::kSepaLp);
if (!gresult.ok()) {
SCIPerrorMessage(gresult.status().ToString().c_str());
return SCIP_ERROR;
}
*result = operations_research::ConvertGScipCallbackResult(*gresult);
return SCIP_OKAY;
}
static SCIP_DECL_CONSSEPASOL(SeparatePrimalSolutionC) {
SCIP_CONSHDLRDATA* scip_handler_data = SCIPconshdlrGetData(conshdlr);
operations_research::GScip* gscip = scip_handler_data->gscip;
operations_research::GScipCallbackStats stats =
operations_research::GetCallbackStats(gscip->scip());
operations_research::GScipConstraintHandlerContext context(gscip, &stats,
conshdlr, sol);
GScipHandler* gscip_handler = scip_handler_data->gscip_handler.get();
auto DoSeparateSolution = [=](void* constraint_data) {
return gscip_handler->CallSeparateSolution(context, constraint_data);
};
const absl::StatusOr<operations_research::GScipCallbackResult> gresult =
operations_research::ApplyCallback(
conss, nusefulconss, nconss, DoSeparateSolution,
operations_research::ConstraintHandlerCallbackType::kSepaSol);
if (!gresult.ok()) {
SCIPerrorMessage(gresult.status().ToString().c_str());
return SCIP_ERROR;
}
*result = operations_research::ConvertGScipCallbackResult(*gresult);
return SCIP_OKAY;
}
static SCIP_DECL_CONSLOCK(VariableRoundingLockC) {
SCIP_CONSHDLRDATA* scip_handler_data = SCIPconshdlrGetData(conshdlr);
operations_research::GScip* gscip = scip_handler_data->gscip;
GScipHandler* gscip_handler = scip_handler_data->gscip_handler.get();
SCIP_CONSDATA* consdata = SCIPconsGetData(cons);
if (consdata == nullptr) {
return SCIP_ERROR;
}
const bool lock_type_is_model = locktype == SCIP_LOCKTYPE_MODEL;
for (const auto [locked_var, lock_direction] :
gscip_handler->RoundingLock(gscip, consdata->data, lock_type_is_model)) {
int lock_down;
int lock_up;
switch (lock_direction) {
case operations_research::RoundingLockDirection::kUp:
lock_down = nlocksneg;
lock_up = nlockspos;
break;
case operations_research::RoundingLockDirection::kDown:
lock_down = nlockspos;
lock_up = nlocksneg;
break;
case operations_research::RoundingLockDirection::kBoth:
lock_down = nlocksneg + nlockspos;
lock_up = nlocksneg + nlockspos;
break;
}
SCIP_CALL(
SCIPaddVarLocksType(scip, locked_var, locktype, lock_down, lock_up));
}
return SCIP_OKAY;
}
}
namespace operations_research {
namespace internal {
absl::Status RegisterConstraintHandler(
GScip* gscip,
std::unique_ptr<UntypedGScipConstraintHandler> constraint_handler) {
SCIP_CONSHDLR* c_scip_handler;
SCIP_CONSHDLRDATA* scip_handler_data = new SCIP_CONSHDLRDATA;
scip_handler_data->gscip_handler = std::move(constraint_handler);
scip_handler_data->gscip = gscip;
SCIP* scip = gscip->scip();
const GScipConstraintHandlerProperties& properties =
scip_handler_data->gscip_handler->properties();
RETURN_IF_SCIP_ERROR(SCIPincludeConshdlrBasic(
scip, &c_scip_handler, properties.name.c_str(),
properties.description.c_str(), properties.enforcement_priority,
properties.feasibility_check_priority, properties.eager_frequency,
properties.needs_constraints, EnforceLpC, EnforcePseudoSolutionC,
CheckFeasibilityC, VariableRoundingLockC, scip_handler_data));
if (c_scip_handler == nullptr) {
return absl::InternalError("SCIP failed to add constraint handler");
}
RETURN_IF_SCIP_ERROR(SCIPsetConshdlrSepa(
scip, c_scip_handler, SeparateLpC, SeparatePrimalSolutionC,
properties.separation_frequency, properties.separation_priority,
properties.delay_separation));
RETURN_IF_SCIP_ERROR(
SCIPsetConshdlrFree(scip, c_scip_handler, ConstraintHandlerFreeC));
RETURN_IF_SCIP_ERROR(
SCIPsetConshdlrDelete(scip, c_scip_handler, ConstraintDataDeleteC));
return absl::OkStatus();
}
absl::Status AddCallbackConstraint(GScip* gscip, absl::string_view handler_name,
absl::string_view constraint_name,
void* constraint_data,
const GScipConstraintOptions& options) {
if (constraint_data == nullptr) {
return absl::InvalidArgumentError(
"Constraint data missing when adding a constraint handler callback");
}
SCIP* scip = gscip->scip();
SCIP_CONSHDLR* conshdlr = SCIPfindConshdlr(scip, handler_name.data());
if (conshdlr == nullptr) {
return util::InternalErrorBuilder()
<< "Constraint handler " << handler_name
<< " not registered with SCIP. Check if you "
"registered the constraint handler before adding constraints.";
}
SCIP_CONSDATA* consdata = new SCIP_CONSDATA;
consdata->data = constraint_data;
SCIP_CONS* constraint = nullptr;
RETURN_IF_SCIP_ERROR(SCIPcreateCons(
scip, &constraint, constraint_name.data(), conshdlr, consdata,
options.initial, options.separate, options.enforce, options.check,
options.propagate, options.local, options.modifiable, options.dynamic,
options.removable, options.sticking_at_node));
if (constraint == nullptr) {
return absl::InternalError("SCIP failed to create constraint");
}
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, constraint));
RETURN_IF_SCIP_ERROR(SCIPreleaseCons(scip, &constraint));
return absl::OkStatus();
}
} // namespace internal
} // namespace operations_research

View File

@@ -0,0 +1,653 @@
// Copyright 2010-2022 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Provides a safe C++ interface for SCIP constraint handlers, which are
// described at https://www.scipopt.org/doc/html/CONS.php. For instructions to
// write a constraint handler, see the documentation of GScipConstraintHandler.
// Examples can be found in gscip_constraint_handler_test.cc.
#ifndef OR_TOOLS_GSCIP_GSCIP_CONSTRAINT_HANDLER_H_
#define OR_TOOLS_GSCIP_GSCIP_CONSTRAINT_HANDLER_H_
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "ortools/gscip/gscip.h"
#include "ortools/gscip/gscip_callback_result.h"
#include "scip/type_cons.h"
#include "scip/type_sol.h"
#include "scip/type_var.h"
namespace operations_research {
// Properties for the constraint handler. It is recommended to set priorities
// and frequencies manually.
struct GScipConstraintHandlerProperties {
// For members below, the corresponding SCIP constraint handler property name
// is provided. See https://www.scipopt.org/doc/html/CONS.php#CONS_PROPERTIES
// for details.
//
// While it is recommended to set your own parameters, we provide a default
// set of parameters that has the following behavior:
// * Enforcement and feasibility checking is done right after enforcing
// integrality, but before any other constraint handlers. This implies that
// it is only performed on integer solutions by default.
// * Obsolete constraints are revisited every 100 nodes (eager frequency).
// This default follows the most common frequency in SCIP's existing
// constraint handlers.
// * If separation is used, it is run before all constraint handlers and at
// every node. Note however that all separators are always run before any
// constraint handler separation. A user may control separation frequency
// either by changing this parameter or implementing a check in the
// callback.
// Name of the constraint handler. See CONSHDLR_NAME in SCIP
// documentation above.
std::string name;
// Description of the constraint handler. See CONSHDLR_DESC in SCIP
// documentation above.
std::string description;
// Determines the order this constraint class is checked at each LP node. If
// negative, the enforcement is only performed on integer solutions. See
// CONSHDLR_ENFOPRIORITY in the SCIP documentation above. Only relevant if
// enforcement callbacks are implemented.
int enforcement_priority = -1;
// Determines the order this constraint class runs in when testing solution
// feasibility. If negative, the feasibility check is only performed on
// integer solutions. See CONSHDLR_CHECKPRIORITY in the SCIP documentation
// above. Only relevant if check callback is implemented.
int feasibility_check_priority = -1;
// Determines the order the separation from this constraint handler runs in
// the cut loop. Note that separators are run before constraint handlers.
// See CONSHDLR_SEPAPRIORITY in SCIP documentation above. Only relevant if
// separation callbacks are implemented.
int separation_priority = 3000000;
// Frequency for separating cuts. See CONSHDLR_SEPAFREQ in the SCIP
// documentation above. Only relevant if separation callbacks are implemented.
int separation_frequency = 1;
// Determines if this separator be delayed if another separator has already
// found a cut. See CONSHDLR_DELAYSEPA in the SCIP documentation above.
bool delay_separation = false;
// Frequency for using all instead of only the useful constraints in
// separation, propagation, and enforcement. For example, some constraints may
// be aged out by SCIP if they are not relevant for several iterations. See
// CONSHDLR_EAGERFREQ in SCIP documentation above.
int eager_frequency = 100;
// Indicates whether the constraint handler can be skipped if no constraints
// from this handler are present in the model. In most cases, this should be
// true. This should only be false for constraints that are not added
// explicitly as a constraint, such as integrality. See CONSHDLR_NEEDSCONS in
// SCIP documentation above.
bool needs_constraints = true;
};
// Advanced use only. Indicates that if a variable moves in this direction, it
// can cause a constraint violation. `kBoth` is the safest option and always
// valid, but it is the least flexible for SCIP.
enum class RoundingLockDirection { kUp, kDown, kBoth };
// Options passed to SCIP when adding a cut.
struct GScipCutOptions {
// Cut is only valid for the current subtree.
bool local = false;
// Cut is modifiable during node processing (subject to column generation).
bool modifiable = false;
// Cut can be removed from the LP due to aging or cleanup.
bool removable = true;
// Cut is forced to enter the LP.
bool force_cut = false;
};
// Options passed to SCIP when adding a lazy constraint.
struct GScipLazyConstraintOptions {
// Cut is only valid for the current subtree.
bool local = false;
// Constraint is subject to aging.
bool dynamic = false;
};
struct GScipCallbackStats {
// A unique id within a run, assigned consecutively by order of creation. -1
// if no nodes have been created yet, or num_processed_nodes if
// search is over. See SCIPgetCurrentNode().
int64_t current_node_id = 0;
// The number of processed nodes in the current run (i.e. does not include
// nodes before a restart), including the focus node. See SCIPgetNNodes().
int64_t num_processed_nodes = 0;
// The total number of processed nodes in all runs, including the focus node.
// If the solver restarts > 1 time, will be larger than
// num_processed_nodes, otherwise is equal. See SCIPgetNTotalNodes().
int64_t num_processed_nodes_total = 0;
};
// Interface to the callback context and underlying problem. Supports adding
// cuts and lazy constraints, and setting bounds. Prefer to use this context to
// query information instead of a raw SCIP pointer. Passed by value.
// TODO(user): Add support for branching.
class GScipConstraintHandlerContext {
public:
// Construct the context for the given handler. Following SCIP convention, if
// SCIP_SOL is nullptr, then the current solution from the LP is used.
GScipConstraintHandlerContext(GScip* gscip, const GScipCallbackStats* stats,
SCIP_CONSHDLR* current_handler,
SCIP_SOL* current_solution)
: gscip_(gscip),
stats_(stats),
current_handler_(current_handler),
current_solution_(current_solution) {}
GScip* gscip() { return gscip_; }
// Returns the current solution value of a variable. This may be for a given
// solution (e.g. in CONS_SEPASOL) or the current LP/pseudosolution (e.g. in
// CONS_SEPALP). Equivalent to calling SCIPgetSolVal.
double VariableValue(SCIP_VAR* variable) const;
// Adds a cut (row) to the SCIP separation storage.
//
// If this is called and succeeds, the callback result must be the one
// returned or a higher priority result. The result returned is either kCutOff
// (SCIP_CUTOFF) if SCIP determined that the cut results in infeasibility
// based on local bounds, or kSeparated (SCIP_SEPARATED) otherwise.
absl::StatusOr<GScipCallbackResult> AddCut(
const GScipLinearRange& range, const std::string& name,
const GScipCutOptions& options = GScipCutOptions());
// Adds a lazy constraint as a SCIP linear constraint. This is similar to
// adding it as a row (and it would be valid to add a lazy constraint with
// AddCut and proper options), but it is treated as a higher-level object and
// may affect other portions of SCIP such as propagation. This is a thin
// wrapper on GScip::AddLinearConstraint() with different defaults.
//
// If this is called and succeeds, the callback result must be kConsAdded
// (equivalent to SCIP_CONSADDED) or a higher priority result.
absl::Status AddLazyLinearConstraint(
const GScipLinearRange& range, const std::string& name,
const GScipLazyConstraintOptions& options = GScipLazyConstraintOptions());
// The functions below set variable bounds. If they are used to cut off a
// solution, then the callback result must be kReducedDomain
// (SCIP_REDUCEDDOM) or a higher priority result.
absl::Status SetLocalVarLb(SCIP_VAR* var, double value);
absl::Status SetLocalVarUb(SCIP_VAR* var, double value);
absl::Status SetGlobalVarLb(SCIP_VAR* var, double value);
absl::Status SetGlobalVarUb(SCIP_VAR* var, double value);
double LocalVarLb(SCIP_VAR* var) const;
double LocalVarUb(SCIP_VAR* var) const;
double GlobalVarLb(SCIP_VAR* var) const;
double GlobalVarUb(SCIP_VAR* var) const;
const GScipCallbackStats& stats() const { return *stats_; }
private:
GScip* gscip_;
const GScipCallbackStats* stats_;
SCIP_CONSHDLR* current_handler_;
SCIP_SOL* current_solution_;
};
// Constraint handler class. To implement a constraint handler, the user can
// inherit this class and implement the desired callback functions. The
// templated ConstraintData is the equivalent of SCIP's SCIP_CONSHDLRDATA, and
// can hold the data needed for the constraint. To then use it, the function
// Register must be called once, and AddCallbackConstraint must be called for
// each constraint to be added in this constraint handler.
//
// There is a one-to-one mapping between relevant SCIP callback functions and
// the functions in this class; see SCIP documentation for which types of
// callbacks to use. Make sure to follow SCIP's rules (e.g. if implementing
// enforcement, all enforcement and check callbacks must be implemented).
//
// For examples of usage, see gscip_constraint_handler_test.cc.
//
// Implementation details:
//
// * Default implementations: All callback functions have a default
// implementation that returns "did not run" or "feasible" accordingly. For
// rounding lock, the default implementation locks both directions.
//
// * Status errors: If the user returns an absl::Status error, then the solve is
// interrupted via SCIPinterruptSolve(), and the status error is ultimately
// returned by GScip::Solve() after SCIP completes the interruption. The
// callback function returns SCIP_OKAY to SCIP except for internal errors. We
// try to avoid returning SCIP_ERROR in the middle of a callback since SCIP
// might not stay in a fully clean state (e.g. calling SCIPfree might hit an
// assert).
//
// * Constraint priority: SCIP informs the callback which subset of constraints
// are more likely to be violated. The callback is called on those constraints
// first, and if the highest priority result is kDidNotFind, kDidNotRun, or
// kFeasible, it is called for the remaining ones.
//
// Supported SCIP callback functions:
// * SCIP_DECL_CONSENFOLP
// * SCIP_DECL_CONSENFOPS
// * SCIP_DECL_CONSCHECK
// * SCIP_DECL_CONSLOCK
// * SCIP_DECL_CONSSEPALP
// * SCIP_DECL_CONSSEPASOL
//
// Used, but not customizable:
// * SCIP_DECL_CONSFREE
// * SCIP_DECL_CONSINIT
// * SCIP_DECL_CONSDELETE
template <typename ConstraintData>
class GScipConstraintHandler {
public:
// Constructs a constraint handler that will be registered using the given
// properties. It is recommended to set priorities and frequencies manually in
// properties.
explicit GScipConstraintHandler(
const GScipConstraintHandlerProperties& properties)
: properties_(properties) {}
virtual ~GScipConstraintHandler() = default;
const GScipConstraintHandlerProperties& properties() const {
return properties_;
}
// Registers this constraint handler with GScip. If the handler has already
// been registered, returns an error.
absl::Status Register(GScip* gscip);
// Adds a callback constraint to the model. That is, it attaches to the
// constraint handler a constraint for the given constraint data.
absl::Status AddCallbackConstraint(GScip* gscip,
std::string_view constraint_name,
const ConstraintData* constraint_data,
const GScipConstraintOptions& options);
// Callback function called at SCIP's CONSENFOLP. Must check if an LP solution
// at a node is feasible, and if not, resolve the infeasibility if possible by
// branching, reducing variable domains, or separating the solution with a
// cutting plane. If properties_.enforcement_priority < 0, then this only acts
// on integer solutions.
//
// SCIP CONSENFOLP callback arguments:
// * solution_infeasible: solinfeasible in SCIP, indicates if the solution was
// already declared infeasible by a constraint handler.
//
// It is the user's responsibility to return a valid result for CONSENFOLP;
// see SCIP's documentation (e.g. type_cons.h).
virtual absl::StatusOr<GScipCallbackResult> EnforceLp(
GScipConstraintHandlerContext context,
const ConstraintData& constraint_data, bool solution_infeasible);
// Callback function called at SCIP's CONSENFOPS. Must check if a
// pseudosolution is feasible, and if not, resolve the infeasibility if
// possible by branching, reducing variable domains, or adding an additional
// constraint. Separating with a cutting plane is not possible since there is
// no corresponding LP (i.e. kSeparated cannot be returned). If
// properties_.enforcement_priority < 0, then this only acts on integer
// solutions.
//
// SCIP CONSENFOPS callback arguments:
// * solution_infeasible: solinfeasible in SCIP, indicates if the solution was
// already declared infeasible by a constraint handler.
// * objective_infeasible: objinfeasible in SCIP, indicates if the solution is
// infeasible due to violating objective bound.
//
// It is the user's responsibility to return a valid result for CONSENFOPS;
// see SCIP's documentation (e.g. type_cons.h).
virtual absl::StatusOr<GScipCallbackResult> EnforcePseudoSolution(
GScipConstraintHandlerContext context,
const ConstraintData& constraint_data, bool solution_infeasible,
bool objective_infeasible);
// Callback function called at SCIP's CONSCHECK. Must return true if the
// current solution stored in the context satisfies all constraints of the
// constraint handler, or false otherwise. If
// properties_.feasibility_check_priority < 0, then this only acts on integer
// solutions.
//
// SCIP CONSCHECK callback arguments:
// * check_integrality: checkintegrality in SCIP, indicates if integrality
// must be checked. Used to avoid redundant checks in cases where
// integrality is already checked or implicit.
// * check_lp_rows: checklprows in SCIP, indicates if the constraints
// represented by rows in the current LP must be checked. Used to avoid
// redundant checks in cases where row feasibility is already checked or
// implicit.
// * print_reason: printreason in SCIP, indicates if the reason for the
// violation should be printed.
// * check_completely: completely in SCIP, indicates if all violations should
// be checked.
virtual absl::StatusOr<bool> CheckIsFeasible(
GScipConstraintHandlerContext context,
const ConstraintData& constraint_data, bool check_integrality,
bool check_lp_rows, bool print_reason, bool check_completely);
// Callback function called at SCIP's CONSLOCK. Must return, for each
// variable, whether the constraint may be violated by decreasing and/or
// increasing the variable value. It is always safe to claim that both
// directions can violate the constraint, which is the default implementation,
// but it may affect SCIP's capabilities.
//
// SCIP CONSLOCK callback arguments:
// * lock_type_is_model: if locktype == SCIP_LOCKTYPE_MODEL in SCIP. If true,
// this callback is called for model constraints, otherwise it is called for
// conflict constraints.
//
// It is the user's responsibility to return a valid result for CONSLOCK; see
// SCIP's documentation (e.g. type_cons.h).
virtual std::vector<std::pair<SCIP_VAR*, RoundingLockDirection>> RoundingLock(
GScip* gscip, const ConstraintData& constraint_data,
bool lock_type_is_model);
// Callback function called at SCIP's CONSSEPALP. Separates all constraints of
// the constraint handler for LP solutions.
//
// It is the user's responsibility to return a valid result for CONSSEPALP;
// see SCIP's documentation (e.g. type_cons.h).
virtual absl::StatusOr<GScipCallbackResult> SeparateLp(
GScipConstraintHandlerContext context,
const ConstraintData& constraint_data);
// Callback function called at SCIP's CONSSEPASOL. Separates all constraints
// of the constraint handler for solutions that do not come from LP (e.g.
// relaxators and primal heuristics).
//
// It is the user's responsibility to return a valid result for CONSSEPASOL;
// see SCIP's documentation (e.g. type_cons.h).
virtual absl::StatusOr<GScipCallbackResult> SeparateSolution(
GScipConstraintHandlerContext context,
const ConstraintData& constraint_data);
// The functions below wrap each callback function to manage status.
GScipCallbackResult CallEnforceLp(GScipConstraintHandlerContext context,
const ConstraintData& constraint_data,
bool solution_infeasible);
GScipCallbackResult CallEnforcePseudoSolution(
GScipConstraintHandlerContext context,
const ConstraintData& constraint_data, bool solution_infeasible,
bool objective_infeasible);
GScipCallbackResult CallCheckIsFeasible(GScipConstraintHandlerContext context,
const ConstraintData& constraint_data,
bool check_integrality,
bool check_lp_rows, bool print_reason,
bool check_completely);
GScipCallbackResult CallSeparateLp(GScipConstraintHandlerContext context,
const ConstraintData& constraint_data);
GScipCallbackResult CallSeparateSolution(
GScipConstraintHandlerContext context,
const ConstraintData& constraint_data);
private:
// If the result status is not OK, stores the status for GScip to later return
// to the user, and interrupts the solve. Otherwise, returns the result
// itself.
GScipCallbackResult HandleCallbackStatus(
absl::StatusOr<GScipCallbackResult> result,
GScipConstraintHandlerContext context,
GScipCallbackResult default_callback_result);
GScipConstraintHandlerProperties properties_;
};
namespace internal {
// These classes implement a void pointer version of GScipConstraintHandler that
// performs a conversion to the more user-friendly templated implementation with
// ConstraintData. This parent class is needed as an untemplated version that
// SCIP_ConshdlrData can store.
class UntypedGScipConstraintHandler : public GScipConstraintHandler<void*> {
public:
explicit UntypedGScipConstraintHandler(
const GScipConstraintHandlerProperties& properties)
: GScipConstraintHandler<void*>(properties) {}
};
template <typename ConstraintData>
class UntypedGScipConstraintHandlerImpl : public UntypedGScipConstraintHandler {
public:
explicit UntypedGScipConstraintHandlerImpl(
GScipConstraintHandler<ConstraintData>* constraint_handler)
: UntypedGScipConstraintHandler(constraint_handler->properties()),
actual_handler_(constraint_handler) {}
absl::StatusOr<GScipCallbackResult> EnforceLp(
GScipConstraintHandlerContext context, void* const& constraint_data,
bool solution_infeasible) override {
return actual_handler_->EnforceLp(
context, *static_cast<const ConstraintData*>(constraint_data),
solution_infeasible);
}
absl::StatusOr<GScipCallbackResult> EnforcePseudoSolution(
GScipConstraintHandlerContext context, void* const& constraint_data,
bool solution_infeasible, bool objective_infeasible) override {
return actual_handler_->EnforcePseudoSolution(
context, *static_cast<const ConstraintData*>(constraint_data),
solution_infeasible, objective_infeasible);
}
absl::StatusOr<bool> CheckIsFeasible(GScipConstraintHandlerContext context,
void* const& constraint_data,
bool check_integrality,
bool check_lp_rows, bool print_reason,
bool completely) override {
return actual_handler_->CheckIsFeasible(
context, *static_cast<const ConstraintData*>(constraint_data),
check_integrality, check_lp_rows, print_reason, completely);
}
std::vector<std::pair<SCIP_VAR*, RoundingLockDirection>> RoundingLock(
GScip* gscip, void* const& constraint_data,
bool lock_type_is_model) override {
return actual_handler_->RoundingLock(
gscip, *static_cast<const ConstraintData*>(constraint_data),
lock_type_is_model);
}
absl::StatusOr<GScipCallbackResult> SeparateLp(
GScipConstraintHandlerContext context,
void* const& constraint_data) override {
return actual_handler_->SeparateLp(
context, *static_cast<const ConstraintData*>(constraint_data));
}
absl::StatusOr<GScipCallbackResult> SeparateSolution(
GScipConstraintHandlerContext context,
void* const& constraint_data) override {
return actual_handler_->SeparateSolution(
context, *static_cast<const ConstraintData*>(constraint_data));
}
private:
GScipConstraintHandler<ConstraintData>* actual_handler_;
};
absl::Status RegisterConstraintHandler(
GScip* gscip,
std::unique_ptr<UntypedGScipConstraintHandler> constraint_handler);
absl::Status AddCallbackConstraint(GScip* gscip, std::string_view handler_name,
std::string_view constraint_name,
void* constraint_data,
const GScipConstraintOptions& options);
} // namespace internal
// Template implementations.
template <typename ConstraintData>
absl::Status GScipConstraintHandler<ConstraintData>::Register(GScip* gscip) {
return internal::RegisterConstraintHandler(
gscip,
std::make_unique<
internal::UntypedGScipConstraintHandlerImpl<ConstraintData>>(this));
}
template <typename ConstraintData>
absl::Status GScipConstraintHandler<ConstraintData>::AddCallbackConstraint(
GScip* gscip, std::string_view constraint_name,
const ConstraintData* constraint_data,
const GScipConstraintOptions& options) {
return internal::AddCallbackConstraint(
gscip, properties().name, constraint_name,
static_cast<void*>(const_cast<ConstraintData*>(constraint_data)),
options);
}
// Default callback implementations.
template <typename ConstraintData>
absl::StatusOr<GScipCallbackResult>
GScipConstraintHandler<ConstraintData>::EnforceLp(
GScipConstraintHandlerContext /*context*/,
const ConstraintData& /*constraint_data*/, bool /*solution_infeasible*/) {
return GScipCallbackResult::kFeasible;
}
template <typename ConstraintData>
absl::StatusOr<GScipCallbackResult>
GScipConstraintHandler<ConstraintData>::EnforcePseudoSolution(
GScipConstraintHandlerContext /*context*/,
const ConstraintData& /*constraint_data*/, bool /*solution_infeasible*/,
bool /*objective_infeasible*/) {
return GScipCallbackResult::kFeasible;
}
template <typename ConstraintData>
absl::StatusOr<bool> GScipConstraintHandler<ConstraintData>::CheckIsFeasible(
GScipConstraintHandlerContext /*context*/,
const ConstraintData& /*constraint_data*/, bool /*check_integrality*/,
bool /*check_lp_rows*/, bool /*print_reason*/, bool /*check_completely*/) {
return true;
}
template <typename ConstraintData>
std::vector<std::pair<SCIP_VAR*, RoundingLockDirection>>
GScipConstraintHandler<ConstraintData>::RoundingLock(
GScip* gscip, const ConstraintData& /*constraint_data*/,
bool /*lock_type_is_model*/) {
std::vector<std::pair<SCIP_VAR*, RoundingLockDirection>> result;
for (SCIP_VAR* var : gscip->variables()) {
result.push_back({var, RoundingLockDirection::kBoth});
}
return result;
}
template <typename ConstraintData>
absl::StatusOr<GScipCallbackResult>
GScipConstraintHandler<ConstraintData>::SeparateLp(
GScipConstraintHandlerContext /*context*/,
const ConstraintData& /*constraint_data*/) {
return GScipCallbackResult::kDidNotRun;
}
template <typename ConstraintData>
absl::StatusOr<GScipCallbackResult>
GScipConstraintHandler<ConstraintData>::SeparateSolution(
GScipConstraintHandlerContext /*context*/,
const ConstraintData& /*constraint_data*/) {
return GScipCallbackResult::kDidNotRun;
}
// Internal functions to handle status.
template <typename ConstraintData>
GScipCallbackResult
GScipConstraintHandler<ConstraintData>::HandleCallbackStatus(
const absl::StatusOr<GScipCallbackResult> result,
GScipConstraintHandlerContext context,
const GScipCallbackResult default_callback_result) {
if (!result.ok()) {
context.gscip()->InterruptSolveFromCallback(result.status());
return default_callback_result;
}
return result.value();
}
template <typename ConstraintData>
GScipCallbackResult GScipConstraintHandler<ConstraintData>::CallEnforceLp(
GScipConstraintHandlerContext context,
const ConstraintData& constraint_data, bool solution_infeasible) {
return HandleCallbackStatus(
EnforceLp(context, constraint_data, solution_infeasible), context,
GScipCallbackResult::kFeasible);
}
template <typename ConstraintData>
GScipCallbackResult
GScipConstraintHandler<ConstraintData>::CallEnforcePseudoSolution(
GScipConstraintHandlerContext context,
const ConstraintData& constraint_data, bool solution_infeasible,
bool objective_infeasible) {
return HandleCallbackStatus(
EnforcePseudoSolution(context, constraint_data, solution_infeasible,
objective_infeasible),
context, GScipCallbackResult::kFeasible);
}
template <typename ConstraintData>
GScipCallbackResult GScipConstraintHandler<ConstraintData>::CallCheckIsFeasible(
GScipConstraintHandlerContext context,
const ConstraintData& constraint_data, bool check_integrality,
bool check_lp_rows, bool print_reason, bool check_completely) {
const absl::StatusOr<bool> result =
CheckIsFeasible(context, constraint_data, check_integrality,
check_lp_rows, print_reason, check_completely);
if (!result.ok()) {
return HandleCallbackStatus(result.status(), context,
GScipCallbackResult::kFeasible);
} else {
return HandleCallbackStatus(*result ? GScipCallbackResult::kFeasible
: GScipCallbackResult::kInfeasible,
context, GScipCallbackResult::kFeasible);
}
}
template <typename ConstraintData>
GScipCallbackResult GScipConstraintHandler<ConstraintData>::CallSeparateLp(
GScipConstraintHandlerContext context,
const ConstraintData& constraint_data) {
return HandleCallbackStatus(SeparateLp(context, constraint_data), context,
GScipCallbackResult::kDidNotRun);
}
template <typename ConstraintData>
GScipCallbackResult
GScipConstraintHandler<ConstraintData>::CallSeparateSolution(
GScipConstraintHandlerContext context,
const ConstraintData& constraint_data) {
return HandleCallbackStatus(SeparateSolution(context, constraint_data),
context, GScipCallbackResult::kDidNotRun);
}
} // namespace operations_research
#endif // OR_TOOLS_GSCIP_GSCIP_CONSTRAINT_HANDLER_H_

View File

@@ -416,13 +416,8 @@ PYBIND11_MODULE(model_builder_helper, m) {
.def("enable_output", &ModelSolverHelper::EnableOutput, arg("output"))
.def("has_solution", &ModelSolverHelper::has_solution)
.def("has_response", &ModelSolverHelper::has_response)
.def("status",
[](const ModelSolverHelper& solver) {
// TODO(user):
// - Return the true enum when pybind11_protobuf is working.
// - Return the response proto
return static_cast<int>(solver.status());
})
.def("response", &ModelSolverHelper::response)
.def("status", &ModelSolverHelper::status)
.def("status_string", &ModelSolverHelper::status_string)
.def("wall_time", &ModelSolverHelper::wall_time)
.def("user_time", &ModelSolverHelper::user_time)

View File

@@ -383,8 +383,8 @@ class SparseVector {
EntryIndex capacity_;
// Pointers to the first elements of the index and coefficient arrays.
Index* index_;
Fractional* coefficient_;
Index* index_ = nullptr;
Fractional* coefficient_ = nullptr;
// This is here to speed up the CheckNoDuplicates() methods and is mutable
// so we can perform checks on const argument.

View File

@@ -0,0 +1,50 @@
// Copyright 2010-2022 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Check MathOpt available solvers.
#include <iostream>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/math_opt/cpp/math_opt.h"
#include "ortools/math_opt/parameters.pb.h"
#include "ortools/math_opt/core/solver_interface.h"
//#include "ortools/math_opt/cpp/enums.h"
namespace {
struct SolverTypeProtoFormatter {
void operator()(
std::string* const out,
const operations_research::math_opt::SolverTypeProto solver_type) {
out->append(EnumToString(EnumFromProto(solver_type).value()));
}
};
} // namespace
int main(int argc, char* argv[]) {
InitGoogle(argv[0], &argc, &argv, /*remove_flags=*/true);
std::cout <<
absl::StrCat(
"MathOpt is configured to support the following solvers: ",
absl::StrJoin(
operations_research::math_opt::AllSolversRegistry::Instance()->RegisteredSolvers(),
", ",
SolverTypeProtoFormatter())) << std::endl;
return 0;
}

View File

@@ -127,7 +127,7 @@ for idx, (c_block, s, e) in enumerate(
print(f'Removing import {c_block.module}.{c_block.names[0].name}...')
# rewrite absl flag import
elif (isinstance(c_block, ast.ImportFrom) and c_block.module == 'absl'
and c_block.names[0].name =='flags'):
and c_block.names[0].name == 'flags'):
print(f'Rewrite import {c_block.module}.{c_block.names[0].name}...')
full_text += 'from ortools.sat.colab import flags\n'
# Unwrap __main__ function
@@ -156,7 +156,7 @@ for idx, (c_block, s, e) in enumerate(
print('Appending block...')
filtered_lines = lines[s:e]
for i, line in enumerate(filtered_lines):
filtered_lines[i] = line.replace('DEFINE_', 'define_')
filtered_lines[i] = line.replace('DEFINE_', 'define_')
filtered_lines = list(
filter(lambda l: not re.search(r'# \[START .*\]$', l), filtered_lines))
filtered_lines = list(