capture CP-SAT log in python/java/.NET; visual tweak of the log; tweak the logging facilities for CP-SAT

This commit is contained in:
Laurent Perron
2021-03-11 21:07:17 +01:00
parent e14760e4b2
commit 4d1b9d227f
26 changed files with 511 additions and 307 deletions

View File

@@ -19,6 +19,7 @@ import com.google.ortools.sat.CpSolverStatus;
import com.google.ortools.sat.IntVar;
import com.google.ortools.sat.LinearExpr;
import com.google.ortools.util.Domain;
import java.util.function.Consumer;
import java.util.Random;
import java.util.logging.Logger;
import org.junit.jupiter.api.Test;
@@ -160,6 +161,32 @@ public class SatSolverTest {
solver.solve(model);
}
@Test
public void testLogCapture() {
// Creates the model.
CpModel model = new CpModel();
// Creates the variables.
int numVals = 3;
IntVar x = model.newIntVar(0, numVals - 1, "x");
IntVar y = model.newIntVar(0, numVals - 1, "y");
// Creates the constraints.
model.addDifferent(x, y);
// Creates a solver and solves the model.
CpSolver solver = new CpSolver();
StringBuilder logBuilder = new StringBuilder();
Consumer<String> appendToLog = (String message) -> logBuilder.append(message).append('\n');
solver.setLogCallback(appendToLog);
solver.getParameters().setLogToStdout(false).setLogSearchProgress(true);
CpSolverStatus status = solver.solve(model);
String log = logBuilder.toString();
if (log.isEmpty()) {
throw new RuntimeException("Log should not be empty");
}
}
private void addEqualities(
final CpModel model, final IntVar[] entities, final Integer[] equalities) {
for (int i = 0; i < (equalities.length - 1); i++) {

View File

@@ -85,8 +85,8 @@ namespace Google.OrTools.Tests
model.Constraints.Add(NewLinear3(0, 1, 2, 1, 2, -1, 0, 100000));
model.Objective = NewMaximize1(2, 1);
// Console.WriteLine("model = " + model.ToString());
CpSolverResponse response = SatHelper.Solve(model);
SolveWrapper solve_wrapper = new SolveWrapper();
CpSolverResponse response = solve_wrapper.Solve(model);
Assert.Equal(CpSolverStatus.Optimal, response.Status);
Assert.Equal(30, response.ObjectiveValue);
Assert.Equal(new long[] { 10, 10, 30 }, response.Solution);
@@ -103,7 +103,8 @@ namespace Google.OrTools.Tests
model.Objective = NewMaximize2(0, 1, 1, -2);
// Console.WriteLine("model = " + model.ToString());
CpSolverResponse response = SatHelper.Solve(model);
SolveWrapper solve_wrapper = new SolveWrapper();
CpSolverResponse response = solve_wrapper.Solve(model);
Assert.Equal(CpSolverStatus.Optimal, response.Status);
Assert.Equal(30, response.ObjectiveValue);
Assert.Equal(new long[] { 10, -10 }, response.Solution);
@@ -393,9 +394,31 @@ namespace Google.OrTools.Tests
]
}";
CpModelProto model = Google.Protobuf.JsonParser.Default.Parse<CpModelProto>(model_str);
CpSolverResponse response = SatHelper.Solve(model);
SolveWrapper solve_wrapper = new SolveWrapper();
CpSolverResponse response = solve_wrapper.Solve(model);
Console.WriteLine(response);
}
[Fact]
public void CaptureLog() {
Console.WriteLine("CaptureLog test");
CpModel model = new CpModel();
IntVar v1 = model.NewIntVar(-10, 10, "v1");
IntVar v2 = model.NewIntVar(-10, 10, "v2");
IntVar v3 = model.NewIntVar(-100000, 100000, "v3");
model.AddLinearConstraint(v1 + v2, -1000000, 100000);
model.AddLinearConstraint(v1 + 2 * v2 - v3, 0, 100000);
model.Maximize(v3);
Assert.Equal(v1.Domain.FlattenedIntervals(), new long[] { -10, 10 });
// Console.WriteLine("model = " + model.Model.ToString());
CpSolver solver = new CpSolver();
solver.StringParameters = "log_search_progress:true log_to_stdout:false";
string log = "";
solver.SetLogCallback(message => log += message + "\n");
solver.Solve(model);
Assert.NotEmpty(log);
Assert.Contains("OPTIMAL", log);
}
}
} // namespace Google.OrTools.Tests

View File

@@ -20,6 +20,20 @@ class SolutionCounter(cp_model.CpSolverSolutionCallback):
return self.__solution_count
class LogToString(object):
"""Record log in a string."""
def __init__(self):
self.__log = ''
def NewMessage(self, message: str):
self.__log += message
self.__log += '\n'
def Log(self):
return self.__log
class CpModelTest(unittest.TestCase):
def testDomainFromValues(self):
@@ -631,6 +645,26 @@ class CpModelTest(unittest.TestCase):
status = solver.Solve(model)
self.assertEqual(status, cp_model.OPTIMAL)
def testCustomLog(self):
print('testCustomLog')
model = cp_model.CpModel()
x = model.NewIntVar(-10, 10, 'x')
y = model.NewIntVar(-10, 10, 'y')
model.AddLinearConstraint(x + 2 * y, 0, 10)
model.Minimize(y)
solver = cp_model.CpSolver()
solver.parameters.log_search_progress = True
solver.parameters.log_to_stdout = False
log_callback = LogToString()
solver.log_callback = log_callback.NewMessage
self.assertEqual(cp_model.OPTIMAL, solver.Solve(model))
self.assertEqual(10, solver.Value(x))
self.assertEqual(-5, solver.Value(y))
print(log_callback.Log())
self.assertRegex(log_callback.Log(), 'Parameters.*log_to_stdout.*')
if __name__ == '__main__':
unittest.main(verbosity=2)

View File

