Skip to content

Commit

Permalink
routing: Add penalty_per_inactive parameter to AddDisjunction() Fix #…
Browse files Browse the repository at this point in the history
  • Loading branch information
Mizux committed Jun 6, 2024
1 parent cd5e4bc commit b07e295
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 20 deletions.
30 changes: 23 additions & 7 deletions ortools/constraint_solver/routing.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1757,14 +1757,15 @@ void RoutingModel::TopologicallySortVisitTypes() {

RoutingModel::DisjunctionIndex RoutingModel::AddDisjunction(
const std::vector<int64_t>& indices, int64_t penalty,
int64_t max_cardinality) {
int64_t max_cardinality, bool add_penalty_per_inactive) {
CHECK_GE(max_cardinality, 1);
for (int i = 0; i < indices.size(); ++i) {
CHECK_NE(kUnassigned, indices[i]);
}

const DisjunctionIndex disjunction_index(disjunctions_.size());
disjunctions_.push_back({indices, {penalty, max_cardinality}});
disjunctions_.push_back(
{indices, {penalty, max_cardinality, add_penalty_per_inactive}});
for (const int64_t index : indices) {
index_to_disjunctions_[index].push_back(disjunction_index);
}
Expand Down Expand Up @@ -1830,18 +1831,33 @@ IntVar* RoutingModel::CreateDisjunction(DisjunctionIndex disjunction) {
}
const int64_t max_cardinality =
disjunctions_[disjunction].value.max_cardinality;
IntVar* no_active_var = solver_->MakeBoolVar();

IntVar* number_active_vars = solver_->MakeIntVar(0, max_cardinality);
solver_->AddConstraint(
solver_->MakeSumEquality(disjunction_vars, number_active_vars));
solver_->AddConstraint(solver_->MakeIsDifferentCstCt(
number_active_vars, max_cardinality, no_active_var));

const int64_t penalty = disjunctions_[disjunction].value.penalty;
// if penalty is negative, then disjunction is mandatory.
// i.e. number of active vars must be equal to max cardinality.
if (penalty < 0) {
no_active_var->SetMax(0);
solver_->AddConstraint(
solver_->MakeEquality(number_active_vars, max_cardinality));
return nullptr;
} else {
}

const bool penalty_per_inactive =
disjunctions_[disjunction].value.penalty_per_inactive;
if (max_cardinality == 1 || !penalty_per_inactive) {
IntVar* no_active_var = solver_->MakeBoolVar();
solver_->AddConstraint(solver_->MakeIsDifferentCstCt(
number_active_vars, max_cardinality, no_active_var));
return solver_->MakeProd(no_active_var, penalty)->Var();
} else {
IntVar* number_no_active_vars = solver_->MakeIntVar(0, max_cardinality);
solver_->AddConstraint(solver_->MakeEquality(
number_no_active_vars,
solver_->MakeDifference(max_cardinality, number_active_vars)));
return solver_->MakeProd(number_no_active_vars, penalty)->Var();
}
}

Expand Down
22 changes: 17 additions & 5 deletions ortools/constraint_solver/routing.h
Original file line number Diff line number Diff line change
Expand Up @@ -862,20 +862,26 @@ class RoutingModel {
/// part of a disjunction.
///
/// If a penalty is given, at most 'max_cardinality' of the indices can be
/// active, and if less are active, 'penalty' is payed per inactive index.
/// active, and if less are active, 'penalty' is payed per inactive index if
/// `add_penalty_per_inactive` is enabled.
/// This is equivalent to adding the constraint:
/// p + Sum(i)active[i] == max_cardinality
/// where p is an integer variable, and the following cost to the cost
/// function:
/// p * penalty.
/// where p is an integer variable.
/// If `add_penalty_per_inactive` is disable, 'penalty' is payed once if there
/// are no 'max_cardinality' of the indices actives.
/// This is equivalent to adding the constraint:
/// p == (Sum(i)active[i] != max_cardinality)
/// where p is a boolean variable.
/// The following cost to the cost function: p * penalty.
/// 'penalty' must be positive to make the disjunction optional; a negative
/// penalty will force 'max_cardinality' indices of the disjunction to be
/// performed, and therefore p == 0.
/// Note: passing a vector with a single index will model an optional index
/// with a penalty cost if it is not visited.
DisjunctionIndex AddDisjunction(const std::vector<int64_t>& indices,
int64_t penalty = kNoPenalty,
int64_t max_cardinality = 1);
int64_t max_cardinality = 1,
bool add_penalty_per_inactive = false);
/// Returns the indices of the disjunctions to which an index belongs.
const std::vector<DisjunctionIndex>& GetDisjunctionIndices(
int64_t index) const {
Expand Down Expand Up @@ -912,6 +918,11 @@ class RoutingModel {
int64_t GetDisjunctionMaxCardinality(DisjunctionIndex index) const {
return disjunctions_[index].value.max_cardinality;
}
/// Returns true if the penalty is payed per inactive nodes of the node
/// disjunction of index 'index'.
bool GetDisjunctionPenaltyPerInactive(DisjunctionIndex index) const {
return disjunctions_[index].value.penalty_per_inactive;
}
/// Returns the number of node disjunctions in the model.
int GetNumberOfDisjunctions() const { return disjunctions_.size(); }
/// Returns true if the model contains mandatory disjunctions (ones with
Expand Down Expand Up @@ -2015,6 +2026,7 @@ class RoutingModel {
struct DisjunctionValues {
int64_t penalty;
int64_t max_cardinality;
bool penalty_per_inactive;
};
typedef ValuedNodes<DisjunctionValues> Disjunction;

Expand Down
50 changes: 42 additions & 8 deletions ortools/constraint_solver/routing_filters.cc
Original file line number Diff line number Diff line change
Expand Up @@ -279,20 +279,45 @@ class NodeDisjunctionFilter : public IntVarLocalSearchFilter {
routing_model_.GetDisjunctionNodeIndices(disjunction_index).size() -
routing_model_.GetDisjunctionMaxCardinality(disjunction_index);
// Too many inactive nodes.
if (current_inactive_nodes + inactive_nodes > max_inactive_cardinality) {
const int inactive_nodes_above_limit =
(current_inactive_nodes + inactive_nodes) - max_inactive_cardinality;
if (inactive_nodes_above_limit > 0) {
if (penalty < 0) {
// Nodes are mandatory, i.e. exactly max_cardinality nodes must be
// performed, so the move is not acceptable.
return false;
} else if (current_inactive_nodes <= max_inactive_cardinality) {
}
const bool penalty_per_inactive =
routing_model_.GetDisjunctionPenaltyPerInactive(disjunction_index);
if (current_inactive_nodes <= max_inactive_cardinality) {
// Add penalty if there were not too many inactive nodes before the
// move.
CapAddTo(penalty, &accepted_objective_value_);
if (!penalty_per_inactive) {
CapAddTo(penalty, &accepted_objective_value_);
} else {
CapAddTo(penalty * inactive_nodes_above_limit,
&accepted_objective_value_);
}
} else if (penalty_per_inactive && inactive_nodes > 0) {
// Increase penalty cost if using penalty per inactive node and there
// is new inactive nodes.
CapAddTo(penalty * inactive_nodes, &accepted_objective_value_);
}
} else if (current_inactive_nodes > max_inactive_cardinality) {
// Remove penalty if there were too many inactive nodes before the
// move and there are not too many after the move.
accepted_objective_value_ = CapSub(accepted_objective_value_, penalty);
const bool penalty_per_inactive =
routing_model_.GetDisjunctionPenaltyPerInactive(disjunction_index);
if (!penalty_per_inactive) {
accepted_objective_value_ =
CapSub(accepted_objective_value_, penalty);
} else {
const int current_inactive_nodes_above_limit =
current_inactive_nodes - max_inactive_cardinality;
accepted_objective_value_ =
CapSub(accepted_objective_value_,
penalty * current_inactive_nodes_above_limit);
}
}
}
// Only compare to max as a cost lower bound is computed.
Expand Down Expand Up @@ -328,10 +353,19 @@ class NodeDisjunctionFilter : public IntVarLocalSearchFilter {
const int64_t penalty = routing_model_.GetDisjunctionPenalty(i);
const int max_cardinality =
routing_model_.GetDisjunctionMaxCardinality(i);
if (inactive_per_disjunction_[i] >
disjunction_indices.size() - max_cardinality &&
penalty > 0) {
CapAddTo(penalty, &synchronized_objective_value_);
const bool penalty_per_inactive =
routing_model_.GetDisjunctionPenaltyPerInactive(i);

const int inactive_nodes_above_limit =
inactive_per_disjunction_[i] -
(disjunction_indices.size() - max_cardinality);
if (inactive_nodes_above_limit > 0 && penalty > 0) {
if (!penalty_per_inactive) {
CapAddTo(penalty, &synchronized_objective_value_);
} else {
CapAddTo(penalty * inactive_nodes_above_limit,
&synchronized_objective_value_);
}
}
}
}
Expand Down

0 comments on commit b07e295

Please sign in to comment.