352 lines
15 KiB
C++
352 lines
15 KiB
C++
// Copyright 2010-2025 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_SAT_SOLUTION_CRUSH_H_
|
|
#define OR_TOOLS_SAT_SOLUTION_CRUSH_H_
|
|
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/container/flat_hash_map.h"
|
|
#include "absl/container/inlined_vector.h"
|
|
#include "absl/log/check.h"
|
|
#include "absl/types/span.h"
|
|
#include "ortools/algorithms/sparse_permutation.h"
|
|
#include "ortools/sat/cp_model_utils.h"
|
|
#include "ortools/util/sorted_interval_list.h"
|
|
|
|
namespace operations_research {
|
|
namespace sat {
|
|
|
|
class SolutionCrush {
|
|
public:
|
|
SolutionCrush() = default;
|
|
|
|
// SolutionCrush is neither copyable nor movable.
|
|
SolutionCrush(const SolutionCrush&) = delete;
|
|
SolutionCrush(SolutionCrush&&) = delete;
|
|
SolutionCrush& operator=(const SolutionCrush&) = delete;
|
|
SolutionCrush& operator=(SolutionCrush&&) = delete;
|
|
|
|
// Resizes the solution to contain `new_size` variables. Does not change the
|
|
// value of existing variables, and does not set any value for the new
|
|
// variables.
|
|
// WARNING: the methods below do not automatically resize the solution. To set
|
|
// the value of a new variable with one of them, call this method first.
|
|
void Resize(int new_size);
|
|
|
|
// Sets the given values in the solution. `solution` must be a map from
|
|
// variable indices to variable values. This must be called only once, before
|
|
// any other method (besides `Resize`).
|
|
// TODO(user): revisit this near the end of the refactoring.
|
|
void LoadSolution(const absl::flat_hash_map<int, int64_t>& solution);
|
|
|
|
// Solution hint accessors.
|
|
bool VarHasSolutionHint(int var) const { return hint_has_value_[var]; }
|
|
int64_t SolutionHint(int var) const { return hint_[var]; }
|
|
bool HintIsLoaded() const { return hint_is_loaded_; }
|
|
absl::Span<const int64_t> SolutionHint() const { return hint_; }
|
|
|
|
// Similar to SolutionHint() but make sure the value is within the given
|
|
// domain.
|
|
int64_t ClampedSolutionHint(int var, const Domain& domain) {
|
|
int64_t value = hint_[var];
|
|
if (value > domain.Max()) {
|
|
value = domain.Max();
|
|
} else if (value < domain.Min()) {
|
|
value = domain.Min();
|
|
}
|
|
return value;
|
|
}
|
|
|
|
bool LiteralSolutionHint(int lit) const {
|
|
const int var = PositiveRef(lit);
|
|
return RefIsPositive(lit) ? hint_[var] : !hint_[var];
|
|
}
|
|
|
|
bool LiteralSolutionHintIs(int lit, bool value) const {
|
|
const int var = PositiveRef(lit);
|
|
return hint_is_loaded_ && hint_has_value_[var] &&
|
|
hint_[var] == (RefIsPositive(lit) ? value : !value);
|
|
}
|
|
|
|
// If the given literal is already hinted, updates its hint.
|
|
// Otherwise do nothing.
|
|
void UpdateLiteralSolutionHint(int lit, bool value) {
|
|
UpdateVarSolutionHint(PositiveRef(lit),
|
|
RefIsPositive(lit) == value ? 1 : 0);
|
|
}
|
|
|
|
std::optional<int64_t> GetRefSolutionHint(int ref) {
|
|
const int var = PositiveRef(ref);
|
|
if (!VarHasSolutionHint(var)) return std::nullopt;
|
|
const int64_t var_hint = SolutionHint(var);
|
|
return RefIsPositive(ref) ? var_hint : -var_hint;
|
|
}
|
|
|
|
std::optional<int64_t> GetExpressionSolutionHint(
|
|
const LinearExpressionProto& expr) {
|
|
int64_t result = expr.offset();
|
|
for (int i = 0; i < expr.vars().size(); ++i) {
|
|
if (expr.coeffs(i) == 0) continue;
|
|
if (!VarHasSolutionHint(expr.vars(i))) return std::nullopt;
|
|
result += expr.coeffs(i) * SolutionHint(expr.vars(i));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void UpdateRefSolutionHint(int ref, int hint) {
|
|
UpdateVarSolutionHint(PositiveRef(ref), RefIsPositive(ref) ? hint : -hint);
|
|
}
|
|
|
|
// If the given variable is already hinted, updates its hint value.
|
|
// Otherwise, do nothing.
|
|
void UpdateVarSolutionHint(int var, int64_t value) {
|
|
DCHECK(RefIsPositive(var));
|
|
if (!hint_is_loaded_) return;
|
|
if (!hint_has_value_[var]) return;
|
|
hint_[var] = value;
|
|
}
|
|
|
|
// Allows to set the hint of a newly created variable.
|
|
void SetNewVariableHint(int var, int64_t value) {
|
|
CHECK(hint_is_loaded_);
|
|
CHECK(!hint_has_value_[var]);
|
|
SetVarHint(var, value);
|
|
}
|
|
|
|
// Sets the value of `literal` to "`var`'s value == `value`". Does nothing if
|
|
// `literal` already has a value.
|
|
void MaybeSetLiteralToValueEncoding(int literal, int var, int64_t value);
|
|
|
|
// Sets the value of `var` to the value of the given linear expression.
|
|
// `linear` must be a list of (variable index, coefficient) pairs.
|
|
void SetVarToLinearExpression(
|
|
int var, absl::Span<const std::pair<int, int64_t>> linear);
|
|
|
|
// Sets the value of `var` to the value of the given linear expression.
|
|
// The two spans must have the same size.
|
|
void SetVarToLinearExpression(int var, absl::Span<const int> vars,
|
|
absl::Span<const int64_t> coeffs);
|
|
|
|
// Sets the value of `var` to 1 if the value of at least one literal in
|
|
// `clause` is equal to 1 (or to 0 otherwise). `clause` must be a list of
|
|
// literal indices.
|
|
void SetVarToClause(int var, absl::Span<const int> clause);
|
|
|
|
// Sets the value of `var` to 1 if the value of all the literals in
|
|
// `conjunction` is 1 (or to 0 otherwise). `conjunction` must be a list of
|
|
// literal indices.
|
|
void SetVarToConjunction(int var, absl::Span<const int> conjunction);
|
|
|
|
// Sets the value of `var` to `value` if the value of the given linear
|
|
// expression is not in `domain` (or does nothing otherwise). `linear` must be
|
|
// a list of (variable index, coefficient) pairs.
|
|
void SetVarToValueIfLinearConstraintViolated(
|
|
int var, int64_t value, absl::Span<const std::pair<int, int64_t>> linear,
|
|
const Domain& domain);
|
|
|
|
// Sets the value of `literal` to `value` if the value of the given linear
|
|
// expression is not in `domain` (or does nothing otherwise). `linear` must be
|
|
// a list of (variable index, coefficient) pairs.
|
|
void SetLiteralToValueIfLinearConstraintViolated(
|
|
int literal, bool value, absl::Span<const std::pair<int, int64_t>> linear,
|
|
const Domain& domain);
|
|
|
|
// Sets the value of `var` to `value` if the value of `condition_lit` is true.
|
|
void SetVarToValueIf(int var, int64_t value, int condition_lit);
|
|
|
|
// Sets the value of `var` to the value `expr` if the value of `condition_lit`
|
|
// is true.
|
|
void SetVarToLinearExpressionIf(int var, const LinearExpressionProto& expr,
|
|
int condition_lit);
|
|
|
|
// Sets the value of `literal` to `value` if the value of `condition_lit` is
|
|
// true.
|
|
void SetLiteralToValueIf(int literal, bool value, int condition_lit);
|
|
|
|
// Sets the value of `var` to `value_if_true` if the value of all the
|
|
// `condition_lits` literals is true, and to `value_if_false` otherwise.
|
|
void SetVarToConditionalValue(int var, absl::Span<const int> condition_lits,
|
|
int64_t value_if_true, int64_t value_if_false);
|
|
|
|
// If one literal does not have a value, and the other one does, sets the
|
|
// value of the latter to the value of the former. If both literals have a
|
|
// value, sets the value of `lit1` to the value of `lit2`.
|
|
void MakeLiteralsEqual(int lit1, int lit2);
|
|
|
|
// Updates the value of the given variable to be within the given domain. The
|
|
// variable is updated to the closest value within the domain. `var` must
|
|
// already have a value.
|
|
void UpdateVarToDomain(int var, const Domain& domain);
|
|
|
|
// Updates the value of the given literals to false if their current values
|
|
// are different (or does nothing otherwise).
|
|
void UpdateLiteralsToFalseIfDifferent(int lit1, int lit2);
|
|
|
|
// Decrements the value of `lit` and increments the value of `dominating_lit`
|
|
// if their values are equal to 1 and 0, respectively.
|
|
void UpdateLiteralsWithDominance(int lit, int dominating_lit);
|
|
|
|
// Decrements the value of `ref` by the minimum amount necessary to be in
|
|
// [`min_value`, `max_value`], and increments the value of one or more
|
|
// `dominating_refs` by the same total amount (or less if it is not possible
|
|
// to exactly match this amount), while staying within their respective
|
|
// domains. The value of a negative reference index r is the opposite of the
|
|
// value of the variable PositiveRef(r).
|
|
//
|
|
// `min_value` must be the minimum value of `ref`'s current domain D, and
|
|
// `max_value` must be in D.
|
|
void UpdateRefsWithDominance(
|
|
int ref, int64_t min_value, int64_t max_value,
|
|
absl::Span<const std::pair<int, Domain>> dominating_refs);
|
|
|
|
// If `var`'s value != `value` finds another variable in the orbit of `var`
|
|
// that can take that value, and permute the solution (using the symmetry
|
|
// `generators`) so that this other variable is at position var. If no other
|
|
// variable can be found, does nothing.
|
|
void MaybeUpdateVarWithSymmetriesToValue(
|
|
int var, bool value,
|
|
absl::Span<const std::unique_ptr<SparsePermutation>> generators);
|
|
|
|
// Sets the value of the i-th variable in `vars` so that the given constraint
|
|
// "dotproduct(coeffs, vars values) = rhs" is satisfied, if all the other
|
|
// variables have a value. i is equal to `var_index` if set. Otherwise it is
|
|
// the index of the variable without a value (if there is not exactly one,
|
|
// this method does nothing).
|
|
void SetVarToLinearConstraintSolution(std::optional<int> var_index,
|
|
absl::Span<const int> vars,
|
|
absl::Span<const int64_t> coeffs,
|
|
int64_t rhs);
|
|
|
|
// Sets the value of the variables in `level_vars` and in `circuit` if all the
|
|
// variables in `reservoir` have a value. This assumes that there is one level
|
|
// variable and one circuit node per element in `reservoir` (in the same
|
|
// order) -- plus one last node representing the start and end of the circuit.
|
|
void SetReservoirCircuitVars(const ReservoirConstraintProto& reservoir,
|
|
int64_t min_level, int64_t max_level,
|
|
absl::Span<const int> level_vars,
|
|
const CircuitConstraintProto& circuit);
|
|
|
|
// Sets the value of `var` to "`time_i`'s value <= `time_j`'s value &&
|
|
// `active_i`'s value == true && `active_j`'s value == true".
|
|
void SetVarToReifiedPrecedenceLiteral(int var,
|
|
const LinearExpressionProto& time_i,
|
|
const LinearExpressionProto& time_j,
|
|
int active_i, int active_j);
|
|
|
|
// Sets the value of `div_var` and `prod_var` if all the variables in the
|
|
// IntMod `ct` constraint have a value, assuming that this "target = x % m"
|
|
// constraint is expanded into "div_var = x / m", "prod_var = div_var * m",
|
|
// and "target = x - prod_var" constraints. If `ct` is not enforced, sets the
|
|
// values of `div_var` and `prod_var` to `default_div_value` and
|
|
// `default_prod_value`, respectively.
|
|
void SetIntModExpandedVars(const ConstraintProto& ct, int div_var,
|
|
int prod_var, int64_t default_div_value,
|
|
int64_t default_prod_value);
|
|
|
|
// Sets the value of as many variables in `prod_vars` as possible (depending
|
|
// on how many expressions in `int_prod` have a value), assuming that the
|
|
// `int_prod` constraint "target = x_0 * x_1 * ... * x_n" is expanded into
|
|
// "prod_var_1 = x_0 * x1",
|
|
// "prod_var_2 = prod_var_1 * x_2",
|
|
// ...,
|
|
// "prod_var_(n-1) = prod_var_(n-2) * x_(n-1)",
|
|
// and "target = prod_var_(n-1) * x_n" constraints.
|
|
void SetIntProdExpandedVars(const LinearArgumentProto& int_prod,
|
|
absl::Span<const int> prod_vars);
|
|
|
|
// Sets the value of `enforcement_lits` if all the variables in `lin_max`
|
|
// have a value, assuming that the `lin_max` constraint "target = max(x_0,
|
|
// x_1, ..., x_(n-1))" is expanded into "enforcement_lits[i] => target <= x_i"
|
|
// constraints, with at most one enforcement value equal to true.
|
|
// `enforcement_lits` must have as many elements as `lin_max`.
|
|
void SetLinMaxExpandedVars(const LinearArgumentProto& lin_max,
|
|
absl::Span<const int> enforcement_lits);
|
|
|
|
// Represents `var` = "automaton is in state `state` at time `time`".
|
|
struct StateVar {
|
|
int var;
|
|
int time;
|
|
int64_t state;
|
|
};
|
|
|
|
// Represents `var` = "automaton takes the transition labeled
|
|
// `transition_label` from state `transition_tail` at time `time`".
|
|
struct TransitionVar {
|
|
int var;
|
|
int time;
|
|
int64_t transition_tail;
|
|
int64_t transition_label;
|
|
};
|
|
|
|
// Sets the value of `state_vars` and `transition_vars` if all the variables
|
|
// in `automaton` have a value.
|
|
void SetAutomatonExpandedVars(
|
|
const AutomatonConstraintProto& automaton,
|
|
absl::Span<const StateVar> state_vars,
|
|
absl::Span<const TransitionVar> transition_vars);
|
|
|
|
// Represents `lit` = "for all i, the value of the i-th column var of a table
|
|
// constraint is in the `var_values`[i] set (unless this set is empty).".
|
|
struct TableRowLiteral {
|
|
int lit;
|
|
// TODO(user): use a vector of (var, value) pairs instead?
|
|
std::vector<absl::InlinedVector<int64_t, 2>> var_values;
|
|
};
|
|
|
|
// Sets the value of the `new_row_lits` literals if all the variables in
|
|
// `column_vars` and `existing_row_lits` have a value. For each `row_lits`,
|
|
// `column_values` must have the same size as `column_vars`. This method
|
|
// assumes that exactly one of `existing_row_lits` and `new_row_lits` must be
|
|
// true.
|
|
void SetTableExpandedVars(absl::Span<const int> column_vars,
|
|
absl::Span<const int> existing_row_lits,
|
|
absl::Span<const TableRowLiteral> new_row_lits);
|
|
|
|
// Sets the value of `bucket_lits` if all the variables in `linear` have a
|
|
// value, assuming that they are expanded from the complex linear constraint
|
|
// (i.e. one whose domain has two or more intervals). The value of
|
|
// `bucket_lits`[i] is set to 1 iff the value of the linear expression is in
|
|
// the i-th interval of the domain.
|
|
void SetLinearWithComplexDomainExpandedVars(
|
|
const LinearConstraintProto& linear, absl::Span<const int> bucket_lits);
|
|
|
|
private:
|
|
void SetVarHint(int var, int64_t value) {
|
|
hint_has_value_[var] = true;
|
|
hint_[var] = value;
|
|
}
|
|
|
|
void SetLiteralHint(int lit, bool value) {
|
|
SetVarHint(PositiveRef(lit), RefIsPositive(lit) == value ? 1 : 0);
|
|
}
|
|
|
|
void PermuteVariables(const SparsePermutation& permutation);
|
|
|
|
// This contains all the hinted value or zero if the hint wasn't specified.
|
|
// We try to maintain this as we create new variable.
|
|
bool model_has_hint_ = false;
|
|
bool hint_is_loaded_ = false;
|
|
std::vector<bool> hint_has_value_;
|
|
std::vector<int64_t> hint_;
|
|
};
|
|
|
|
} // namespace sat
|
|
} // namespace operations_research
|
|
|
|
#endif // OR_TOOLS_SAT_SOLUTION_CRUSH_H_
|