@@ -192,9 +192,9 @@ cc_library(
"//ortools/base",
"//ortools/base:file",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:map_util",
"//ortools/base:stl_util",
"//ortools/base:strong_vector",
"//ortools/base:threadpool",
"//ortools/graph:connected_components",
"//ortools/port:proto_utils",
@@ -243,9 +243,9 @@ cc_library(
"//ortools/base",
"//ortools/base:file",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:map_util",
"//ortools/base:stl_util",
"//ortools/base:strong_vector",
"//ortools/util:saturated_arithmetic",
"//ortools/util:sorted_interval_list",
"@com_google_absl//absl/base:core_headers",
@@ -264,8 +264,8 @@ cc_library(
":cp_model_utils",
"//ortools/base",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:map_util",
"//ortools/base:strong_vector",
"//ortools/util:bitset",
"//ortools/util:sorted_interval_list",
"@com_google_absl//absl/container:flat_hash_map",
@@ -285,9 +285,9 @@ cc_library(
":util",
"//ortools/base",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:map_util",
"//ortools/base:mathutil",
"//ortools/base:strong_vector",
"//ortools/port:proto_utils",
"//ortools/util:affine_relation",
"//ortools/util:bitset",
@@ -404,9 +404,9 @@ cc_library(
"//ortools/base",
"//ortools/base:hash",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:map_util",
"//ortools/base:stl_util",
"//ortools/base:strong_vector",
"//ortools/port:proto_utils",
"//ortools/port:sysinfo",
"//ortools/util:saturated_arithmetic",
@@ -496,8 +496,8 @@ cc_library(
"//ortools/base",
"//ortools/base:hash",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:stl_util",
"//ortools/base:strong_vector",
"//ortools/graph:strongly_connected_components",
"//ortools/util:bitset",
"//ortools/util:random_engine",
@@ -524,9 +524,9 @@ cc_library(
"//ortools/base",
"//ortools/base:adjustable_priority_queue",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:random",
"//ortools/base:stl_util",
"//ortools/base:strong_vector",
"//ortools/graph:strongly_connected_components",
"//ortools/util:logging",
"//ortools/util:time_limit",
@@ -545,8 +545,8 @@ cc_library(
"//ortools/base",
"//ortools/base:hash",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:murmur",
"//ortools/base:strong_vector",
"//ortools/util:bitset",
"//ortools/util:saturated_arithmetic",
"//ortools/util:stats",
@@ -576,9 +576,9 @@ cc_library(
srcs = ["symmetry_util.cc"],
hdrs = ["symmetry_util.h"],
deps = [
"//ortools/base",
"//ortools/algorithms:dynamic_partition",
"//ortools/algorithms:sparse_permutation",
"//ortools/base",
],
)
@@ -591,11 +591,11 @@ cc_library(
":cp_model_utils",
":integer",
":presolve_context",
"//ortools/algorithms:dynamic_partition",
"//ortools/base",
"@com_google_absl//absl/types:span",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/algorithms:dynamic_partition",
"@com_google_absl//absl/types:span",
],
)
@@ -611,10 +611,10 @@ cc_library(
"//ortools/base",
"//ortools/base:hash",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:iterator_adaptors",
"//ortools/base:map_util",
"//ortools/base:stl_util",
"//ortools/base:strong_vector",
"//ortools/graph:iterators",
"//ortools/util:bitset",
"//ortools/util:rev",
@@ -691,8 +691,8 @@ cc_library(
"//ortools/base",
"//ortools/base:cleanup",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:stl_util",
"//ortools/base:strong_vector",
"//ortools/util:bitset",
"@com_google_absl//absl/container:inlined_vector",
],
@@ -708,8 +708,8 @@ cc_library(
":sat_base",
"//ortools/base",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:map_util",
"//ortools/base:strong_vector",
"//ortools/util:bitset",
"@com_google_absl//absl/container:inlined_vector",
],
@@ -888,13 +888,13 @@ cc_library(
"//ortools/base",
"//ortools/base:hash",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:map_util",
"//ortools/base:strong_vector",
"//ortools/graph:io",
"//ortools/graph:util",
"//ortools/port:proto_utils",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/status:status",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings:str_format",
],
)
@@ -942,8 +942,8 @@ cc_library(
":zero_half_cuts",
"//ortools/base",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:map_util",
"//ortools/base:strong_vector",
"//ortools/glop:parameters_cc_proto",
"//ortools/glop:preprocessor",
"//ortools/glop:revised_simplex",
@@ -970,6 +970,7 @@ cc_library(
":model",
":sat_parameters_cc_proto",
"//ortools/glop:revised_simplex",
"//ortools/util:logging",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
],
@@ -1069,8 +1070,8 @@ cc_library(
":sat_parameters_cc_proto",
"//ortools/base",
"//ortools/base:random",
"//ortools/util:random_engine",
"//ortools/base:stl_util",
"//ortools/util:random_engine",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/random",
"@com_google_absl//absl/random:bit_gen_ref",
@@ -1214,8 +1215,8 @@ cc_library(
hdrs = ["feasibility_pump.h"],
visibility = ["//visibility:public"],
deps = [
":cp_model_loader",
":cp_model_cc_proto",
":cp_model_loader",
":integer",
":linear_constraint",
":synchronization",
@@ -1274,7 +1275,7 @@ cc_library(
"//ortools/base:strong_vector",
"@com_google_absl//absl/hash",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/status:status",
"@com_google_absl//absl/status",
"@com_google_absl//absl/types:span",
],
)
@@ -1292,11 +1293,11 @@ cc_library(
"//ortools/base",
"//ortools/base:hash",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:stl_util",
"//ortools/base:strong_vector",
"//ortools/util:time_limit",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/status:status",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
"@com_google_absl//absl/types:span",
@@ -1311,7 +1312,7 @@ cc_library(
":sat_base",
"//ortools/base",
"//ortools/base:file",
"@com_google_absl//absl/status:status",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/types:span",
],
@@ -1331,6 +1332,7 @@ cc_library(
"//ortools/algorithms:sparse_permutation",
"//ortools/base:hash",
"//ortools/base:map_util",
"//ortools/util:logging",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/memory",
"@com_google_protobuf//:protobuf",

View File

@@ -748,7 +748,7 @@ message CpSolverResponse {
// Additional information about how the solution was found.
string solution_info = 20;
// The solve log will be filled if the fill_log_in_response parameter is set
// to true.
// The solve log will be filled if the parameter log_to_response is set to
// true.
string solve_log = 26;
}

View File

@@ -50,6 +50,7 @@
#include "ortools/sat/symmetry.h"
#include "ortools/sat/table.h"
#include "ortools/sat/timetable.h"
#include "ortools/util/logging.h"
#include "ortools/util/saturated_arithmetic.h"
#include "ortools/util/sorted_interval_list.h"
@@ -274,7 +275,6 @@ void CpModelMapping::CreateVariables(const CpModelProto& model_proto,
void CpModelMapping::LoadBooleanSymmetries(const CpModelProto& model_proto,
Model* m) {
const SatParameters& params = *m->GetOrCreate<SatParameters>();
const SymmetryProto symmetry = model_proto.symmetry();
if (symmetry.permutations().empty()) return;
@@ -320,11 +320,9 @@ void CpModelMapping::LoadBooleanSymmetries(const CpModelProto& model_proto,
symmetry_handler->AddSymmetry(std::move(literal_permutation));
}
const bool log_info = VLOG_IS_ON(1) || params.log_search_progress();
if (log_info) {
LOG(INFO) << "Added " << symmetry_handler->num_permutations()
<< " symmetry to the SAT solver.";
}
SOLVER_LOG(m->GetOrCreate<SolverLogger>(), "Added ",
symmetry_handler->num_permutations(),
" symmetry to the SAT solver.");
}
// The logic assumes that the linear constraints have been presolved, so that

View File

@@ -3861,6 +3861,7 @@ void CpModelPresolver::Probe() {
// TODO(user): Maybe do not load slow to propagate constraints? for instance
// we do not use any linear relaxation here.
Model model;
model.Register<SolverLogger>(context_->logger());
// Adapt some of the parameters during this probing phase.
auto* local_param = model.GetOrCreate<SatParameters>();
@@ -4446,6 +4447,7 @@ void CpModelPresolver::TransformIntoMaxCliques() {
}
}
int64 num_literals_before = 0;
const int num_old_cliques = cliques.size();
// We reuse the max-clique code from sat.
@@ -4455,6 +4457,7 @@ void CpModelPresolver::TransformIntoMaxCliques() {
auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
graph->Resize(num_variables);
for (const std::vector<Literal>& clique : cliques) {
num_literals_before += clique.size();
if (!graph->AddAtMostOne(clique)) {
return (void)context_->NotifyThatModelIsUnsat();
}
@@ -4479,9 +4482,11 @@ void CpModelPresolver::TransformIntoMaxCliques() {
}
int num_new_cliques = 0;
int64 num_literals_after = 0;
for (const std::vector<Literal>& clique : cliques) {
if (clique.empty()) continue;
num_new_cliques++;
num_literals_after += clique.size();
ConstraintProto* ct = context_->working_model->add_constraints();
for (const Literal literal : clique) {
if (literal.IsPositive()) {
@@ -4500,8 +4505,12 @@ void CpModelPresolver::TransformIntoMaxCliques() {
context_->UpdateRuleStats("at_most_one: transformed into max clique.");
}
SOLVER_LOG(logger_, "Merged ", num_old_cliques, " into ", num_new_cliques,
" cliques.");
if (num_old_cliques != num_new_cliques ||
num_literals_before != num_literals_after) {
SOLVER_LOG(logger_, "[MaxClique] Merged ", num_old_cliques, "(",
num_literals_before, " literals) into ", num_new_cliques, "(",
num_literals_after, " literals) at_most_ones.");
}
}
bool CpModelPresolver::PresolveOneConstraint(int c) {
@@ -5171,17 +5180,19 @@ void CpModelPresolver::PresolveToFixPoint() {
void LogInfoFromContext(const PresolveContext* context) {
SolverLogger* logger = context->logger();
SOLVER_LOG(logger, "- ", context->NumAffineRelations(),
SOLVER_LOG(logger, "");
SOLVER_LOG(logger, "Presolve summary:");
SOLVER_LOG(logger, " - ", context->NumAffineRelations(),
" affine relations were detected.");
SOLVER_LOG(logger, "- ", context->NumEquivRelations(),
SOLVER_LOG(logger, " - ", context->NumEquivRelations(),
" variable equivalence relations were detected.");
std::map<std::string, int> sorted_rules(context->stats_by_rule_name.begin(),
context->stats_by_rule_name.end());
for (const auto& entry : sorted_rules) {
if (entry.second == 1) {
SOLVER_LOG(logger, "- rule '", entry.first, "' was applied 1 time.");
SOLVER_LOG(logger, " - rule '", entry.first, "' was applied 1 time.");
} else {
SOLVER_LOG(logger, "- rule '", entry.first, "' was applied ",
SOLVER_LOG(logger, " - rule '", entry.first, "' was applied ",
entry.second, " times.");
}
}

View File

@@ -207,10 +207,10 @@ std::string CpModelStats(const CpModelProto& model_proto) {
std::string result;
if (model_proto.has_objective()) {
absl::StrAppend(&result, "Optimization model '", model_proto.name(),
absl::StrAppend(&result, "optimization model '", model_proto.name(),
"':\n");
} else {
absl::StrAppend(&result, "Satisfaction model '", model_proto.name(),
absl::StrAppend(&result, "satisfaction model '", model_proto.name(),
"':\n");
}
@@ -235,7 +235,7 @@ std::string CpModelStats(const CpModelProto& model_proto) {
objective_string, "\n");
if (num_vars_per_domains.size() < 100) {
for (const auto& entry : num_vars_per_domains) {
const std::string temp = absl::StrCat(" - ", entry.second, " in ",
const std::string temp = absl::StrCat(" - ", entry.second, " in ",
entry.first.ToString(), "\n");
absl::StrAppend(&result, Summarize(temp));
}
@@ -249,14 +249,14 @@ std::string CpModelStats(const CpModelProto& model_proto) {
max_complexity = std::max(
max_complexity, static_cast<int64_t>(entry.first.NumIntervals()));
}
absl::StrAppend(&result, " - ", num_vars_per_domains.size(),
absl::StrAppend(&result, " - ", num_vars_per_domains.size(),
" different domains in [", min, ",", max,
"] with a largest complexity of ", max_complexity, ".\n");
}
if (num_constants > 0) {
const std::string temp =
absl::StrCat(" - ", num_constants, " constants in {",
absl::StrCat(" - ", num_constants, " constants in {",
absl::StrJoin(constant_values, ","), "} \n");
absl::StrAppend(&result, Summarize(temp));
}
@@ -288,7 +288,7 @@ std::string CpModelStats(const CpModelProto& model_proto) {
std::string CpSolverResponseStats(const CpSolverResponse& response,
bool has_objective) {
std::string result;
absl::StrAppend(&result, "CpSolverResponse:");
absl::StrAppend(&result, "CpSolverResponse summary:");
absl::StrAppend(&result, "\nstatus: ",
ProtoEnumToString<CpSolverStatus>(response.status()));
@@ -1450,13 +1450,16 @@ void LoadCpModel(const CpModelProto& model_proto,
// Note that we do one last propagation at level zero once all the
// constraints were added.
SOLVER_LOG(model->GetOrCreate<SolverLogger>(),
"Initial num_bool: ", sat_solver->NumVariables());
if (!sat_solver->FinishPropagation()) return unsat();
if (model_proto.has_objective()) {
// Report the initial objective variable bounds.
auto* integer_trail = model->GetOrCreate<IntegerTrail>();
shared_response_manager->UpdateInnerObjectiveBounds(
"init", integer_trail->LowerBound(objective_var),
absl::StrCat(model->Name(), " initial_propagation"),
integer_trail->LowerBound(objective_var),
integer_trail->UpperBound(objective_var));
// Watch improved objective best bounds.
@@ -2861,7 +2864,8 @@ void SolveCpModelParallel(const CpModelProto& model_proto,
for (const auto& subsolver : subsolvers) {
if (!subsolver->name().empty()) names.push_back(subsolver->name());
}
SOLVER_LOG(logger, absl::StrFormat("*** starting Search at %.2fs with %i "
SOLVER_LOG(logger, "");
SOLVER_LOG(logger, absl::StrFormat("Starting Search at %.2fs with %i "
"workers and subsolvers: [ %s ]",
wall_timer->Get(), num_search_workers,
absl::StrJoin(names, ", ")));
@@ -2957,12 +2961,10 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
}
#endif // __PORTABLE_PLATFORM__
const SatParameters& params = *model->GetOrCreate<SatParameters>();
// Enable the logging component.
const SatParameters& params = *model->GetOrCreate<SatParameters>();
const bool log_search = params.log_search_progress() || VLOG_IS_ON(1);
SolverLogger* logger = model->GetOrCreate<SolverLogger>();
bool fill_log_in_response = false;
std::string log_string;
if (log_search) {
@@ -2971,29 +2973,9 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
logger->SetLogPrefix(params.log_prefix());
}
switch (params.log_destination()) {
case SatParameters::USE_STANDARD_LOGGING: {
logger->ForceStandardLogging();
break;
}
case SatParameters::FILL_IN_RESPONSE: {
logger->RelaxStandardLogging();
fill_log_in_response = true;
break;
}
case SatParameters::LOG_TO_BOTH: {
logger->ForceStandardLogging();
fill_log_in_response = true;
break;
}
case SatParameters::NO_DESTINATION: {
logger->RelaxStandardLogging();
fill_log_in_response = false;
break;
}
}
logger->SetLogToStdOut(params.log_to_stdout());
if (fill_log_in_response) {
if (params.log_to_response()) {
const auto append_to_string = [&log_string](const std::string& message) {
absl::StrAppend(&log_string, message, "\n");
};
@@ -3003,6 +2985,8 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
logger->DisableLogging();
}
SOLVER_LOG(logger, "");
SOLVER_LOG(logger, "Starting CP-SAT solver.");
SOLVER_LOG(logger, "Parameters: ", params.ShortDebugString());
if (log_search && params.use_absl_random()) {
model->GetOrCreate<ModelRandomGenerator>()->LogSalt();
@@ -3010,11 +2994,11 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
// Always display the final response stats if requested.
auto display_response_cleanup =
absl::MakeCleanup([&final_response, &model_proto, logger,
fill_log_in_response, &log_string] {
absl::MakeCleanup([&final_response, &model_proto, logger, &log_string] {
SOLVER_LOG(logger, "");
SOLVER_LOG(logger, CpSolverResponseStats(final_response,
model_proto.has_objective()));
if (fill_log_in_response) {
if (!log_string.empty()) {
final_response.set_solve_log(log_string);
}
});
@@ -3029,7 +3013,9 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
return final_response;
}
}
SOLVER_LOG(logger, CpModelStats(model_proto));
SOLVER_LOG(logger, "");
SOLVER_LOG(logger, "Initial ", CpModelStats(model_proto));
// Special case for pure-sat problem.
// TODO(user): improve the normal presolver to do the same thing.
@@ -3073,8 +3059,9 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
}
// Presolve and expansions.
SOLVER_LOG(logger, absl::StrFormat("*** starting model presolve at %.2fs",
wall_timer.Get()));
SOLVER_LOG(logger, "");
SOLVER_LOG(logger,
absl::StrFormat("Starting presolve at %.2fs", wall_timer.Get()));
CpModelProto new_cp_model_proto = model_proto; // Copy.
CpModelProto mapping_proto;
@@ -3112,7 +3099,12 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
final_response.set_status(CpSolverStatus::MODEL_INVALID);
return final_response;
}
SOLVER_LOG(logger, CpModelStats(new_cp_model_proto));
SOLVER_LOG(logger, "");
SOLVER_LOG(logger, "Presolved ", CpModelStats(new_cp_model_proto));
SOLVER_LOG(logger, "");
SOLVER_LOG(logger, "Preloading model.");
if (params.cp_model_presolve()) {
postprocess_solution = [&model_proto, &params, &mapping_proto,
&shared_time_limit, &postsolve_mapping, &wall_timer,
@@ -3169,7 +3161,7 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
context.reset(nullptr);
if (params.symmetry_level() > 1) {
DetectAndAddSymmetryToProto(params, &new_cp_model_proto);
DetectAndAddSymmetryToProto(params, &new_cp_model_proto, logger);
}
SharedResponseManager shared_response_manager(
@@ -3209,7 +3201,7 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
const Domain domain = ReadDomainFromProto(new_cp_model_proto.objective());
if (!domain.IsEmpty()) {
shared_response_manager.UpdateInnerObjectiveBounds(
"initial domain", IntegerValue(domain.Min()),
"initial_domain", IntegerValue(domain.Min()),
IntegerValue(domain.Max()));
}
}
@@ -3270,17 +3262,16 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
&shared_time_limit, &wall_timer, model, logger);
#endif // __PORTABLE_PLATFORM__
} else {
SOLVER_LOG(logger,
absl::StrFormat("*** starting to load the model at %.2fs",
wall_timer.Get()));
SOLVER_LOG(logger, "");
SOLVER_LOG(logger, absl::StrFormat("Starting to load the model at %.2fs",
wall_timer.Get()));
shared_response_manager.SetUpdatePrimalIntegralOnEachChange(true);
LoadCpModel(new_cp_model_proto, &shared_response_manager, model);
shared_response_manager.LoadDebugSolution(model);
SOLVER_LOG(logger,
absl::StrFormat("*** starting sequential search at %.2fs",
wall_timer.Get()));
SOLVER_LOG(logger,
"Initial num_bool: ", model->Get<SatSolver>()->NumVariables());
SOLVER_LOG(logger, "");
SOLVER_LOG(logger, absl::StrFormat("Starting sequential search at %.2fs",
wall_timer.Get()));
if (params.repair_hint()) {
MinimizeL1DistanceWithHint(new_cp_model_proto, &shared_response_manager,
&wall_timer, &shared_time_limit, model);
@@ -3304,6 +3295,7 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
model_proto.assumptions();
}
if (log_search && params.num_search_workers() > 1) {
SOLVER_LOG(logger, "");
shared_response_manager.DisplayImprovementStatistics();
}
return final_response;

View File

@@ -87,8 +87,8 @@ void Append(
// between each other.
template <typename Graph>
std::unique_ptr<Graph> GenerateGraphForSymmetryDetection(
bool log_info, const CpModelProto& problem,
std::vector<int>* initial_equivalence_classes) {
const CpModelProto& problem, std::vector<int>* initial_equivalence_classes,
SolverLogger* logger) {
CHECK(initial_equivalence_classes != nullptr);
const int num_variables = problem.variables_size();
@@ -278,11 +278,11 @@ std::unique_ptr<Graph> GenerateGraphForSymmetryDetection(
// The other cases should be presolved before this is called.
// TODO(user): not 100% true, this happen on rmatr200-p5, Fix.
if (constraint.enforcement_literal_size() != 1) {
if (log_info) {
LOG(INFO) << "BoolAnd with multiple enforcement literal are not "
"supported in symmetry code:"
<< constraint.ShortDebugString();
}
SOLVER_LOG(
logger,
"[Symmetry] BoolAnd with multiple enforcement literal are not "
"supported in symmetry code:",
constraint.ShortDebugString());
return nullptr;
}
@@ -300,10 +300,8 @@ std::unique_ptr<Graph> GenerateGraphForSymmetryDetection(
// TODO(user): support other types of constraints. Or at least, we
// could associate to them an unique node so that their variables can
// appear in no symmetry.
if (log_info) {
VLOG(1) << "Unsupported constraint type "
<< ConstraintCaseName(constraint.constraint_case());
}
VLOG(1) << "Unsupported constraint type "
<< ConstraintCaseName(constraint.constraint_case());
return nullptr;
}
}
@@ -338,7 +336,7 @@ std::unique_ptr<Graph> GenerateGraphForSymmetryDetection(
}
for (int i = 0; i < num_nodes; ++i) {
if (in_degree[i] >= num_nodes || out_degree[i] >= num_nodes) {
if (log_info) LOG(INFO) << "Too many multi-arcs in symmetry code.";
SOLVER_LOG(logger, "[Symmetry] Too many multi-arcs in symmetry code.");
return nullptr;
}
}
@@ -376,8 +374,7 @@ std::unique_ptr<Graph> GenerateGraphForSymmetryDetection(
void FindCpModelSymmetries(
const SatParameters& params, const CpModelProto& problem,
std::vector<std::unique_ptr<SparsePermutation>>* generators,
double deterministic_limit) {
const bool log_info = params.log_search_progress() || VLOG_IS_ON(1);
double deterministic_limit, SolverLogger* logger) {
CHECK(generators != nullptr);
generators->clear();
@@ -385,13 +382,11 @@ void FindCpModelSymmetries(
std::vector<int> equivalence_classes;
std::unique_ptr<Graph> graph(GenerateGraphForSymmetryDetection<Graph>(
log_info, problem, &equivalence_classes));
problem, &equivalence_classes, logger));
if (graph == nullptr) return;
if (log_info) {
LOG(INFO) << "Graph for symmetry has " << graph->num_nodes()
<< " nodes and " << graph->num_arcs() << " arcs.";
}
SOLVER_LOG(logger, "[Symmetry] Graph for symmetry has ", graph->num_nodes(),
" nodes and ", graph->num_arcs(), " arcs.");
if (graph->num_nodes() == 0) return;
GraphSymmetryFinder symmetry_finder(*graph, /*is_undirected=*/false);
@@ -404,8 +399,9 @@ void FindCpModelSymmetries(
// TODO(user): Change the API to not return an error when the time limit is
// reached.
if (log_info && !status.ok()) {
LOG(INFO) << "GraphSymmetryFinder error: " << status.message();
if (!status.ok()) {
SOLVER_LOG(logger,
"[Symmetry] GraphSymmetryFinder error: ", status.message());
}
// Remove from the permutations the part not concerning the variables.
@@ -443,30 +439,28 @@ void FindCpModelSymmetries(
}
generators->resize(num_generators);
average_support_size /= num_generators;
if (log_info) {
LOG(INFO) << "Symmetry computation done. time: "
<< time_limit->GetElapsedTime()
<< " dtime: " << time_limit->GetElapsedDeterministicTime();
if (num_generators > 0) {
LOG(INFO) << "# of generators: " << num_generators;
LOG(INFO) << "Average support size: " << average_support_size;
if (num_duplicate_constraints > 0) {
LOG(INFO) << "The model contains " << num_duplicate_constraints
<< " duplicate constraints !";
}
SOLVER_LOG(logger, "[Symmetry] Symmetry computation done. time: ",
time_limit->GetElapsedTime(),
" dtime: ", time_limit->GetElapsedDeterministicTime());
if (num_generators > 0) {
SOLVER_LOG(logger, "[Symmetry] # of generators: ", num_generators);
SOLVER_LOG(logger,
"[Symmetry] Average support size: ", average_support_size);
if (num_duplicate_constraints > 0) {
SOLVER_LOG(logger, "[Symmetry] The model contains ",
num_duplicate_constraints, " duplicate constraints !");
}
}
}
void DetectAndAddSymmetryToProto(const SatParameters& params,
CpModelProto* proto) {
const bool log_info = params.log_search_progress() || VLOG_IS_ON(1);
CpModelProto* proto, SolverLogger* logger) {
SymmetryProto* symmetry = proto->mutable_symmetry();
symmetry->Clear();
std::vector<std::unique_ptr<SparsePermutation>> generators;
FindCpModelSymmetries(params, *proto, &generators,
/*deterministic_limit=*/1.0);
/*deterministic_limit=*/1.0, logger);
if (generators.empty()) return;
for (const std::unique_ptr<SparsePermutation>& perm : generators) {
@@ -483,10 +477,8 @@ void DetectAndAddSymmetryToProto(const SatParameters& params,
std::vector<std::vector<int>> orbitope = BasicOrbitopeExtraction(generators);
if (orbitope.empty()) return;
if (log_info) {
LOG(INFO) << "Found orbitope of size " << orbitope.size() << " x "
<< orbitope[0].size();
}
SOLVER_LOG(logger, "[Symmetry] Found orbitope of size ", orbitope.size(),
" x ", orbitope[0].size());
DenseMatrixProto* matrix = symmetry->add_orbitopes();
matrix->set_num_rows(orbitope.size());
matrix->set_num_cols(orbitope[0].size());
@@ -499,7 +491,6 @@ void DetectAndAddSymmetryToProto(const SatParameters& params,
bool DetectAndExploitSymmetriesInPresolve(PresolveContext* context) {
const SatParameters& params = context->params();
const bool log_info = params.log_search_progress() || VLOG_IS_ON(1);
const CpModelProto& proto = *context->working_model;
// We need to make sure the proto is up to date before computing symmetries!
@@ -537,7 +528,7 @@ bool DetectAndExploitSymmetriesInPresolve(PresolveContext* context) {
std::vector<std::unique_ptr<SparsePermutation>> generators;
FindCpModelSymmetries(params, proto, &generators,
/*deterministic_limit=*/1.0);
/*deterministic_limit=*/1.0, context->logger());
// Remove temporary affine relation.
context->working_model->mutable_constraints()->DeleteSubrange(
@@ -621,11 +612,10 @@ bool DetectAndExploitSymmetriesInPresolve(PresolveContext* context) {
}
}
if (log_info) {
LOG(INFO) << "Num fixable by intersecting at_most_one with orbits: "
<< can_be_fixed_to_false.size()
<< " largest_orbit: " << max_orbit_size;
}
SOLVER_LOG(
context->logger(),
"[Symmetry] Num fixable by intersecting at_most_one with orbits: ",
can_be_fixed_to_false.size(), " largest_orbit: ", max_orbit_size);
}
// Orbitope approach.
@@ -644,9 +634,9 @@ bool DetectAndExploitSymmetriesInPresolve(PresolveContext* context) {
//
// TODO(user): code the generic approach with orbits and stabilizer.
std::vector<std::vector<int>> orbitope = BasicOrbitopeExtraction(generators);
if (!orbitope.empty() && log_info) {
LOG(INFO) << "Found orbitope of size " << orbitope.size() << " x "
<< orbitope[0].size();
if (!orbitope.empty()) {
SOLVER_LOG(context->logger(), "[Symmetry] Found orbitope of size ",
orbitope.size(), " x ", orbitope[0].size());
}
// Supper simple heuristic to use the orbitope or not.

View File

@@ -22,6 +22,7 @@
#include "ortools/sat/cp_model.pb.h"
#include "ortools/sat/presolve_context.h"
#include "ortools/sat/sat_parameters.pb.h"
#include "ortools/util/logging.h"
namespace operations_research {
namespace sat {
@@ -44,11 +45,11 @@ namespace sat {
void FindCpModelSymmetries(
const SatParameters& params, const CpModelProto& problem,
std::vector<std::unique_ptr<SparsePermutation>>* generators,
double deterministic_limit = std::numeric_limits<double>::infinity());
double deterministic_limit, SolverLogger* logger);
// Detects symmetries and fill the symmetry field.
void DetectAndAddSymmetryToProto(const SatParameters& params,
CpModelProto* proto);
CpModelProto* proto, SolverLogger* logger);
// Basic implementation of some symmetry breaking during presolve.
//

View File

@@ -486,7 +486,7 @@ namespace Google.OrTools.Sat
ct.Proto.IntProd = args;
return ct;
}
public Constraint AddProdEquality(IntVar target, IEnumerable<IntVar> vars)
{
return AddMultiplicationEquality(target, vars);
@@ -693,17 +693,17 @@ namespace Google.OrTools.Sat
public String ModelStats()
{
return SatHelper.ModelStats(model_);
return CpSatHelper.ModelStats(model_);
}
public Boolean ExportToFile(String filename)
{
return SatHelper.WriteModelToFile(model_, filename);
return CpSatHelper.WriteModelToFile(model_, filename);
}
public String Validate()
{
return SatHelper.ValidateModel(model_);
return CpSatHelper.ValidateModel(model_);
}
private int ConvertConstant(long value)

View File

@@ -20,49 +20,59 @@ namespace Google.OrTools.Sat
{
public CpSolverStatus Solve(CpModel model)
{
SolveWrapper solve_wrapper = new SolveWrapper();
if (string_parameters_ != null)
{
response_ = SatHelper.SolveWithStringParameters(model.Model, string_parameters_);
solve_wrapper.SetStringParameters(string_parameters_);
}
else
if (log_callback_ != null)
{
response_ = SatHelper.Solve(model.Model);
solve_wrapper.AddLogCallbackFromClass(log_callback_);
}
response_ = solve_wrapper.Solve(model.Model);
return response_.Status;
}
public CpSolverStatus SolveWithSolutionCallback(CpModel model, SolutionCallback cb)
{
solution_callback_ = cb;
SolveWrapper solve_wrapper = new SolveWrapper();
if (string_parameters_ != null)
{
response_ = SatHelper.SolveWithStringParametersAndSolutionCallback(model.Model, string_parameters_, cb);
solve_wrapper.SetStringParameters(string_parameters_);
}
else
if (log_callback_ != null)
{
response_ = SatHelper.SolveWithStringParametersAndSolutionCallback(model.Model, "", cb);
solve_wrapper.AddLogCallbackFromClass(log_callback_);
}
solve_wrapper.AddSolutionCallback(cb);
response_ = solve_wrapper.Solve(model.Model);
solution_callback_ = null;
return response_.Status;
}
public CpSolverStatus SearchAllSolutions(CpModel model, SolutionCallback cb)
{
solution_callback_ = cb;
SolveWrapper solve_wrapper = new SolveWrapper();
if (string_parameters_ != null)
{
string extra_parameters = " enumerate_all_solutions:true";
response_ = SatHelper.SolveWithStringParametersAndSolutionCallback(
model.Model, string_parameters_ + extra_parameters, cb);
solve_wrapper.SetStringParameters(string_parameters_);
}
else
if (log_callback_ != null)
{
string parameters = "enumerate_all_solutions:true";
response_ = SatHelper.SolveWithStringParametersAndSolutionCallback(model.Model, parameters, cb);
solve_wrapper.AddLogCallbackFromClass(log_callback_);
}
solve_wrapper.AddSolutionCallback(cb);
solve_wrapper.SetEnumerateAllSolutions();
response_ = solve_wrapper.Solve(model.Model);
solution_callback_ = null;
return response_.Status;
}
public String ResponseStats()
{
return SatHelper.SolverResponseStats(response_);
return CpSatHelper.SolverResponseStats(response_);
}
public double ObjectiveValue
@@ -89,6 +99,11 @@ namespace Google.OrTools.Sat
}
}
public void SetLogCallback(StringToVoidDelegate del)
{
log_callback_ = new LogCallbackDelegate(del);
}
public CpSolverResponse Response
{
get {
@@ -192,7 +207,24 @@ namespace Google.OrTools.Sat
private CpModelProto model_;
private CpSolverResponse response_;
SolutionCallback solution_callback_;
LogCallback log_callback_;
string string_parameters_;
}
class LogCallbackDelegate : LogCallback
{
public LogCallbackDelegate(StringToVoidDelegate del)
{
this.delegate_ = del;
}
public override void NewMessage(string message)
{
delegate_(message);
}
private StringToVoidDelegate delegate_;
}
} // namespace Google.OrTools.Sat

View File

@@ -537,7 +537,7 @@ namespace Google.OrTools.Sat
public Domain Domain
{
get {
return SatHelper.VariableDomain(var_);
return CpSatHelper.VariableDomain(var_);
}
}

View File

@@ -51,6 +51,11 @@ PROTO_INPUT(operations_research::sat::IntegerVariableProto,
PROTO2_RETURN(operations_research::sat::CpSolverResponse,
Google.OrTools.Sat.CpSolverResponse);
%pragma(csharp) imclassimports=%{
// Used to wrap log callbacks (std::function<void(const std::string&>)
public delegate void StringToVoidDelegate(string message);
%}
%ignoreall
// SatParameters are proto2, thus not compatible with C# Protobufs.
@@ -58,17 +63,24 @@ PROTO2_RETURN(operations_research::sat::CpSolverResponse,
%unignore operations_research;
%unignore operations_research::sat;
%unignore operations_research::sat::SatHelper;
%unignore operations_research::sat::SatHelper::Solve;
%unignore operations_research::sat::SatHelper::SolveWithStringParameters;
%unignore operations_research::sat::SatHelper::SolveWithStringParametersAndSolutionCallback;
%unignore operations_research::sat::SatHelper::ModelStats;
%unignore operations_research::sat::SatHelper::SolverResponseStats;
%unignore operations_research::sat::SatHelper::ValidateModel;
%unignore operations_research::sat::SatHelper::VariableDomain;
%unignore operations_research::sat::SatHelper::WriteModelToFile;
%typemap(csimports) operations_research::sat::SatHelper %{
// Wrap the SolveWrapper class.
%unignore operations_research::sat::SolveWrapper;
%unignore operations_research::sat::SolveWrapper::SetStringParameters;
%unignore operations_research::sat::SolveWrapper::AddSolutionCallback;
%unignore operations_research::sat::SolveWrapper::AddLogCallbackFromClass;
%unignore operations_research::sat::SolveWrapper::SetEnumerateAllSolutions;
%unignore operations_research::sat::SolveWrapper::Solve;
// Wrap the CpSatHelper class.
%unignore operations_research::sat::CpSatHelper;
%unignore operations_research::sat::CpSatHelper::ModelStats;
%unignore operations_research::sat::CpSatHelper::SolverResponseStats;
%unignore operations_research::sat::CpSatHelper::ValidateModel;
%unignore operations_research::sat::CpSatHelper::VariableDomain;
%unignore operations_research::sat::CpSatHelper::WriteModelToFile;
%typemap(csimports) operations_research::sat::CpSatHelper %{
using Google.OrTools.Util;
%}
@@ -103,6 +115,11 @@ using Google.OrTools.Util;
%unignore operations_research::sat::SolutionCallback::WallTime;
%feature("nodirector") operations_research::sat::SolutionCallback::WallTime;
%feature("director") operations_research::sat::LogCallback;
%unignore operations_research::sat::LogCallback;
%unignore operations_research::sat::LogCallback::~LogCallback;
%unignore operations_research::sat::LogCallback::NewMessage;
%include "ortools/sat/swig_helper.h"
%unignoreall

View File

@@ -24,6 +24,18 @@
#include "ortools/sat/sat_parameters.pb.h"
#include "ortools/sat/swig_helper.h"
#include "ortools/util/sorted_interval_list.h"
/* Global JNI reference deleter. Instantiate it via std::make_shared<> */
class GlobalRefGuard {
JNIEnv *jenv_;
jobject jref_;
// non-copyable
GlobalRefGuard(const GlobalRefGuard &) = delete;
GlobalRefGuard &operator=(const GlobalRefGuard &) = delete;
public:
GlobalRefGuard(JNIEnv *jenv, jobject jref): jenv_(jenv), jref_(jref) {}
~GlobalRefGuard() { jenv_->DeleteGlobalRef(jref_); }
};
%}
%module(directors="1") operations_research_sat
@@ -48,25 +60,51 @@ PROTO_INPUT(operations_research::sat::CpSolverResponse,
PROTO2_RETURN(operations_research::sat::CpSolverResponse,
com.google.ortools.sat.CpSolverResponse);
%typemap(in) std::function<void(const std::string&)> %{
jclass $input_object_class = jenv->GetObjectClass($input);
if (nullptr == $input_object_class) return $null;
jmethodID $input_method_id = jenv->GetMethodID(
$input_object_class, "accept", "(Ljava/lang/Object;)V");
assert($input_method_id != nullptr);
// $input will be deleted once this function return.
jobject $input_object = jenv->NewGlobalRef($input);
// Global JNI reference deleter
auto $input_guard = std::make_shared<GlobalRefGuard>(jenv, $input_object);
$1 = [jenv, $input_object, $input_method_id, $input_guard](
const std::string& message) -> void {
return jenv->CallVoidMethod($input_object, $input_method_id,
(jenv)->NewStringUTF(message.c_str()));
};
%}
%typemap(jni) std::function<void(const std::string&)> "jobject" // Type used in the JNI C.
%typemap(jtype) std::function<void(const std::string&)> "java.util.function.Consumer<String>" // Type used in the JNI.java.
%typemap(jstype) std::function<void(const std::string&)> "java.util.function.Consumer<String>" // Type used in the Proxy class.
%typemap(javain) std::function<void(const std::string&)> "$javainput" // passing the Callback to JNI java class.
%ignoreall
%unignore operations_research;
%unignore operations_research::sat;
// Wrap the relevant part of the SatHelper.
%unignore operations_research::sat::SatHelper;
%rename (solve) operations_research::sat::SatHelper::Solve;
%rename (solveWithParameters) operations_research::sat::SatHelper::SolveWithParameters;
%rename (solveWithParametersAndSolutionCallback) operations_research::sat::SatHelper::SolveWithParametersAndSolutionCallback;
%rename (modelStats) operations_research::sat::SatHelper::ModelStats;
%rename (solverResponseStats) operations_research::sat::SatHelper::SolverResponseStats;
%rename (validateModel) operations_research::sat::SatHelper::ValidateModel;
%rename (variableDomain) operations_research::sat::SatHelper::VariableDomain;
%rename (writeModelToFile) operations_research::sat::SatHelper::WriteModelToFile;
// Wrap the SolveWrapper class.
%unignore operations_research::sat::SolveWrapper;
%rename (setParameters) operations_research::sat::SolveWrapper::SetParameters;
%rename (addSolutionCallback) operations_research::sat::SolveWrapper::AddSolutionCallback;
%rename (addLogCallback) operations_research::sat::SolveWrapper::AddLogCallback;
%rename (setEnumerateAllSolutions) operations_research::sat::SolveWrapper::SetEnumerateAllSolutions;
%rename (solve) operations_research::sat::SolveWrapper::Solve;
%typemap(javaimports) operations_research::sat::SatHelper %{
import com.google.ortools.util.Domain;
%}
// Wrap the relevant part of the CpSatHelper.
%unignore operations_research::sat::CpSatHelper;
%rename (modelStats) operations_research::sat::CpSatHelper::ModelStats;
%rename (solverResponseStats) operations_research::sat::CpSatHelper::SolverResponseStats;
%rename (validateModel) operations_research::sat::CpSatHelper::ValidateModel;
%rename (variableDomain) operations_research::sat::CpSatHelper::VariableDomain;
%rename (writeModelToFile) operations_research::sat::CpSatHelper::WriteModelToFile;
// We use directors for the solution callback.
%feature("director") operations_research::sat::SolutionCallback;
@@ -87,6 +125,10 @@ import com.google.ortools.util.Domain;
%rename (userTime) operations_research::sat::SolutionCallback::UserTime;
%rename (wallTime) operations_research::sat::SolutionCallback::WallTime;
%typemap(javaimports) operations_research::sat::CpSatHelper %{
import com.google.ortools.util.Domain;
%}
%include "ortools/sat/swig_helper.h"
%unignoreall

View File

@@ -56,17 +56,16 @@ LinearConstraintManager::~LinearConstraintManager() {
if (num_coeff_strenghtening_ > 0) {
VLOG(2) << "num_coeff_strenghtening: " << num_coeff_strenghtening_;
}
if (VLOG_IS_ON(1) && sat_parameters_.log_search_progress() && num_cuts_ > 0) {
VLOG(1) << "Total cuts added: " << num_cuts_ << " (out of "
<< num_add_cut_calls_ << " calls) worker: '" << model_->Name()
<< "'";
VLOG(1) << " - num simplifications: " << num_simplifications_;
if (logger_->LoggingIsEnabled() && num_cuts_ > 0) {
SOLVER_LOG(logger_, "Total cuts added: ", num_cuts_, " (out of ",
num_add_cut_calls_, " calls) worker: '", model_->Name(), "'");
SOLVER_LOG(logger_, " - num simplifications: ", num_simplifications_);
for (const auto& entry : type_to_num_cuts_) {
if (entry.second == 1) {
VLOG(1) << " - added 1 cut of type '" << entry.first << "'.";
SOLVER_LOG(logger_, " - added 1 cut of type '", entry.first, "'.");
} else {
VLOG(1) << " - added " << entry.second << " cuts of type '"
<< entry.first << "'.";
SOLVER_LOG(logger_, " - added ", entry.second, " cuts of type '",
entry.first, "'.");
}
}
}
@@ -228,7 +227,7 @@ bool LinearConstraintManager::AddCut(
// them undeletable.
constraint_infos_[ct_index].is_deletable = true;
VLOG(2) << "Cut '" << type_name << "'"
VLOG(1) << "Cut '" << type_name << "'"
<< " size=" << constraint_infos_[ct_index].constraint.vars.size()
<< " max_magnitude="
<< ComputeInfinityNorm(constraint_infos_[ct_index].constraint)
@@ -291,7 +290,7 @@ void LinearConstraintManager::PermanentlyRemoveSomeConstraints() {
}
if (num_deleted_constraints > 0) {
VLOG(2) << "Constraint manager cleanup: #deleted:"
VLOG(1) << "Constraint manager cleanup: #deleted:"
<< num_deleted_constraints;
}
num_deletable_constraints_ -= num_deleted_constraints;

View File

@@ -25,6 +25,7 @@
#include "ortools/sat/linear_constraint.h"
#include "ortools/sat/model.h"
#include "ortools/sat/sat_parameters.pb.h"
#include "ortools/util/logging.h"
#include "ortools/util/time_limit.h"
namespace operations_research {
@@ -68,7 +69,8 @@ class LinearConstraintManager {
: sat_parameters_(*model->GetOrCreate<SatParameters>()),
integer_trail_(*model->GetOrCreate<IntegerTrail>()),
time_limit_(model->GetOrCreate<TimeLimit>()),
model_(model) {}
model_(model),
logger_(model->GetOrCreate<SolverLogger>()) {}
~LinearConstraintManager();
// Add a new constraint to the manager. Note that we canonicalize constraints
@@ -213,6 +215,7 @@ class LinearConstraintManager {
TimeLimit* time_limit_;
Model* model_;
SolverLogger* logger_;
// We want to decay the active counts of all constraints at each call and
// increase the active counts of active/violated constraints. However this can

View File

@@ -249,23 +249,24 @@ bool Prober::ProbeBooleanVariables(
time_limit_->GetElapsedDeterministicTime() - initial_deterministic_time;
const int num_fixed = sat_solver_->LiteralTrail().Index();
const int num_newly_fixed = num_fixed - initial_num_fixed;
SOLVER_LOG(logger_, "Probing deterministic_time: ", time_diff,
SOLVER_LOG(logger_, "[Probing] deterministic_time: ", time_diff,
" (limit: ", deterministic_time_limit,
") wall_time: ", wall_timer.Get(), " (",
(limit_reached ? "Aborted " : ""), num_probed, "/",
bool_vars.size(), ")");
if (num_newly_fixed > 0) {
SOLVER_LOG(logger_, " - new fixed Boolean: ", num_newly_fixed, " (",
num_fixed, "/", sat_solver_->NumVariables(), ")");
SOLVER_LOG(logger_, "[Probing] - new fixed Boolean: ", num_newly_fixed,
" (", num_fixed, "/", sat_solver_->NumVariables(), ")");
}
if (num_new_holes_ > 0) {
SOLVER_LOG(logger_, " - new integer holes: ", num_new_holes_);
SOLVER_LOG(logger_, "[Probing] - new integer holes: ", num_new_holes_);
}
if (num_new_integer_bounds_ > 0) {
SOLVER_LOG(logger_, " - new integer bounds: ", num_new_integer_bounds_);
SOLVER_LOG(logger_,
"[Probing] - new integer bounds: ", num_new_integer_bounds_);
}
if (num_new_binary_ > 0) {
SOLVER_LOG(logger_, " - new binary clause: ", num_new_binary_);
SOLVER_LOG(logger_, "[Probing] - new binary clause: ", num_new_binary_);
}
}

View File

@@ -1586,15 +1586,15 @@ class CpModel(object):
def ModelStats(self):
"""Returns a string containing some model statistics."""
return pywrapsat.SatHelper.ModelStats(self.__model)
return pywrapsat.CpSatHelper.ModelStats(self.__model)
def Validate(self):
"""Returns a string indicating that the model is invalid."""
return pywrapsat.SatHelper.ValidateModel(self.__model)
return pywrapsat.CpSatHelper.ValidateModel(self.__model)
def ExportToFile(self, file):
"""Write the model as a ascii protocol buffer to 'file'."""
return pywrapsat.SatHelper.WriteModelToFile(self.__model, file)
return pywrapsat.CpSatHelper.WriteModelToFile(self.__model, file)
def AssertIsBooleanVariable(self, x):
if isinstance(x, IntVar):
@@ -1689,19 +1689,25 @@ class CpSolver(object):
self.__model = None
self.__solution: cp_model_pb2.CpSolverResponse = None
self.parameters = sat_parameters_pb2.SatParameters()
self.log_callback = None
def Solve(self, model):
"""Solves the given model and returns the solve status."""
self.__solution = pywrapsat.SatHelper.SolveWithParameters(
model.Proto(), self.parameters)
def Solve(self, model, solution_callback=None):
"""Solves a problem and passes each solution to the callback if not null."""
solve_wrapper = pywrapsat.SolveWrapper()
solve_wrapper.SetParameters(self.parameters)
if solution_callback is not None:
solve_wrapper.AddSolutionCallback(solution_callback)
if self.log_callback:
solve_wrapper.AddLogCallback(self.log_callback)
self.__solution = solve_wrapper.Solve(model.Proto())
return self.__solution.status
# DEPRECATED, just use Solve() with the callback argument.
def SolveWithSolutionCallback(self, model, callback):
"""Solves a problem and passes each solution found to the callback."""
self.__solution = (
pywrapsat.SatHelper.SolveWithParametersAndSolutionCallback(
model.Proto(), self.parameters, callback))
return self.__solution.status
"""DEPRECATED Use Solve() with the callback argument."""
return self.Solve(model, callback)
def SearchForAllSolutions(self, model, callback):
"""Search for all solutions of a satisfiability problem.
@@ -1728,9 +1734,9 @@ class CpSolver(object):
# Store old values.
enumerate_all = self.parameters.enumerate_all_solutions
self.parameters.enumerate_all_solutions = True
self.__solution = (
pywrapsat.SatHelper.SolveWithParametersAndSolutionCallback(
model.Proto(), self.parameters, callback))
self.Solve(model, callback)
# Restore parameters.
self.parameters.enumerate_all_solutions = enumerate_all
return self.__solution.status
@@ -1783,7 +1789,7 @@ class CpSolver(object):
def ResponseStats(self):
"""Returns some statistics on the solution found as a string."""
return pywrapsat.SatHelper.SolverResponseStats(self.__solution)
return pywrapsat.CpSatHelper.SolverResponseStats(self.__solution)
def ResponseProto(self):
"""Returns the response object."""

View File

@@ -53,15 +53,22 @@ PY_PROTO_TYPEMAP(ortools.sat.sat_parameters_pb2,
%unignore operations_research;
%unignore operations_research::sat;
%unignore operations_research::sat::SatHelper;
%unignore operations_research::sat::SatHelper::Solve;
%unignore operations_research::sat::SatHelper::SolveWithParameters;
%unignore operations_research::sat::SatHelper::SolveWithParametersAndSolutionCallback;
%unignore operations_research::sat::SatHelper::ModelStats;
%unignore operations_research::sat::SatHelper::SolverResponseStats;
%unignore operations_research::sat::SatHelper::ValidateModel;
%unignore operations_research::sat::SatHelper::VariableDomain;
%unignore operations_research::sat::SatHelper::WriteModelToFile;
// Wrap the SolveWrapper class.
%unignore operations_research::sat::SolveWrapper;
%unignore operations_research::sat::SolveWrapper::SetParameters;
%unignore operations_research::sat::SolveWrapper::AddSolutionCallback;
%unignore operations_research::sat::SolveWrapper::AddLogCallback;
%unignore operations_research::sat::SolveWrapper::SetEnumerateAllSolutions;
%unignore operations_research::sat::SolveWrapper::Solve;
// Wrap the CpSatHelper class.
%unignore operations_research::sat::CpSatHelper;
%unignore operations_research::sat::CpSatHelper::ModelStats;
%unignore operations_research::sat::CpSatHelper::SolverResponseStats;
%unignore operations_research::sat::CpSatHelper::ValidateModel;
%unignore operations_research::sat::CpSatHelper::VariableDomain;
%unignore operations_research::sat::CpSatHelper::WriteModelToFile;
%feature("director") operations_research::sat::SolutionCallback;
%unignore operations_research::sat::SolutionCallback;

View File

@@ -21,7 +21,7 @@ option java_multiple_files = true;
// Contains the definitions for all the sat algorithm parameters and their
// default values.
//
// NEXT TAG: 187
// NEXT TAG: 188
message SatParameters {
// In some context, like in a portfolio of search, it makes sense to name a
// given parameters set for logging purpose.
@@ -373,21 +373,11 @@ message SatParameters {
// Add a prefix to all logs.
optional string log_prefix = 185 [default = ""];
// Enum to control the recipient of the log.
enum LogDestination {
// Use the C++ Logging facilities.
USE_STANDARD_LOGGING = 0;
// Use the C++ Logging facilities.
FILL_IN_RESPONSE = 1;
// Use the C++ Logging facilities.
LOG_TO_BOTH = 2;
// No destination specified. This is useful if a custom destination has been
// manually added to the logger class.
NO_DESTINATION = 3;
}
// Copy logs in the final response.
optional LogDestination log_destination = 186
[default = USE_STANDARD_LOGGING];
// Log to stdout.
optional bool log_to_stdout = 186 [default = true];
// Log to response proto.
optional bool log_to_response = 187 [default = false];
// Whether to use pseudo-Boolean resolution to analyze a conflict. Note that
// this option only make sense if your problem is modelized using

View File

@@ -333,9 +333,11 @@ bool SatPresolver::Presolve(const std::vector<bool>& can_be_removed) {
for (const bool b : can_be_removed) {
if (b) ++num_removable;
}
SOLVER_LOG(logger_, "num removable Booleans: ", num_removable, " / ",
SOLVER_LOG(logger_,
"[SAT presolve] num removable Booleans: ", num_removable, " / ",
can_be_removed.size());
SOLVER_LOG(logger_, "num trivial clauses: ", num_trivial_clauses_);
SOLVER_LOG(logger_,
"[SAT presolve] num trivial clauses: ", num_trivial_clauses_);
DisplayStats(0);
}
@@ -920,9 +922,9 @@ void SatPresolver::DisplayStats(double elapsed_seconds) {
num_simple_definition++;
}
}
SOLVER_LOG(logger_, " [", elapsed_seconds, "s]", " clauses:", num_clauses,
" literals:", num_literals, " vars:", num_vars,
" one_side_vars:", num_one_side,
SOLVER_LOG(logger_, "[SAT presolve] [", elapsed_seconds, "s]",
" clauses:", num_clauses, " literals:", num_literals,
" vars:", num_vars, " one_side_vars:", num_one_side,
" simple_definition:", num_simple_definition,
" singleton_clauses:", num_singleton_clauses);
}

View File

@@ -23,13 +23,14 @@
#include "ortools/sat/cp_model_utils.h"
#include "ortools/sat/model.h"
#include "ortools/sat/sat_parameters.pb.h"
#include "ortools/util/logging.h"
#include "ortools/util/time_limit.h"
namespace operations_research {
namespace sat {
// Base class for SWIG director based on solution callbacks.
// See http://www.swig.org/Doc3.0/SWIGDocumentation.html#CSharp_directors.
// See http://www.swig.org/Doc4.0/SWIGDocumentation.html#CSharp_directors.
class SolutionCallback {
public:
virtual ~SolutionCallback() {}
@@ -95,7 +96,15 @@ class SolutionCallback {
mutable std::atomic<bool> stopped_;
};
class SatHelper {
// Simple director class for C#.
class LogCallback {
public:
virtual ~LogCallback() {}
virtual void NewMessage(const std::string& message) = 0;
};
// This class is not meant to be reused after one solve.
class SolveWrapper {
public:
// The arguments of the functions defined below must follow these rules
// to be wrapped by swig correctly:
@@ -105,61 +114,63 @@ class SatHelper {
// file (see the python/ and java/ subdirectories).
// 3) String variations of the parameters have been added for C# as
// C# protobufs do not support proto2.
static operations_research::sat::CpSolverResponse Solve(
void SetParameters(
const operations_research::sat::SatParameters& parameters) {
model_.Add(NewSatParameters(parameters));
}
void SetStringParameters(const std::string& string_parameters) {
model_.Add(NewSatParameters(string_parameters));
}
void AddSolutionCallback(const SolutionCallback& callback) {
model_.Add(NewFeasibleSolutionObserver(
[&callback](const CpSolverResponse& r) { return callback.Run(r); }));
if (!stopped_ptr_) {
stopped_ptr_ = callback.stopped();
}
}
void AddLogCallback(std::function<void(const std::string&)> log_callback) {
if (log_callback != nullptr) {
model_.GetOrCreate<SolverLogger>()->AddInfoLoggingCallback(log_callback);
}
}
// Workaround for C#.
void AddLogCallbackFromClass(LogCallback* log_callback) {
model_.GetOrCreate<SolverLogger>()->AddInfoLoggingCallback(
[log_callback](const std::string& message) {
log_callback->NewMessage(message);
});
}
operations_research::sat::CpSolverResponse Solve(
const operations_research::sat::CpModelProto& model_proto) {
FixFlagsAndEnvironmentForSwig();
return operations_research::sat::Solve(model_proto);
if (stopped_ptr_ != nullptr) {
(*stopped_ptr_) = false;
model_.GetOrCreate<TimeLimit>()->RegisterExternalBooleanAsLimit(
stopped_ptr_);
}
return operations_research::sat::SolveCpModel(model_proto, &model_);
}
static operations_research::sat::CpSolverResponse SolveWithParameters(
const operations_research::sat::CpModelProto& model_proto,
const operations_research::sat::SatParameters& parameters) {
FixFlagsAndEnvironmentForSwig();
return operations_research::sat::SolveWithParameters(model_proto,
parameters);
void SetEnumerateAllSolutions() {
model_.GetOrCreate<SatParameters>()->set_enumerate_all_solutions(true);
}
static operations_research::sat::CpSolverResponse SolveWithStringParameters(
const operations_research::sat::CpModelProto& model_proto,
const std::string& parameters) {
FixFlagsAndEnvironmentForSwig();
return operations_research::sat::SolveWithParameters(model_proto,
parameters);
}
static operations_research::sat::CpSolverResponse
SolveWithParametersAndSolutionCallback(
const operations_research::sat::CpModelProto& model_proto,
const operations_research::sat::SatParameters& parameters,
const SolutionCallback& callback) {
FixFlagsAndEnvironmentForSwig();
callback.ResetSharedBoolean();
Model model;
model.Add(NewSatParameters(parameters));
model.Add(NewFeasibleSolutionObserver(
[&callback](const CpSolverResponse& r) { return callback.Run(r); }));
model.GetOrCreate<TimeLimit>()->RegisterExternalBooleanAsLimit(
callback.stopped());
return SolveCpModel(model_proto, &model);
}
static operations_research::sat::CpSolverResponse
SolveWithStringParametersAndSolutionCallback(
const operations_research::sat::CpModelProto& model_proto,
const std::string& parameters, const SolutionCallback& callback) {
FixFlagsAndEnvironmentForSwig();
callback.ResetSharedBoolean();
Model model;
model.Add(NewSatParameters(parameters));
model.Add(NewFeasibleSolutionObserver(
[&callback](const CpSolverResponse& r) { return callback.Run(r); }));
model.GetOrCreate<TimeLimit>()->RegisterExternalBooleanAsLimit(
callback.stopped());
return SolveCpModel(model_proto, &model);
}
private:
Model model_;
std::atomic<bool>* stopped_ptr_ = nullptr;
};
// Static methods are stored in a module which name can vary.
// To avoid this issue, we put all global functions as a static method of the
// CpSatHelper class.
struct CpSatHelper {
public:
// Returns a string with some statistics on the given CpModelProto.
static std::string ModelStats(
const operations_research::sat::CpModelProto& model_proto) {

View File

@@ -13,6 +13,8 @@
#include "ortools/util/logging.h"
#include <iostream>
#include "absl/strings/str_cat.h"
#include "ortools/base/logging.h"
@@ -25,9 +27,8 @@ void SolverLogger::AddInfoLoggingCallback(
void SolverLogger::LogInfo(const char* source_filename, int source_line,
const std::string& message) {
if (info_callbacks_.empty() || force_standard_logging_) {
google::LogMessage(source_filename, source_line).stream()
<< prefix_ << message;
if (log_to_stdout_) {
std::cout << prefix_ << message << std::endl;
}
for (const auto& callback : info_callbacks_) {

View File

@@ -44,12 +44,8 @@ class SolverLogger {
// Returns true iff logging is enabled.
bool LoggingIsEnabled() const { return is_enabled_; }
// Forces standard logging, even if some callbacks have been added.
void ForceStandardLogging() { force_standard_logging_ = true; }
// Relaxes the standard logging. It will be used only if no callbacks have
// been added.
void RelaxStandardLogging() { force_standard_logging_ = false; }
// Should all messages be displayed on stdout ?
void SetLogToStdOut(bool enable) { log_to_stdout_ = enable; }
// Adds a prefix to all logs.
void SetLogPrefix(const std::string& prefix) { prefix_ = prefix; }
@@ -66,7 +62,7 @@ class SolverLogger {
private:
bool is_enabled_ = false;
bool force_standard_logging_ = false;
bool log_to_stdout_ = false;
std::string prefix_;
std::vector<std::function<void(const std::string& message)>> info_callbacks_;
};

View File

@@ -205,3 +205,22 @@ static ReturnT InvokePythonCallableReturning(PyObject* pyfunc) {
SharedPyPtr input($input);
$1 = [input]() { return InvokePythonCallableReturning<void>(input.get()); };
}
// Wrap std::function<void(std::string)>
%typecheck(SWIG_TYPECHECK_POINTER) std::function<void(const std::string&)> {
$1 = PyCallable_Check($input);
}
%typemap(in) std::function<void(const std::string&)> {
SharedPyPtr input($input);
$1 = [input](const std::string& str) {
PyObject* py_str = PyUnicode_FromStringAndSize(str.c_str(), str.size());
PyObject* result;
SWIG_PYTHON_THREAD_BEGIN_BLOCK;
result = PyObject_CallOneArg(input.get(), py_str);
SWIG_PYTHON_THREAD_END_BLOCK;
Py_DECREF(py_str);
return result;
};
}