Merge branch 'main' of github.com:google/or-tools
This commit is contained in:
14
.github/workflows/bazel_linux.yml
vendored
14
.github/workflows/bazel_linux.yml
vendored
@@ -30,6 +30,16 @@ jobs:
|
||||
- name: Check Bazel
|
||||
run: bazel version
|
||||
- name: Build
|
||||
run: bazel build -c opt --cxxopt=-std=c++17 --subcommands=true //ortools/... //examples/...
|
||||
run: >
|
||||
bazel build
|
||||
-c opt
|
||||
--action_env=BAZEL_CXXOPTS="-std=c++17"
|
||||
--subcommands=true
|
||||
ortools/... examples/...
|
||||
- name: Test
|
||||
run: bazel test -c opt --cxxopt=-std=c++17 --test_output=errors //ortools/... //examples/...
|
||||
run: >
|
||||
bazel test
|
||||
-c opt
|
||||
--action_env=BAZEL_CXXOPTS="-std=c++17"
|
||||
--test_output=errors
|
||||
ortools/... examples/...
|
||||
|
||||
@@ -233,7 +233,7 @@ JUNIT_JUPITER_VERSION = "5.9.2"
|
||||
load("@rules_jvm_external//:defs.bzl", "maven_install")
|
||||
maven_install(
|
||||
artifacts = [
|
||||
"net.java.dev.jna:jna:aar:5.12.1",
|
||||
"net.java.dev.jna:jna:aar:5.13.0",
|
||||
"com.google.truth:truth:0.32",
|
||||
"org.junit.platform:junit-platform-launcher:%s" % JUNIT_PLATFORM_VERSION,
|
||||
"org.junit.platform:junit-platform-reporting:%s" % JUNIT_PLATFORM_VERSION,
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
set_property(SOURCE knapsack_solver.i PROPERTY CPLUSPLUS ON)
|
||||
set_property(SOURCE knapsack_solver.i PROPERTY SWIG_MODULE_NAME main)
|
||||
set_property(SOURCE knapsack_solver.i PROPERTY COMPILE_DEFINITIONS ${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE knapsack_solver.i PROPERTY COMPILE_DEFINITIONS
|
||||
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE knapsack_solver.i PROPERTY COMPILE_OPTIONS
|
||||
-package ${JAVA_PACKAGE}.algorithms)
|
||||
swig_add_library(jnialgorithms
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
set_property(SOURCE knapsack_solver.i PROPERTY CPLUSPLUS ON)
|
||||
set_property(SOURCE knapsack_solver.i PROPERTY SWIG_MODULE_NAME pywrapknapsack_solver)
|
||||
set_property(SOURCE knapsack_solver.i PROPERTY COMPILE_DEFINITIONS ${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE knapsack_solver.i PROPERTY COMPILE_DEFINITIONS
|
||||
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
swig_add_library(pywrapknapsack_solver
|
||||
TYPE MODULE
|
||||
LANGUAGE python
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
set_property(SOURCE routing.i PROPERTY CPLUSPLUS ON)
|
||||
set_property(SOURCE routing.i PROPERTY SWIG_MODULE_NAME operations_research_constraint_solver)
|
||||
set_property(SOURCE routing.i PROPERTY COMPILE_DEFINITIONS ${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE routing.i PROPERTY COMPILE_DEFINITIONS
|
||||
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE routing.i PROPERTY COMPILE_OPTIONS
|
||||
-namespace ${DOTNET_PROJECT}.ConstraintSolver
|
||||
-dllimport google-ortools-native)
|
||||
|
||||
@@ -21,6 +21,7 @@ and .Net. Each language have different requirements for the code samples.
|
||||
```cpp
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
|
||||
#include "ortools/constraint_solver/routing.h"
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
set_property(SOURCE routing.i PROPERTY CPLUSPLUS ON)
|
||||
set_property(SOURCE routing.i PROPERTY SWIG_MODULE_NAME main)
|
||||
set_property(SOURCE routing.i PROPERTY COMPILE_DEFINITIONS ${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE routing.i PROPERTY COMPILE_DEFINITIONS
|
||||
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE routing.i PROPERTY COMPILE_OPTIONS
|
||||
-package ${JAVA_PACKAGE}.constraintsolver)
|
||||
swig_add_library(jniconstraint_solver
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
set_property(SOURCE routing.i PROPERTY CPLUSPLUS ON)
|
||||
set_property(SOURCE routing.i PROPERTY SWIG_MODULE_NAME pywrapcp)
|
||||
set_property(SOURCE routing.i PROPERTY COMPILE_DEFINITIONS ${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE routing.i PROPERTY COMPILE_DEFINITIONS
|
||||
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE routing.i PROPERTY COMPILE_OPTIONS -nofastunpack)
|
||||
swig_add_library(pywrapcp
|
||||
TYPE MODULE
|
||||
|
||||
@@ -202,18 +202,17 @@ def add_time_window_constraints(routing, manager, data, time_evaluator_index):
|
||||
# [START solution_printer]
|
||||
def print_solution(data, manager, routing, assignment): # pylint:disable=too-many-locals
|
||||
"""Prints assignment on console."""
|
||||
print('Objective: {}'.format(assignment.ObjectiveValue()))
|
||||
print(f'Objective: {assignment.ObjectiveValue()}')
|
||||
|
||||
print('Breaks:')
|
||||
intervals = assignment.IntervalVarContainer()
|
||||
for i in range(intervals.Size()):
|
||||
brk = intervals.Element(i)
|
||||
if brk.PerformedValue() == 1:
|
||||
print('{}: Start({}) Duration({})'.format(brk.Var().Name(),
|
||||
brk.StartValue(),
|
||||
brk.DurationValue()))
|
||||
print(f'{brk.Var().Name()}:'
|
||||
f' Start({brk.StartValue()}) Duration({brk.DurationValue()})')
|
||||
else:
|
||||
print('{}: Unperformed'.format(brk.Var().Name()))
|
||||
print(f'{brk.Var().Name()}: Unperformed')
|
||||
|
||||
total_distance = 0
|
||||
total_load = 0
|
||||
@@ -222,37 +221,40 @@ def print_solution(data, manager, routing, assignment): # pylint:disable=too-ma
|
||||
time_dimension = routing.GetDimensionOrDie('Time')
|
||||
for vehicle_id in range(data['num_vehicles']):
|
||||
index = routing.Start(vehicle_id)
|
||||
plan_output = 'Route for vehicle {}:\n'.format(vehicle_id)
|
||||
plan_output = f'Route for vehicle {vehicle_id}:\n'
|
||||
distance = 0
|
||||
while not routing.IsEnd(index):
|
||||
load_var = capacity_dimension.CumulVar(index)
|
||||
time_var = time_dimension.CumulVar(index)
|
||||
slack_var = time_dimension.SlackVar(index)
|
||||
plan_output += ' {0} Load({1}) Time({2},{3}) Slack({4},{5}) ->'.format(
|
||||
manager.IndexToNode(index), assignment.Value(load_var),
|
||||
assignment.Min(time_var), assignment.Max(time_var),
|
||||
assignment.Min(slack_var), assignment.Max(slack_var))
|
||||
node = manager.IndexToNode(index)
|
||||
plan_output += (
|
||||
f' {node}'
|
||||
f' Load({assignment.Value(load_var)})'
|
||||
f' Time({assignment.Min(time_var)}, {assignment.Max(time_var)})'
|
||||
f' Slack({assignment.Min(slack_var)}, {assignment.Max(slack_var)})'
|
||||
' ->')
|
||||
previous_index = index
|
||||
index = assignment.Value(routing.NextVar(index))
|
||||
distance += routing.GetArcCostForVehicle(previous_index, index,
|
||||
vehicle_id)
|
||||
load_var = capacity_dimension.CumulVar(index)
|
||||
time_var = time_dimension.CumulVar(index)
|
||||
plan_output += ' {0} Load({1}) Time({2},{3})\n'.format(
|
||||
manager.IndexToNode(index), assignment.Value(load_var),
|
||||
assignment.Min(time_var), assignment.Max(time_var))
|
||||
plan_output += 'Distance of the route: {0}m\n'.format(distance)
|
||||
plan_output += 'Load of the route: {}\n'.format(
|
||||
assignment.Value(load_var))
|
||||
plan_output += 'Time of the route: {}\n'.format(
|
||||
assignment.Value(time_var))
|
||||
node = manager.IndexToNode(index)
|
||||
plan_output += (
|
||||
f' {node}'
|
||||
f' Load({assignment.Value(load_var)})'
|
||||
f' Time({assignment.Min(time_var)}, {assignment.Max(time_var)})\n')
|
||||
plan_output += f'Distance of the route: {distance}m\n'
|
||||
plan_output += f'Load of the route: {assignment.Value(load_var)}\n'
|
||||
plan_output += f'Time of the route: {assignment.Value(time_var)}\n'
|
||||
print(plan_output)
|
||||
total_distance += distance
|
||||
total_load += assignment.Value(load_var)
|
||||
total_time += assignment.Value(time_var)
|
||||
print('Total Distance of all routes: {0}m'.format(total_distance))
|
||||
print('Total Load of all routes: {}'.format(total_load))
|
||||
print('Total Time of all routes: {0}min'.format(total_time))
|
||||
print(f'Total Distance of all routes: {total_distance}m')
|
||||
print(f'Total Load of all routes: {total_load}')
|
||||
print(f'Total Time of all routes: {total_time}min')
|
||||
# [END solution_printer]
|
||||
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ void SolveJobShopExample() {
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
InitGoogle(argv[0], &argc, &argv, true);
|
||||
absl::SetFlag(&FLAGS_logtostderr, true);
|
||||
absl::SetFlag(&FLAGS_stderrthreshold, 0);
|
||||
operations_research::SolveJobShopExample();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ void SolveNursesExample() {
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
InitGoogle(argv[0], &argc, &argv, true);
|
||||
absl::SetFlag(&FLAGS_logtostderr, true);
|
||||
absl::SetFlag(&FLAGS_stderrthreshold, 0);
|
||||
operations_research::SolveNursesExample();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ void RunConstraintProgrammingExample() {
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
InitGoogle(argv[0], &argc, &argv, true);
|
||||
absl::SetFlag(&FLAGS_logtostderr, true);
|
||||
absl::SetFlag(&FLAGS_stderrthreshold, 0);
|
||||
operations_research::RunConstraintProgrammingExample();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -53,18 +53,18 @@ def main():
|
||||
solver.NewSearch(decision_builder)
|
||||
while solver.NextSolution():
|
||||
count += 1
|
||||
solution = 'Solution {}:\n'.format(count)
|
||||
solution = f'Solution {count}:\n'
|
||||
for var in [x, y, z]:
|
||||
solution += ' {} = {}'.format(var.Name(), var.Value())
|
||||
solution += f' {var.Name()} = {var.Value()}'
|
||||
print(solution)
|
||||
solver.EndSearch()
|
||||
print('Number of solutions found: ', count)
|
||||
print(f'Number of solutions found: {count}')
|
||||
# [END print_solution]
|
||||
|
||||
# [START advanced]
|
||||
print('Advanced usage:')
|
||||
print('Problem solved in ', solver.WallTime(), 'ms')
|
||||
print('Memory usage: ', pywrapcp.Solver.MemoryUsage(), 'bytes')
|
||||
print(f'Problem solved in {solver.WallTime()}ms')
|
||||
print(f'Memory usage: {pywrapcp.Solver.MemoryUsage()}bytes')
|
||||
# [END advanced]
|
||||
|
||||
|
||||
|
||||
@@ -29,13 +29,12 @@ class OneVarLns : public BaseLns {
|
||||
explicit OneVarLns(const std::vector<IntVar*>& vars)
|
||||
: BaseLns(vars), index_(0) {}
|
||||
|
||||
~OneVarLns() override {}
|
||||
~OneVarLns() override = default;
|
||||
|
||||
void InitFragments() override { index_ = 0; }
|
||||
|
||||
bool NextFragment() override {
|
||||
const int size = Size();
|
||||
if (index_ < size) {
|
||||
if (index_ < Size()) {
|
||||
AppendToFragment(index_);
|
||||
++index_;
|
||||
return true;
|
||||
@@ -55,7 +54,7 @@ class MoveOneVar : public IntVarLocalSearchOperator {
|
||||
variable_index_(0),
|
||||
move_up_(false) {}
|
||||
|
||||
~MoveOneVar() override {}
|
||||
~MoveOneVar() override = default;
|
||||
|
||||
protected:
|
||||
// Make a neighbor assigning one variable to its target value.
|
||||
@@ -88,7 +87,7 @@ class SumFilter : public IntVarLocalSearchFilter {
|
||||
explicit SumFilter(const std::vector<IntVar*>& vars)
|
||||
: IntVarLocalSearchFilter(vars), sum_(0) {}
|
||||
|
||||
~SumFilter() override {}
|
||||
~SumFilter() override = default;
|
||||
|
||||
void OnSynchronize(const Assignment* delta) override {
|
||||
sum_ = 0;
|
||||
@@ -200,7 +199,7 @@ void SolveProblem(SolveType solve_type) {
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
InitGoogle(argv[0], &argc, &argv, true);
|
||||
absl::SetFlag(&FLAGS_logtostderr, true);
|
||||
absl::SetFlag(&FLAGS_stderrthreshold, 0);
|
||||
operations_research::SolveProblem(operations_research::LNS);
|
||||
operations_research::SolveProblem(operations_research::LS);
|
||||
operations_research::SolveProblem(operations_research::LS_WITH_FILTER);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// [START import]
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
|
||||
#include "ortools/constraint_solver/routing.h"
|
||||
@@ -73,11 +74,11 @@ void SimpleRoutingProgram() {
|
||||
// Inspect solution.
|
||||
int64_t index = routing.Start(0);
|
||||
LOG(INFO) << "Route for Vehicle 0:";
|
||||
int64_t route_distance{0};
|
||||
int64_t route_distance = 0;
|
||||
std::ostringstream route;
|
||||
while (routing.IsEnd(index) == false) {
|
||||
while (!routing.IsEnd(index)) {
|
||||
route << manager.IndexToNode(index).value() << " -> ";
|
||||
int64_t previous_index = index;
|
||||
const int64_t previous_index = index;
|
||||
index = solution->Value(routing.NextVar(index));
|
||||
route_distance +=
|
||||
routing.GetArcCostForVehicle(previous_index, index, int64_t{0});
|
||||
|
||||
@@ -72,17 +72,17 @@ def main():
|
||||
|
||||
# Print solution on console.
|
||||
# [START print_solution]
|
||||
print('Objective: {}'.format(assignment.ObjectiveValue()))
|
||||
print(f'Objective: {assignment.ObjectiveValue()}')
|
||||
index = routing.Start(0)
|
||||
plan_output = 'Route for vehicle 0:\n'
|
||||
route_distance = 0
|
||||
while not routing.IsEnd(index):
|
||||
plan_output += '{} -> '.format(manager.IndexToNode(index))
|
||||
plan_output += f'{manager.IndexToNode(index)} -> '
|
||||
previous_index = index
|
||||
index = assignment.Value(routing.NextVar(index))
|
||||
route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)
|
||||
plan_output += '{}\n'.format(manager.IndexToNode(index))
|
||||
plan_output += 'Distance of the route: {}m\n'.format(route_distance)
|
||||
plan_output += f'{manager.IndexToNode(index)}\n'
|
||||
plan_output += f'Distance of the route: {route_distance}m\n'
|
||||
print(plan_output)
|
||||
# [END print_solution]
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// [START import]
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
@@ -53,12 +54,12 @@ std::vector<std::vector<int64_t>> GenerateManhattanDistanceMatrix(
|
||||
std::vector<std::vector<int64_t>> distances =
|
||||
std::vector<std::vector<int64_t>>(
|
||||
locations.size(), std::vector<int64_t>(locations.size(), int64_t{0}));
|
||||
for (int fromNode = 0; fromNode < locations.size(); fromNode++) {
|
||||
for (int toNode = 0; toNode < locations.size(); toNode++) {
|
||||
if (fromNode != toNode)
|
||||
distances[fromNode][toNode] =
|
||||
int64_t{std::abs(locations[toNode][0] - locations[fromNode][0]) +
|
||||
std::abs(locations[toNode][1] - locations[fromNode][1])};
|
||||
for (int from_node = 0; from_node < locations.size(); from_node++) {
|
||||
for (int to_node = 0; to_node < locations.size(); to_node++) {
|
||||
if (from_node != to_node)
|
||||
distances[from_node][to_node] =
|
||||
int64_t{std::abs(locations[to_node][0] - locations[from_node][0]) +
|
||||
std::abs(locations[to_node][1] - locations[from_node][1])};
|
||||
}
|
||||
}
|
||||
return distances;
|
||||
@@ -78,9 +79,9 @@ void PrintSolution(const RoutingIndexManager& manager,
|
||||
LOG(INFO) << "Route for Vehicle 0:";
|
||||
int64_t distance{0};
|
||||
std::stringstream route;
|
||||
while (routing.IsEnd(index) == false) {
|
||||
while (!routing.IsEnd(index)) {
|
||||
route << manager.IndexToNode(index).value() << " -> ";
|
||||
int64_t previous_index = index;
|
||||
const int64_t previous_index = index;
|
||||
index = solution.Value(routing.NextVar(index));
|
||||
distance += routing.GetArcCostForVehicle(previous_index, index, int64_t{0});
|
||||
}
|
||||
@@ -113,8 +114,8 @@ void Tsp() {
|
||||
// [START transit_callback]
|
||||
const auto distance_matrix = GenerateManhattanDistanceMatrix(data.locations);
|
||||
const int transit_callback_index = routing.RegisterTransitCallback(
|
||||
[&distance_matrix, &manager](int64_t from_index,
|
||||
int64_t to_index) -> int64_t {
|
||||
[&distance_matrix, &manager](const int64_t from_index,
|
||||
const int64_t to_index) -> int64_t {
|
||||
// Convert from routing variable Index to distance matrix NodeIndex.
|
||||
auto from_node = manager.IndexToNode(from_index).value();
|
||||
auto to_node = manager.IndexToNode(to_index).value();
|
||||
|
||||
@@ -78,17 +78,17 @@ def create_distance_callback(data, manager):
|
||||
# [START solution_printer]
|
||||
def print_solution(manager, routing, assignment):
|
||||
"""Prints assignment on console."""
|
||||
print('Objective: {}'.format(assignment.ObjectiveValue()))
|
||||
print(f'Objective: {assignment.ObjectiveValue()}')
|
||||
index = routing.Start(0)
|
||||
plan_output = 'Route for vehicle 0:\n'
|
||||
route_distance = 0
|
||||
while not routing.IsEnd(index):
|
||||
plan_output += ' {} ->'.format(manager.IndexToNode(index))
|
||||
plan_output += f' {manager.IndexToNode(index)} ->'
|
||||
previous_index = index
|
||||
index = assignment.Value(routing.NextVar(index))
|
||||
route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)
|
||||
plan_output += ' {}\n'.format(manager.IndexToNode(index))
|
||||
plan_output += 'Distance of the route: {}m\n'.format(route_distance)
|
||||
plan_output += f' {manager.IndexToNode(index)}\n'
|
||||
plan_output += f'Distance of the route: {route_distance}m\n'
|
||||
print(plan_output)
|
||||
# [END solution_printer]
|
||||
|
||||
|
||||
@@ -88,12 +88,12 @@ std::vector<std::vector<int64_t>> ComputeEuclideanDistanceMatrix(
|
||||
std::vector<std::vector<int64_t>> distances =
|
||||
std::vector<std::vector<int64_t>>(
|
||||
locations.size(), std::vector<int64_t>(locations.size(), int64_t{0}));
|
||||
for (int fromNode = 0; fromNode < locations.size(); fromNode++) {
|
||||
for (int toNode = 0; toNode < locations.size(); toNode++) {
|
||||
if (fromNode != toNode)
|
||||
distances[fromNode][toNode] = static_cast<int64_t>(
|
||||
std::hypot((locations[toNode][0] - locations[fromNode][0]),
|
||||
(locations[toNode][1] - locations[fromNode][1])));
|
||||
for (int from_node = 0; from_node < locations.size(); from_node++) {
|
||||
for (int to_node = 0; to_node < locations.size(); to_node++) {
|
||||
if (from_node != to_node)
|
||||
distances[from_node][to_node] = static_cast<int64_t>(
|
||||
std::hypot((locations[to_node][0] - locations[from_node][0]),
|
||||
(locations[to_node][1] - locations[from_node][1])));
|
||||
}
|
||||
}
|
||||
return distances;
|
||||
@@ -113,9 +113,9 @@ void PrintSolution(const RoutingIndexManager& manager,
|
||||
LOG(INFO) << "Route:";
|
||||
int64_t distance{0};
|
||||
std::stringstream route;
|
||||
while (routing.IsEnd(index) == false) {
|
||||
while (!routing.IsEnd(index)) {
|
||||
route << manager.IndexToNode(index).value() << " -> ";
|
||||
int64_t previous_index = index;
|
||||
const int64_t previous_index = index;
|
||||
index = solution.Value(routing.NextVar(index));
|
||||
distance += routing.GetArcCostForVehicle(previous_index, index, int64_t{0});
|
||||
}
|
||||
@@ -147,8 +147,8 @@ void Tsp() {
|
||||
// [START transit_callback]
|
||||
const auto distance_matrix = ComputeEuclideanDistanceMatrix(data.locations);
|
||||
const int transit_callback_index = routing.RegisterTransitCallback(
|
||||
[&distance_matrix, &manager](int64_t from_index,
|
||||
int64_t to_index) -> int64_t {
|
||||
[&distance_matrix, &manager](const int64_t from_index,
|
||||
const int64_t to_index) -> int64_t {
|
||||
// Convert from routing variable Index to distance matrix NodeIndex.
|
||||
auto from_node = manager.IndexToNode(from_index).value();
|
||||
auto to_node = manager.IndexToNode(to_index).value();
|
||||
|
||||
@@ -103,18 +103,18 @@ def compute_euclidean_distance_matrix(locations):
|
||||
# [START solution_printer]
|
||||
def print_solution(manager, routing, solution):
|
||||
"""Prints solution on console."""
|
||||
print('Objective: {}'.format(solution.ObjectiveValue()))
|
||||
print(f'Objective: {solution.ObjectiveValue()}')
|
||||
index = routing.Start(0)
|
||||
plan_output = 'Route:\n'
|
||||
route_distance = 0
|
||||
while not routing.IsEnd(index):
|
||||
plan_output += ' {} ->'.format(manager.IndexToNode(index))
|
||||
plan_output += f' {manager.IndexToNode(index)} ->'
|
||||
previous_index = index
|
||||
index = solution.Value(routing.NextVar(index))
|
||||
route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)
|
||||
plan_output += ' {}\n'.format(manager.IndexToNode(index))
|
||||
plan_output += f' {manager.IndexToNode(index)}\n'
|
||||
print(plan_output)
|
||||
plan_output += 'Objective: {}m\n'.format(route_distance)
|
||||
plan_output += f'Objective: {route_distance}m\n'
|
||||
# [END solution_printer]
|
||||
|
||||
|
||||
|
||||
@@ -60,9 +60,9 @@ void PrintSolution(const RoutingIndexManager& manager,
|
||||
LOG(INFO) << "Route:";
|
||||
int64_t distance{0};
|
||||
std::stringstream route;
|
||||
while (routing.IsEnd(index) == false) {
|
||||
while (!routing.IsEnd(index)) {
|
||||
route << manager.IndexToNode(index).value() << " -> ";
|
||||
int64_t previous_index = index;
|
||||
const int64_t previous_index = index;
|
||||
index = solution.Value(routing.NextVar(index));
|
||||
distance += routing.GetArcCostForVehicle(previous_index, index, int64_t{0});
|
||||
}
|
||||
@@ -93,7 +93,8 @@ void Tsp() {
|
||||
|
||||
// [START transit_callback]
|
||||
const int transit_callback_index = routing.RegisterTransitCallback(
|
||||
[&data, &manager](int64_t from_index, int64_t to_index) -> int64_t {
|
||||
[&data, &manager](const int64_t from_index,
|
||||
const int64_t to_index) -> int64_t {
|
||||
// Convert from routing variable Index to distance matrix NodeIndex.
|
||||
auto from_node = manager.IndexToNode(from_index).value();
|
||||
auto to_node = manager.IndexToNode(to_index).value();
|
||||
|
||||
@@ -49,18 +49,18 @@ def create_data_model():
|
||||
# [START solution_printer]
|
||||
def print_solution(manager, routing, solution):
|
||||
"""Prints solution on console."""
|
||||
print('Objective: {} miles'.format(solution.ObjectiveValue()))
|
||||
print(f'Objective: {solution.ObjectiveValue()} miles')
|
||||
index = routing.Start(0)
|
||||
plan_output = 'Route for vehicle 0:\n'
|
||||
route_distance = 0
|
||||
while not routing.IsEnd(index):
|
||||
plan_output += ' {} ->'.format(manager.IndexToNode(index))
|
||||
plan_output += f' {manager.IndexToNode(index)} ->'
|
||||
previous_index = index
|
||||
index = solution.Value(routing.NextVar(index))
|
||||
route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)
|
||||
plan_output += ' {}\n'.format(manager.IndexToNode(index))
|
||||
plan_output += f' {manager.IndexToNode(index)}\n'
|
||||
print(plan_output)
|
||||
plan_output += 'Route distance: {}miles\n'.format(route_distance)
|
||||
plan_output += f'Route distance: {route_distance}miles\n'
|
||||
# [END solution_printer]
|
||||
|
||||
|
||||
|
||||
@@ -92,7 +92,8 @@ void Tsp() {
|
||||
// Define cost of each arc.
|
||||
// [START arc_cost]
|
||||
const int transit_callback_index = routing.RegisterTransitCallback(
|
||||
[&data, &manager](int64_t from_index, int64_t to_index) -> int64_t {
|
||||
[&data, &manager](const int64_t from_index,
|
||||
const int64_t to_index) -> int64_t {
|
||||
// Convert from routing variable Index to distance matrix NodeIndex.
|
||||
auto from_node = manager.IndexToNode(from_index).value();
|
||||
auto to_node = manager.IndexToNode(to_index).value();
|
||||
|
||||
@@ -80,9 +80,9 @@ void PrintSolution(const RoutingIndexManager& manager,
|
||||
LOG(INFO) << "Route for Vehicle 0:";
|
||||
int64_t distance{0};
|
||||
std::stringstream route;
|
||||
while (routing.IsEnd(index) == false) {
|
||||
while (!routing.IsEnd(index)) {
|
||||
route << manager.IndexToNode(index).value() << " -> ";
|
||||
int64_t previous_index = index;
|
||||
const int64_t previous_index = index;
|
||||
index = solution.Value(routing.NextVar(index));
|
||||
distance += routing.GetArcCostForVehicle(previous_index, index, int64_t{0});
|
||||
}
|
||||
@@ -114,7 +114,8 @@ void Tsp() {
|
||||
// Create and register a transit callback.
|
||||
// [START transit_callback]
|
||||
const int transit_callback_index = routing.RegisterTransitCallback(
|
||||
[&data, &manager](int64_t from_index, int64_t to_index) -> int64_t {
|
||||
[&data, &manager](const int64_t from_index,
|
||||
const int64_t to_index) -> int64_t {
|
||||
// Convert from routing variable Index to distance matrix NodeIndex.
|
||||
auto from_node = manager.IndexToNode(from_index).value();
|
||||
auto to_node = manager.IndexToNode(to_index).value();
|
||||
|
||||
@@ -104,17 +104,17 @@ def create_data_model():
|
||||
# [START solution_printer]
|
||||
def print_solution(manager, routing, solution):
|
||||
"""Prints solution on console."""
|
||||
print('Objective: {}'.format(solution.ObjectiveValue()))
|
||||
print(f'Objective: {solution.ObjectiveValue()}')
|
||||
index = routing.Start(0)
|
||||
plan_output = 'Route for vehicle 0:\n'
|
||||
route_distance = 0
|
||||
while not routing.IsEnd(index):
|
||||
plan_output += ' {} ->'.format(manager.IndexToNode(index))
|
||||
plan_output += f' {manager.IndexToNode(index)} ->'
|
||||
previous_index = index
|
||||
index = solution.Value(routing.NextVar(index))
|
||||
route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)
|
||||
plan_output += ' {}\n'.format(manager.IndexToNode(index))
|
||||
plan_output += 'Distance of the route: {}m\n'.format(route_distance)
|
||||
plan_output += f' {manager.IndexToNode(index)}\n'
|
||||
plan_output += f'Distance of the route: {route_distance}m\n'
|
||||
print(plan_output)
|
||||
# [END solution_printer]
|
||||
|
||||
|
||||
@@ -82,9 +82,9 @@ void PrintSolution(const DataModel& data, const RoutingIndexManager& manager,
|
||||
LOG(INFO) << "Route for Vehicle " << vehicle_id << ":";
|
||||
int64_t distance{0};
|
||||
std::stringstream route;
|
||||
while (routing.IsEnd(index) == false) {
|
||||
while (!routing.IsEnd(index)) {
|
||||
route << manager.IndexToNode(index).value() << " -> ";
|
||||
int64_t previous_index = index;
|
||||
const int64_t previous_index = index;
|
||||
index = solution.Value(routing.NextVar(index));
|
||||
distance += routing.GetArcCostForVehicle(previous_index, index,
|
||||
int64_t{vehicle_id});
|
||||
@@ -120,7 +120,8 @@ void Vrp() {
|
||||
// Create and register a transit callback.
|
||||
// [START transit_callback]
|
||||
const int transit_callback_index = routing.RegisterTransitCallback(
|
||||
[&data, &manager](int64_t from_index, int64_t to_index) -> int64_t {
|
||||
[&data, &manager](const int64_t from_index,
|
||||
const int64_t to_index) -> int64_t {
|
||||
// Convert from routing variable Index to distance matrix NodeIndex.
|
||||
auto from_node = manager.IndexToNode(from_index).value();
|
||||
auto to_node = manager.IndexToNode(to_index).value();
|
||||
|
||||
@@ -116,19 +116,19 @@ def print_solution(data, manager, routing, solution):
|
||||
total_distance = 0
|
||||
for vehicle_id in range(data['num_vehicles']):
|
||||
index = routing.Start(vehicle_id)
|
||||
plan_output = 'Route for vehicle {}:\n'.format(vehicle_id)
|
||||
plan_output = f'Route for vehicle {vehicle_id}:\n'
|
||||
route_distance = 0
|
||||
while not routing.IsEnd(index):
|
||||
plan_output += ' {} ->'.format(manager.IndexToNode(index))
|
||||
plan_output += f' {manager.IndexToNode(index)} ->'
|
||||
previous_index = index
|
||||
index = solution.Value(routing.NextVar(index))
|
||||
route_distance += routing.GetArcCostForVehicle(
|
||||
previous_index, index, vehicle_id)
|
||||
plan_output += ' {}\n'.format(manager.IndexToNode(index))
|
||||
plan_output += 'Distance of the route: {}m\n'.format(route_distance)
|
||||
plan_output += f' {manager.IndexToNode(index)}\n'
|
||||
plan_output += f'Distance of the route: {route_distance}m\n'
|
||||
print(plan_output)
|
||||
total_distance += route_distance
|
||||
print('Total Distance of all routes: {}m'.format(total_distance))
|
||||
print(f'Total Distance of all routes: {total_distance}m')
|
||||
|
||||
# [END solution_printer]
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ void PrintSolution(const RoutingIndexManager& manager,
|
||||
LOG(INFO) << "Route for Vehicle " << vehicle_id << ":";
|
||||
int64_t index = routing.Start(vehicle_id);
|
||||
std::stringstream route;
|
||||
while (routing.IsEnd(index) == false) {
|
||||
while (!routing.IsEnd(index)) {
|
||||
const IntVar* time_var = time_dimension.CumulVar(index);
|
||||
route << manager.IndexToNode(index).value() << " Time("
|
||||
<< solution.Value(time_var) << ") -> ";
|
||||
@@ -128,7 +128,8 @@ void VrpBreaks() {
|
||||
// Create and register a transit callback.
|
||||
// [START transit_callback]
|
||||
const int transit_callback_index = routing.RegisterTransitCallback(
|
||||
[&data, &manager](int64_t from_index, int64_t to_index) -> int64_t {
|
||||
[&data, &manager](const int64_t from_index,
|
||||
const int64_t to_index) -> int64_t {
|
||||
// Convert from routing variable Index to distance matrix NodeIndex.
|
||||
int from_node = manager.IndexToNode(from_index).value();
|
||||
int to_node = manager.IndexToNode(to_index).value();
|
||||
|
||||
@@ -4,62 +4,65 @@ This directory contains data structures and algorithms for graph and
|
||||
network flow problems.
|
||||
|
||||
It contains in particular:
|
||||
* a compact and efficient graph representation
|
||||
([EbertGraph](https://dl.acm.org/doi/abs/10.1145/214762.214769)),
|
||||
* a compact and efficient graph representation,
|
||||
[`EbertGraph`](https://dl.acm.org/doi/abs/10.1145/214762.214769),
|
||||
which is used for most of the algorithms herein, unless specified otherwise.
|
||||
* well-tuned algorithms (for example shortest paths and
|
||||
[Hamiltonian paths](https://en.wikipedia.org/wiki/Hamiltonian_path).)
|
||||
* hard-to-find algorithms (Hamiltonian paths, push-relabel flow algorithms.)
|
||||
* other, more common algorithm, that are useful to use with EbertGraph.
|
||||
* well-tuned algorithms (for example shortest, paths and
|
||||
[Hamiltonian paths](https://en.wikipedia.org/wiki/Hamiltonian_path)).
|
||||
* hard-to-find algorithms (Hamiltonian paths, push-relabel flow algorithms).
|
||||
* other, more common algorithm, that are useful to use with `EbertGraph`.
|
||||
|
||||
Graph representations:
|
||||
* [ebert_graph.h](./ebert_graph.h): Entry point for a directed graph class.
|
||||
|
||||
* [digraph.h](./digraph.h): Entry point for a directed graph class.
|
||||
* [ebert_graph.h](./ebert_graph.h): entry point for a directed graph class.
|
||||
* [digraph.h](./digraph.h): entry point for a directed graph class.
|
||||
To be deprecated by `ebert_graph.h`.
|
||||
|
||||
Paths:
|
||||
* [shortestpaths.h](./shortestpaths.h): Entry point for shortest path computations.
|
||||
* [shortestpaths.h](./shortestpaths.h): entry point for shortest paths.
|
||||
Includes [Dijkstra](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) and
|
||||
[Bellman-Ford](https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm) algorithms.
|
||||
|
||||
* [hamiltonian_path.h](./hamiltonian_path.h): Entry point for computing minimum
|
||||
[Bellman-Ford](https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm)
|
||||
algorithms. These implementations are being deprecated.
|
||||
* [hamiltonian_path.h](./hamiltonian_path.h): entry point for computing minimum
|
||||
[Hamiltonian paths](https://en.wikipedia.org/wiki/Hamiltonian_path) and
|
||||
cycles on directed graphs with costs on arcs, using a dynamic-programming
|
||||
algorithm (Does not need `ebert_graph.h` or `digraph.h`.)
|
||||
algorithm. (It does not need `ebert_graph.h` or `digraph.h`.)
|
||||
|
||||
Graph decompositions:
|
||||
* [connected_components.h](./connected_components.h): Entry point for computing connected
|
||||
components in an undirected graph. (Does not need `ebert_graph.h` or `digraph.h`.)
|
||||
* [connected_components.h](./connected_components.h): entry point for computing
|
||||
connected components in an undirected graph. (It does not need `ebert_graph.h`
|
||||
or `digraph.h`.)
|
||||
|
||||
* [strongly_connected_components.h](./strongly_connected_components.h): Entry point for
|
||||
computing the strongly connected components of a directed graph, based on an algorithm of Tarjan.
|
||||
* [strongly_connected_components.h](./strongly_connected_components.h): entry
|
||||
point for computing the strongly connected components of a directed graph,
|
||||
based on Tarjan's algorithm.
|
||||
|
||||
* [cliques.h](./cliques.h): Entry point for computing maximum cliques and clique covers in a directed graph,
|
||||
based on the Bron-Kerbosch algorithm. (Does not need `ebert_graph.h` or `digraph.h`.)
|
||||
* [cliques.h](./cliques.h): entry point for computing maximum cliques and
|
||||
clique covers in a directed graph, based on the Bron-Kerbosch algorithm.
|
||||
(It does not need `ebert_graph.h` or `digraph.h`.)
|
||||
|
||||
Flow algorithms:
|
||||
* [linear_assignment.h](./linear_assignment.h): Entry point for solving linear sum assignment problems
|
||||
(classical assignment problems where the total cost is the sum of the costs
|
||||
of each arc used) on directed graphs with arc costs, based on a push-relabel
|
||||
algorithm of Goldberg and Kennedy.
|
||||
* [linear_assignment.h](./linear_assignment.h): entry point for solving linear
|
||||
sum assignment problems (classical assignment problems where the total cost is
|
||||
the sum of the costs of each arc used) on directed graphs with arc costs,
|
||||
based on the Goldberg-Kennedy push-relabel algorithm.
|
||||
|
||||
* [max_flow.h](./max_flow.h): Entry point for computing maximum flows on directed graphs with
|
||||
arc capacities, based on a push-relabel algorithm of Goldberg and Tarjan.
|
||||
* [max_flow.h](./max_flow.h): entry point for computing maximum flows on
|
||||
directed graphs with arc capacities, based on the Goldberg-Tarjan
|
||||
push-relabel algorithm.
|
||||
|
||||
* [min_cost_flow.h](./min_cost_flow.h): Entry point for computing minimum-cost flows on directed
|
||||
graphs with arc capacities, arc costs, and supplies/demands at nodes, based on
|
||||
a push-relabel algorithm of Goldberg and Tarjan.
|
||||
* [min_cost_flow.h](./min_cost_flow.h): entry point for computing minimum-cost
|
||||
flows on directed graphs with arc capacities, arc costs, and supplies/demands
|
||||
at nodes, based on the Goldberg-Tarjan push-relabel algorithm.
|
||||
|
||||
## Wrappers
|
||||
|
||||
* [python](python): the SWIG code that makes the wrapper available in Python,
|
||||
* [python](python): the SWIG code that makes the wrapper available in Python
|
||||
and its unit tests.
|
||||
|
||||
* [java](java): the SWIG code that makes the wrapper available in Java,
|
||||
* [java](java): the SWIG code that makes the wrapper available in Java
|
||||
and its unit tests.
|
||||
|
||||
* [csharp](csharp): the SWIG code that makes the wrapper available in C#,
|
||||
* [csharp](csharp): the SWIG code that makes the wrapper available in C#
|
||||
and its unit tests.
|
||||
|
||||
## Samples
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
set_property(SOURCE graph.i PROPERTY CPLUSPLUS ON)
|
||||
set_property(SOURCE graph.i PROPERTY SWIG_MODULE_NAME operations_research_graph)
|
||||
set_property(SOURCE graph.i PROPERTY COMPILE_DEFINITIONS ${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE graph.i PROPERTY COMPILE_DEFINITIONS
|
||||
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE graph.i PROPERTY COMPILE_OPTIONS
|
||||
-namespace ${DOTNET_PROJECT}.Graph
|
||||
-dllimport google-ortools-native)
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
set_property(SOURCE graph.i PROPERTY CPLUSPLUS ON)
|
||||
set_property(SOURCE graph.i PROPERTY SWIG_MODULE_NAME main)
|
||||
set_property(SOURCE graph.i PROPERTY COMPILE_DEFINITIONS ${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE graph.i PROPERTY COMPILE_DEFINITIONS
|
||||
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT)
|
||||
set_property(SOURCE graph.i PROPERTY COMPILE_OPTIONS
|
||||
-package ${JAVA_PACKAGE}.graph)
|
||||
swig_add_library(jnigraph
|
||||
|
||||
@@ -26,7 +26,6 @@ def code_sample_cc(name):
|
||||
"//ortools/graph:linear_assignment",
|
||||
"//ortools/graph:max_flow",
|
||||
"//ortools/graph:min_cost_flow",
|
||||
"//ortools/graph:shortestpaths",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -42,7 +41,6 @@ def code_sample_cc(name):
|
||||
"//ortools/graph:linear_assignment",
|
||||
"//ortools/graph:max_flow",
|
||||
"//ortools/graph:min_cost_flow",
|
||||
"//ortools/graph:shortestpaths",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
set_property(SOURCE init.i PROPERTY CPLUSPLUS ON)
|
||||
set_property(SOURCE init.i PROPERTY SWIG_MODULE_NAME operations_research_init)
|
||||
set_property(SOURCE init.i PROPERTY COMPILE_DEFINITIONS ${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE init.i PROPERTY COMPILE_DEFINITIONS
|
||||
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE init.i PROPERTY COMPILE_OPTIONS
|
||||
-namespace ${DOTNET_PROJECT}.Init
|
||||
-dllimport google-ortools-native)
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
set_property(SOURCE init.i PROPERTY CPLUSPLUS ON)
|
||||
set_property(SOURCE init.i PROPERTY SWIG_MODULE_NAME main)
|
||||
set_property(SOURCE init.i PROPERTY COMPILE_DEFINITIONS ${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE init.i PROPERTY COMPILE_DEFINITIONS
|
||||
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE init.i PROPERTY COMPILE_OPTIONS
|
||||
-package ${JAVA_PACKAGE}.init)
|
||||
swig_add_library(jniinit
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
set_property(SOURCE init.i PROPERTY CPLUSPLUS ON)
|
||||
set_property(SOURCE init.i PROPERTY SWIG_MODULE_NAME pywrapinit)
|
||||
set_property(SOURCE init.i PROPERTY COMPILE_DEFINITIONS ${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE init.i PROPERTY COMPILE_DEFINITIONS
|
||||
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
swig_add_library(pywrapinit
|
||||
TYPE MODULE
|
||||
LANGUAGE python
|
||||
|
||||
@@ -110,7 +110,7 @@ Here some dev-note concerning this `POM.xml`.
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna-platform</artifactId>
|
||||
<version>5.12.1</version>
|
||||
<version>5.13.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
@@ -120,6 +120,7 @@ public class Loader {
|
||||
// Load the native library
|
||||
System.load(tempPath.resolve(RESOURCE_PATH)
|
||||
.resolve(System.mapLibraryName("jniortools"))
|
||||
.toAbsolutePath()
|
||||
.toString());
|
||||
loaded = true;
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna-platform</artifactId>
|
||||
<version>5.12.1</version>
|
||||
<version>5.13.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna-platform</artifactId>
|
||||
<version>5.12.1</version>
|
||||
<version>5.13.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
|
||||
@@ -58,8 +58,8 @@ cc_proto_library(
|
||||
|
||||
py_proto_library(
|
||||
name = "linear_solver_py_pb2",
|
||||
deps = [":linear_solver_proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":linear_solver_proto"],
|
||||
)
|
||||
|
||||
# You can include the interfaces to different solvers by invoking '--define'
|
||||
@@ -77,8 +77,8 @@ cc_library(
|
||||
"gurobi_interface.cc",
|
||||
"highs_interface.cc",
|
||||
"linear_expr.cc",
|
||||
"linear_solver_callback.cc",
|
||||
"linear_solver.cc",
|
||||
"linear_solver_callback.cc",
|
||||
"lpi_glop.cpp",
|
||||
"pdlp_interface.cc",
|
||||
"sat_interface.cc",
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
set_property(SOURCE linear_solver.i PROPERTY CPLUSPLUS ON)
|
||||
set_property(SOURCE linear_solver.i PROPERTY SWIG_MODULE_NAME operations_research_linear_solver)
|
||||
set_property(SOURCE linear_solver.i PROPERTY COMPILE_DEFINITIONS ${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE linear_solver.i PROPERTY COMPILE_DEFINITIONS
|
||||
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE linear_solver.i PROPERTY COMPILE_OPTIONS
|
||||
-namespace ${DOTNET_PROJECT}.LinearSolver
|
||||
-dllimport google-ortools-native)
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
set_property(SOURCE linear_solver.i PROPERTY CPLUSPLUS ON)
|
||||
set_property(SOURCE linear_solver.i PROPERTY SWIG_MODULE_NAME main)
|
||||
set_property(SOURCE linear_solver.i PROPERTY COMPILE_DEFINITIONS ${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE linear_solver.i PROPERTY COMPILE_DEFINITIONS
|
||||
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE linear_solver.i PROPERTY COMPILE_OPTIONS
|
||||
-package ${JAVA_PACKAGE}.linearsolver)
|
||||
swig_add_library(jnilinear_solver
|
||||
@@ -30,7 +31,8 @@ target_link_libraries(jnilinear_solver PRIVATE ortools::ortools)
|
||||
|
||||
set_property(SOURCE modelbuilder.i PROPERTY CPLUSPLUS ON)
|
||||
set_property(SOURCE modelbuilder.i PROPERTY SWIG_MODULE_NAME main)
|
||||
set_property(SOURCE modelbuilder.i PROPERTY COMPILE_DEFINITIONS ${OR_TOOLS_COMPILE_DEFINITIONS})
|
||||
set_property(SOURCE modelbuilder.i PROPERTY COMPILE_DEFINITIONS
|
||||
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
|
||||
set_property(SOURCE modelbuilder.i PROPERTY COMPILE_OPTIONS
|
||||
-package ${JAVA_PACKAGE}.modelbuilder)
|
||||
swig_add_library(jnimodelbuilder
|
||||
|
||||
@@ -203,7 +203,6 @@ from ortools.linear_solver.linear_solver_natural_api import VariableExpr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static double Infinity() { return operations_research::MPSolver::infinity(); }
|
||||
void SetTimeLimit(int64_t x) { $self->set_time_limit(x); }
|
||||
int64_t WallTime() const { return $self->wall_time(); }
|
||||
|
||||
@@ -42,7 +42,7 @@ def main():
|
||||
# [START variables]
|
||||
# x[i, j] is an array of 0-1 variables, which will be 1
|
||||
# if worker i is assigned to task j.
|
||||
x = model.new_bool_var_array(shape=[num_workers, num_tasks], name='x')
|
||||
x = model.new_bool_var_array(shape=[num_workers, num_tasks], name='x') # pytype: disable=wrong-arg-types # numpy-scalars
|
||||
# [END variables]
|
||||
|
||||
# Constraints
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#define OR_TOOLS_LP_DATA_SPARSE_VECTOR_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@@ -88,6 +88,7 @@ proto_library(
|
||||
deps = [
|
||||
"//ortools/glop:parameters_proto",
|
||||
"//ortools/gscip:gscip_proto",
|
||||
"//ortools/math_opt/solvers:glpk_proto",
|
||||
"//ortools/math_opt/solvers:gurobi_proto",
|
||||
"//ortools/sat:sat_parameters_proto",
|
||||
"@com_google_protobuf//:duration_proto",
|
||||
@@ -148,3 +149,14 @@ cc_proto_library(
|
||||
":sparse_containers_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "infeasible_subsystem_cc_proto",
|
||||
deps = [":infeasible_subsystem_proto"],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "infeasible_subsystem_proto",
|
||||
srcs = ["infeasible_subsystem.proto"],
|
||||
deps = [":result_proto"],
|
||||
)
|
||||
|
||||
@@ -20,7 +20,6 @@ cc_library(
|
||||
deps = [
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt/constraints/util:model_util",
|
||||
"//ortools/math_opt/cpp:id_map",
|
||||
"//ortools/math_opt/cpp:variable_and_expressions",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"@com_google_absl//absl/strings",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/constraints/util/model_util.h"
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
@@ -27,7 +28,10 @@ namespace operations_research::math_opt {
|
||||
|
||||
BoundedLinearExpression IndicatorConstraint::ImpliedConstraint() const {
|
||||
const IndicatorConstraintData& data = storage()->constraint_data(id_);
|
||||
LinearExpression expr = ToLinearExpression(*storage_, data.linear_terms, 0.0);
|
||||
// NOTE: The following makes a copy of `data.linear_terms`. This can be made
|
||||
// more efficient if the need arises.
|
||||
LinearExpression expr = ToLinearExpression(
|
||||
*storage_, {.coeffs = data.linear_terms, .offset = 0.0});
|
||||
return data.lower_bound <= std::move(expr) <= data.upper_bound;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/constraints/util/model_util.h"
|
||||
#include "ortools/math_opt/cpp/id_map.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
|
||||
@@ -79,11 +78,6 @@ class IndicatorConstraint {
|
||||
IndicatorConstraintId id_;
|
||||
};
|
||||
|
||||
// Implements the API of std::unordered_map<IndicatorConstraint, V>, but forbids
|
||||
// IndicatorConstraints from different models in the same map.
|
||||
template <typename V>
|
||||
using IndicatorConstraintMap = IdMap<IndicatorConstraint, V>;
|
||||
|
||||
// Streams the name of the constraint, as registered upon constraint creation,
|
||||
// or a short default if none was provided.
|
||||
inline std::ostream& operator<<(std::ostream& ostr,
|
||||
|
||||
@@ -18,16 +18,15 @@ cc_library(
|
||||
srcs = ["quadratic_constraint.cc"],
|
||||
hdrs = ["quadratic_constraint.h"],
|
||||
deps = [
|
||||
"//ortools/base",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt/constraints/util:model_util",
|
||||
"//ortools/math_opt/cpp:id_map",
|
||||
"//ortools/math_opt/cpp:key_types",
|
||||
"//ortools/math_opt/cpp:variable_and_expressions",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/storage:model_storage_types",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"//ortools/math_opt/storage:sparse_matrix",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -24,11 +24,10 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/constraints/util/model_util.h"
|
||||
#include "ortools/math_opt/cpp/id_map.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/cpp/key_types.h"
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
@@ -93,11 +92,6 @@ class QuadraticConstraint {
|
||||
QuadraticConstraintId id_;
|
||||
};
|
||||
|
||||
// Implements the API of std::unordered_map<QuadraticConstraint, V>, but forbids
|
||||
// QuadraticConstraints from different models in the same map.
|
||||
template <typename V>
|
||||
using QuadraticConstraintMap = IdMap<QuadraticConstraint, V>;
|
||||
|
||||
// Streams the name of the constraint, as registered upon constraint creation,
|
||||
// or a short default if none was provided.
|
||||
inline std::ostream& operator<<(std::ostream& ostr,
|
||||
|
||||
62
ortools/math_opt/constraints/second_order_cone/BUILD.bazel
Normal file
62
ortools/math_opt/constraints/second_order_cone/BUILD.bazel
Normal file
@@ -0,0 +1,62 @@
|
||||
# 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.
|
||||
|
||||
package(default_visibility = ["//ortools/math_opt:__subpackages__"])
|
||||
|
||||
cc_library(
|
||||
name = "validator",
|
||||
srcs = ["validator.cc"],
|
||||
hdrs = ["validator.h"],
|
||||
deps = [
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:sparse_containers_cc_proto",
|
||||
"//ortools/math_opt/core:model_summary",
|
||||
"//ortools/math_opt/core:sparse_vector_view",
|
||||
"//ortools/math_opt/validators:linear_expression_validator",
|
||||
"@com_google_absl//absl/status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "storage",
|
||||
srcs = ["storage.cc"],
|
||||
hdrs = ["storage.h"],
|
||||
deps = [
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:model_update_cc_proto",
|
||||
"//ortools/math_opt:sparse_containers_cc_proto",
|
||||
"//ortools/math_opt/storage:atomic_constraint_storage",
|
||||
"//ortools/math_opt/storage:linear_expression_data",
|
||||
"//ortools/math_opt/storage:model_storage_types",
|
||||
"//ortools/math_opt/storage:sorted",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "second_order_cone_constraint",
|
||||
srcs = ["second_order_cone_constraint.cc"],
|
||||
hdrs = ["second_order_cone_constraint.h"],
|
||||
deps = [
|
||||
":storage",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt/constraints/util:model_util",
|
||||
"//ortools/math_opt/cpp:variable_and_expressions",
|
||||
"//ortools/math_opt/storage:linear_expression_data",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,67 @@
|
||||
// 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/math_opt/constraints/second_order_cone/second_order_cone_constraint.h"
|
||||
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/constraints/second_order_cone/storage.h"
|
||||
#include "ortools/math_opt/constraints/util/model_util.h"
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
#include "ortools/math_opt/storage/linear_expression_data.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
LinearExpression SecondOrderConeConstraint::UpperBound() const {
|
||||
return ToLinearExpression(*storage_,
|
||||
storage()->constraint_data(id_).upper_bound);
|
||||
}
|
||||
|
||||
std::vector<LinearExpression> SecondOrderConeConstraint::ArgumentsToNorm()
|
||||
const {
|
||||
const SecondOrderConeConstraintData& data = storage()->constraint_data(id_);
|
||||
std::vector<LinearExpression> args;
|
||||
args.reserve(data.arguments_to_norm.size());
|
||||
for (const LinearExpressionData& arg_data : data.arguments_to_norm) {
|
||||
args.push_back(ToLinearExpression(*storage_, arg_data));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
std::string SecondOrderConeConstraint::ToString() const {
|
||||
if (!storage()->has_constraint(id_)) {
|
||||
return std::string(kDeletedConstraintDefaultDescription);
|
||||
}
|
||||
const SecondOrderConeConstraintData& data = storage()->constraint_data(id_);
|
||||
std::stringstream str;
|
||||
str << "||{";
|
||||
bool leading_comma = false;
|
||||
for (const LinearExpressionData& arg_data : data.arguments_to_norm) {
|
||||
if (leading_comma) {
|
||||
str << ", ";
|
||||
}
|
||||
leading_comma = true;
|
||||
str << ToLinearExpression(*storage_, arg_data);
|
||||
}
|
||||
str << "}||₂ ≤ " << ToLinearExpression(*storage_, data.upper_bound);
|
||||
return str.str();
|
||||
}
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
@@ -0,0 +1,143 @@
|
||||
// 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.
|
||||
|
||||
// IWYU pragma: private, include "ortools/math_opt/cpp/math_opt.h"
|
||||
// IWYU pragma: friend "ortools/math_opt/cpp/.*"
|
||||
#ifndef OR_TOOLS_MATH_OPT_CONSTRAINTS_SECOND_ORDER_CONE_SECOND_ORDER_CONE_CONSTRAINT_H_
|
||||
#define OR_TOOLS_MATH_OPT_CONSTRAINTS_SECOND_ORDER_CONE_SECOND_ORDER_CONE_CONSTRAINT_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/constraints/util/model_util.h"
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
// A value type that references a second-order cone constraint from
|
||||
// ModelStorage. Usually this type is passed by copy.
|
||||
class SecondOrderConeConstraint {
|
||||
public:
|
||||
// The typed integer used for ids.
|
||||
using IdType = SecondOrderConeConstraintId;
|
||||
|
||||
inline SecondOrderConeConstraint(const ModelStorage* storage,
|
||||
SecondOrderConeConstraintId id);
|
||||
|
||||
inline int64_t id() const;
|
||||
|
||||
inline SecondOrderConeConstraintId typed_id() const;
|
||||
inline const ModelStorage* storage() const;
|
||||
|
||||
inline absl::string_view name() const;
|
||||
|
||||
// Returns "upper_bound" with respect to a constraint of the form
|
||||
// ||arguments_to_norm||₂ ≤ upper_bound.
|
||||
LinearExpression UpperBound() const;
|
||||
|
||||
// Returns "arguments_to_norm" with respect to a constraint of the form
|
||||
// ||arguments_to_norm||₂ ≤ upper_bound.
|
||||
std::vector<LinearExpression> ArgumentsToNorm() const;
|
||||
|
||||
// Returns all variables that appear in the second-order cone constraint with
|
||||
// a nonzero coefficient. Order is not defined.
|
||||
inline std::vector<Variable> NonzeroVariables() const;
|
||||
|
||||
// Returns a detailed string description of the contents of the constraint
|
||||
// (not its name, use `<<` for that instead).
|
||||
std::string ToString() const;
|
||||
|
||||
friend inline bool operator==(const SecondOrderConeConstraint& lhs,
|
||||
const SecondOrderConeConstraint& rhs);
|
||||
friend inline bool operator!=(const SecondOrderConeConstraint& lhs,
|
||||
const SecondOrderConeConstraint& rhs);
|
||||
template <typename H>
|
||||
friend H AbslHashValue(H h, const SecondOrderConeConstraint& constraint);
|
||||
friend std::ostream& operator<<(std::ostream& ostr,
|
||||
const SecondOrderConeConstraint& constraint);
|
||||
|
||||
private:
|
||||
const ModelStorage* storage_;
|
||||
SecondOrderConeConstraintId id_;
|
||||
};
|
||||
|
||||
// Streams the name of the constraint, as registered upon constraint creation,
|
||||
// or a short default if none was provided.
|
||||
inline std::ostream& operator<<(std::ostream& ostr,
|
||||
const SecondOrderConeConstraint& constraint);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Inline function implementations
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int64_t SecondOrderConeConstraint::id() const { return id_.value(); }
|
||||
|
||||
SecondOrderConeConstraintId SecondOrderConeConstraint::typed_id() const {
|
||||
return id_;
|
||||
}
|
||||
|
||||
const ModelStorage* SecondOrderConeConstraint::storage() const {
|
||||
return storage_;
|
||||
}
|
||||
|
||||
absl::string_view SecondOrderConeConstraint::name() const {
|
||||
if (storage_->has_constraint(id_)) {
|
||||
return storage_->constraint_data(id_).name;
|
||||
}
|
||||
return kDeletedConstraintDefaultDescription;
|
||||
}
|
||||
|
||||
std::vector<Variable> SecondOrderConeConstraint::NonzeroVariables() const {
|
||||
return AtomicConstraintNonzeroVariables(*storage_, id_);
|
||||
}
|
||||
|
||||
bool operator==(const SecondOrderConeConstraint& lhs,
|
||||
const SecondOrderConeConstraint& rhs) {
|
||||
return lhs.id_ == rhs.id_ && lhs.storage_ == rhs.storage_;
|
||||
}
|
||||
|
||||
bool operator!=(const SecondOrderConeConstraint& lhs,
|
||||
const SecondOrderConeConstraint& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template <typename H>
|
||||
H AbslHashValue(H h, const SecondOrderConeConstraint& constraint) {
|
||||
return H::combine(std::move(h), constraint.id_.value(), constraint.storage_);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& ostr,
|
||||
const SecondOrderConeConstraint& constraint) {
|
||||
// TODO(b/170992529): handle quoting of invalid characters in the name.
|
||||
const absl::string_view name = constraint.name();
|
||||
if (name.empty()) {
|
||||
ostr << "__soc_con#" << constraint.id() << "__";
|
||||
} else {
|
||||
ostr << name;
|
||||
}
|
||||
return ostr;
|
||||
}
|
||||
|
||||
SecondOrderConeConstraint::SecondOrderConeConstraint(
|
||||
const ModelStorage* const storage, const SecondOrderConeConstraintId id)
|
||||
: storage_(storage), id_(id) {}
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_CONSTRAINTS_SECOND_ORDER_CONE_SECOND_ORDER_CONE_CONSTRAINT_H_
|
||||
75
ortools/math_opt/constraints/second_order_cone/storage.cc
Normal file
75
ortools/math_opt/constraints/second_order_cone/storage.cc
Normal file
@@ -0,0 +1,75 @@
|
||||
// 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/math_opt/constraints/second_order_cone/storage.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
#include "ortools/math_opt/storage/linear_expression_data.h"
|
||||
#include "ortools/math_opt/storage/model_storage_types.h"
|
||||
#include "ortools/math_opt/storage/sorted.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
SecondOrderConeConstraintData SecondOrderConeConstraintData::FromProto(
|
||||
const ProtoType& in_proto) {
|
||||
SecondOrderConeConstraintData data;
|
||||
data.upper_bound = LinearExpressionData::FromProto(in_proto.upper_bound());
|
||||
data.arguments_to_norm.reserve(in_proto.arguments_to_norm_size());
|
||||
for (const LinearExpressionProto& expr_proto : in_proto.arguments_to_norm()) {
|
||||
data.arguments_to_norm.push_back(
|
||||
LinearExpressionData::FromProto(expr_proto));
|
||||
}
|
||||
data.name = in_proto.name();
|
||||
return data;
|
||||
}
|
||||
|
||||
typename SecondOrderConeConstraintData::ProtoType
|
||||
SecondOrderConeConstraintData::Proto() const {
|
||||
ProtoType constraint;
|
||||
*constraint.mutable_upper_bound() = upper_bound.Proto();
|
||||
for (const LinearExpressionData& expr : arguments_to_norm) {
|
||||
*constraint.add_arguments_to_norm() = expr.Proto();
|
||||
}
|
||||
constraint.set_name(name);
|
||||
return constraint;
|
||||
}
|
||||
|
||||
std::vector<VariableId> SecondOrderConeConstraintData::RelatedVariables()
|
||||
const {
|
||||
absl::flat_hash_set<VariableId> vars;
|
||||
for (const auto& [var, unused] : upper_bound.coeffs.terms()) {
|
||||
vars.insert(var);
|
||||
}
|
||||
for (const LinearExpressionData& expr : arguments_to_norm) {
|
||||
for (const auto& [var, unused] : expr.coeffs.terms()) {
|
||||
vars.insert(var);
|
||||
}
|
||||
}
|
||||
return std::vector<VariableId>(vars.begin(), vars.end());
|
||||
}
|
||||
|
||||
void SecondOrderConeConstraintData::DeleteVariable(const VariableId var) {
|
||||
upper_bound.coeffs.set(var, 0.0);
|
||||
for (LinearExpressionData& expr : arguments_to_norm) {
|
||||
expr.coeffs.set(var, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
57
ortools/math_opt/constraints/second_order_cone/storage.h
Normal file
57
ortools/math_opt/constraints/second_order_cone/storage.h
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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_MATH_OPT_CONSTRAINTS_SECOND_ORDER_CONE_STORAGE_H_
|
||||
#define OR_TOOLS_MATH_OPT_CONSTRAINTS_SECOND_ORDER_CONE_STORAGE_H_
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/model_update.pb.h"
|
||||
#include "ortools/math_opt/storage/atomic_constraint_storage.h"
|
||||
#include "ortools/math_opt/storage/linear_expression_data.h"
|
||||
#include "ortools/math_opt/storage/model_storage_types.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
// Internal storage representation for a single second-order cone constraint.
|
||||
//
|
||||
// Implements the interface specified for the `ConstraintData` parameter of
|
||||
// `AtomicConstraintStorage`.
|
||||
struct SecondOrderConeConstraintData {
|
||||
using IdType = SecondOrderConeConstraintId;
|
||||
using ProtoType = SecondOrderConeConstraintProto;
|
||||
using UpdatesProtoType = SecondOrderConeConstraintUpdatesProto;
|
||||
|
||||
// The `in_proto` must be in a valid state; see the inline comments on
|
||||
// `SecondOrderConeConstraintProto` for details.
|
||||
static SecondOrderConeConstraintData FromProto(const ProtoType& in_proto);
|
||||
ProtoType Proto() const;
|
||||
std::vector<VariableId> RelatedVariables() const;
|
||||
void DeleteVariable(VariableId var);
|
||||
|
||||
LinearExpressionData upper_bound;
|
||||
std::vector<LinearExpressionData> arguments_to_norm;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AtomicConstraintTraits<SecondOrderConeConstraintId> {
|
||||
using ConstraintData = SecondOrderConeConstraintData;
|
||||
};
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_CONSTRAINTS_SECOND_ORDER_CONE_STORAGE_H_
|
||||
43
ortools/math_opt/constraints/second_order_cone/validator.cc
Normal file
43
ortools/math_opt/constraints/second_order_cone/validator.cc
Normal file
@@ -0,0 +1,43 @@
|
||||
// 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/math_opt/constraints/second_order_cone/validator.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "ortools/base/status_builder.h"
|
||||
#include "ortools/base/status_macros.h"
|
||||
#include "ortools/math_opt/core/model_summary.h"
|
||||
#include "ortools/math_opt/core/sparse_vector_view.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
#include "ortools/math_opt/validators/linear_expression_validator.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
absl::Status ValidateConstraint(
|
||||
const SecondOrderConeConstraintProto& constraint,
|
||||
const IdNameBiMap& variable_universe) {
|
||||
RETURN_IF_ERROR(
|
||||
ValidateLinearExpression(constraint.upper_bound(), variable_universe))
|
||||
<< "invalid `upper_bound`";
|
||||
for (int i = 0; i < constraint.arguments_to_norm_size(); ++i) {
|
||||
const LinearExpressionProto& expression = constraint.arguments_to_norm(i);
|
||||
RETURN_IF_ERROR(ValidateLinearExpression(expression, variable_universe))
|
||||
<< "invalid `arguments_to_norm` at index: " << i;
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
29
ortools/math_opt/constraints/second_order_cone/validator.h
Normal file
29
ortools/math_opt/constraints/second_order_cone/validator.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// 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_MATH_OPT_CONSTRAINTS_SECOND_ORDER_CONE_VALIDATOR_H_
|
||||
#define OR_TOOLS_MATH_OPT_CONSTRAINTS_SECOND_ORDER_CONE_VALIDATOR_H_
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "ortools/math_opt/core/model_summary.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
absl::Status ValidateConstraint(
|
||||
const SecondOrderConeConstraintProto& constraint,
|
||||
const IdNameBiMap& variable_universe);
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_CONSTRAINTS_SECOND_ORDER_CONE_VALIDATOR_H_
|
||||
@@ -33,16 +33,14 @@ cc_library(
|
||||
name = "storage",
|
||||
hdrs = ["storage.h"],
|
||||
deps = [
|
||||
"//ortools/base",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:model_update_cc_proto",
|
||||
"//ortools/math_opt:sparse_containers_cc_proto",
|
||||
"//ortools/math_opt/storage:atomic_constraint_storage",
|
||||
"//ortools/math_opt/storage:model_storage_types",
|
||||
"//ortools/math_opt/storage:sorted",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"//ortools/math_opt/storage:linear_expression_data",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/log:check",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -64,9 +62,10 @@ cc_library(
|
||||
":util",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt/constraints/util:model_util",
|
||||
"//ortools/math_opt/cpp:id_map",
|
||||
"//ortools/math_opt/cpp:variable_and_expressions",
|
||||
"//ortools/math_opt/storage:linear_expression_data",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
@@ -79,9 +78,10 @@ cc_library(
|
||||
":util",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt/constraints/util:model_util",
|
||||
"//ortools/math_opt/cpp:id_map",
|
||||
"//ortools/math_opt/cpp:variable_and_expressions",
|
||||
"//ortools/math_opt/storage:linear_expression_data",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -15,15 +15,17 @@
|
||||
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
#include "ortools/math_opt/storage/linear_expression_data.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
LinearExpression Sos1Constraint::Expression(int index) const {
|
||||
const Sos1ConstraintData::LinearExpression storage_expr =
|
||||
const LinearExpressionData& storage_expr =
|
||||
storage_->constraint_data(id_).expression(index);
|
||||
LinearExpression out_expr = storage_expr.offset;
|
||||
for (const auto [var_id, coeff] : storage_expr.terms) {
|
||||
for (const auto [var_id, coeff] : storage_expr.coeffs.terms()) {
|
||||
out_expr += coeff * Variable(storage_, var_id);
|
||||
}
|
||||
return out_expr;
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/constraints/sos/util.h"
|
||||
#include "ortools/math_opt/constraints/util/model_util.h"
|
||||
#include "ortools/math_opt/cpp/id_map.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
|
||||
@@ -72,11 +71,6 @@ class Sos1Constraint {
|
||||
Sos1ConstraintId id_;
|
||||
};
|
||||
|
||||
// Implements the API of std::unordered_map<Sos1Constraint, V>, but forbids
|
||||
// Sos1Constraints from different models in the same map.
|
||||
template <typename V>
|
||||
using Sos1ConstraintMap = IdMap<Sos1Constraint, V>;
|
||||
|
||||
// Streams the name of the constraint, as registered upon constraint creation,
|
||||
// or a short default if none was provided.
|
||||
inline std::ostream& operator<<(std::ostream& ostr,
|
||||
|
||||
@@ -15,15 +15,17 @@
|
||||
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
#include "ortools/math_opt/storage/linear_expression_data.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
LinearExpression Sos2Constraint::Expression(int index) const {
|
||||
const Sos2ConstraintData::LinearExpression storage_expr =
|
||||
const LinearExpressionData& storage_expr =
|
||||
storage_->constraint_data(id_).expression(index);
|
||||
LinearExpression out_expr = storage_expr.offset;
|
||||
for (const auto [var_id, coeff] : storage_expr.terms) {
|
||||
for (const auto [var_id, coeff] : storage_expr.coeffs.terms()) {
|
||||
out_expr += coeff * Variable(storage_, var_id);
|
||||
}
|
||||
return out_expr;
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/constraints/sos/util.h"
|
||||
#include "ortools/math_opt/constraints/util/model_util.h"
|
||||
#include "ortools/math_opt/cpp/id_map.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
|
||||
@@ -73,11 +72,6 @@ class Sos2Constraint {
|
||||
Sos2ConstraintId id_;
|
||||
};
|
||||
|
||||
// Implements the API of std::unordered_map<Sos2Constraint, V>, but forbids
|
||||
// Sos2Constraints from different models in the same map.
|
||||
template <typename V>
|
||||
using Sos2ConstraintMap = IdMap<Sos2Constraint, V>;
|
||||
|
||||
// Streams the name of the constraint, as registered upon constraint creation,
|
||||
// or a short default if none was provided.
|
||||
inline std::ostream& operator<<(std::ostream& ostr,
|
||||
|
||||
@@ -21,16 +21,14 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/model_update.pb.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
#include "ortools/math_opt/storage/atomic_constraint_storage.h"
|
||||
#include "ortools/math_opt/storage/model_storage_types.h"
|
||||
#include "ortools/math_opt/storage/sorted.h"
|
||||
#include "ortools/math_opt/storage/linear_expression_data.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
namespace internal {
|
||||
@@ -51,14 +49,9 @@ class SosConstraintData {
|
||||
std::is_same<ConstraintId, Sos2ConstraintId>>,
|
||||
"ID type may only be Sos1ConstraintId or Sos2ConstraintId");
|
||||
|
||||
struct LinearExpression {
|
||||
absl::flat_hash_map<VariableId, double> terms;
|
||||
double offset = 0.0;
|
||||
};
|
||||
|
||||
// `weights` must either be empty or the same length as `expressions`. If it
|
||||
// is empty, default weights of 1, 2, ... will be used.
|
||||
SosConstraintData(std::vector<LinearExpression> expressions,
|
||||
SosConstraintData(std::vector<LinearExpressionData> expressions,
|
||||
std::vector<double> weights, std::string name)
|
||||
: expressions_(std::move(expressions)), name_(std::move(name)) {
|
||||
if (!weights.empty()) {
|
||||
@@ -80,7 +73,7 @@ class SosConstraintData {
|
||||
AssertInbounds(index);
|
||||
return weights_.has_value() ? (*weights_)[index] : index + 1;
|
||||
}
|
||||
const LinearExpression& expression(const int index) const {
|
||||
const LinearExpressionData& expression(const int index) const {
|
||||
AssertInbounds(index);
|
||||
return expressions_[index];
|
||||
}
|
||||
@@ -96,7 +89,7 @@ class SosConstraintData {
|
||||
// If present, length must be the same as that of `expressions_`.
|
||||
// If absent, default weights of 1, 2, ... are used.
|
||||
std::optional<std::vector<double>> weights_;
|
||||
std::vector<LinearExpression> expressions_;
|
||||
std::vector<LinearExpressionData> expressions_;
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
@@ -128,13 +121,8 @@ SosConstraintData<ConstraintId> SosConstraintData<ConstraintId>::FromProto(
|
||||
SosConstraintData data;
|
||||
data.name_ = in_proto.name();
|
||||
for (int i = 0; i < num_expressions; ++i) {
|
||||
LinearExpression& expression = data.expressions_.emplace_back();
|
||||
const LinearExpressionProto& proto_expression = in_proto.expressions(i);
|
||||
expression.offset = proto_expression.offset();
|
||||
for (int j = 0; j < proto_expression.ids_size(); ++j) {
|
||||
expression.terms.insert({VariableId(proto_expression.ids(j)),
|
||||
proto_expression.coefficients(j)});
|
||||
}
|
||||
data.expressions_.push_back(
|
||||
LinearExpressionData::FromProto(in_proto.expressions(i)));
|
||||
}
|
||||
// Otherwise proto has default weights, so leave data.weights_ as unset.
|
||||
if (!in_proto.weights().empty()) {
|
||||
@@ -152,13 +140,7 @@ SosConstraintData<ConstraintId>::Proto() const {
|
||||
ProtoType constraint;
|
||||
constraint.set_name(name());
|
||||
for (int i = 0; i < num_expressions(); ++i) {
|
||||
const LinearExpression& expr = expression(i);
|
||||
LinearExpressionProto& proto_expr = *constraint.add_expressions();
|
||||
proto_expr.set_offset(expr.offset);
|
||||
for (const VariableId id : SortedMapKeys(expr.terms)) {
|
||||
proto_expr.add_ids(id.value());
|
||||
proto_expr.add_coefficients(expr.terms.at(id));
|
||||
}
|
||||
*constraint.add_expressions() = expression(i).Proto();
|
||||
}
|
||||
if (weights_.has_value()) {
|
||||
for (int i = 0; i < num_expressions(); ++i) {
|
||||
@@ -172,8 +154,8 @@ template <typename ConstraintId>
|
||||
std::vector<VariableId> SosConstraintData<ConstraintId>::RelatedVariables()
|
||||
const {
|
||||
absl::flat_hash_set<VariableId> vars;
|
||||
for (const LinearExpression& expression : expressions_) {
|
||||
for (const auto [var, _] : expression.terms) {
|
||||
for (const LinearExpressionData& expression : expressions_) {
|
||||
for (const auto [var, _] : expression.coeffs.terms()) {
|
||||
vars.insert(var);
|
||||
}
|
||||
}
|
||||
@@ -182,8 +164,8 @@ std::vector<VariableId> SosConstraintData<ConstraintId>::RelatedVariables()
|
||||
|
||||
template <typename ConstraintId>
|
||||
void SosConstraintData<ConstraintId>::DeleteVariable(const VariableId var) {
|
||||
for (LinearExpression& expression : expressions_) {
|
||||
expression.terms.erase(var);
|
||||
for (LinearExpressionData& expression : expressions_) {
|
||||
expression.coeffs.erase(var);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ cc_library(
|
||||
srcs = ["model_util.cc"],
|
||||
hdrs = ["model_util.h"],
|
||||
deps = [
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt/cpp:variable_and_expressions",
|
||||
"//ortools/math_opt/storage:linear_expression_data",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"@com_google_absl//absl/algorithm:container",
|
||||
|
||||
@@ -15,28 +15,28 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
#include "ortools/math_opt/storage/linear_expression_data.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
LinearExpression ToLinearExpression(const ModelStorage& storage,
|
||||
const SparseCoefficientMap& coeffs,
|
||||
const double offset) {
|
||||
LinearExpression expr = offset;
|
||||
for (const auto [var_id, coeff] : coeffs.terms()) {
|
||||
const LinearExpressionData& expr_data) {
|
||||
LinearExpression expr = expr_data.offset;
|
||||
for (const auto [var_id, coeff] : expr_data.coeffs.terms()) {
|
||||
expr += coeff * Variable(&storage, var_id);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
std::pair<SparseCoefficientMap, double> FromLinearExpression(
|
||||
const LinearExpression& expression) {
|
||||
LinearExpressionData FromLinearExpression(const LinearExpression& expression) {
|
||||
SparseCoefficientMap coeffs;
|
||||
for (const auto [var, coeff] : expression.terms()) {
|
||||
coeffs.set(var.typed_id(), coeff);
|
||||
}
|
||||
return {coeffs, expression.offset()};
|
||||
return {.coeffs = std::move(coeffs), .offset = expression.offset()};
|
||||
}
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
#ifndef OR_TOOLS_MATH_OPT_CONSTRAINTS_UTIL_MODEL_UTIL_H_
|
||||
#define OR_TOOLS_MATH_OPT_CONSTRAINTS_UTIL_MODEL_UTIL_H_
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
#include "ortools/math_opt/storage/linear_expression_data.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
@@ -33,12 +33,10 @@ constexpr absl::string_view kDeletedConstraintDefaultDescription =
|
||||
// Converts data from "raw ID" format to a LinearExpression, in the C++ API,
|
||||
// associated with `storage`.
|
||||
LinearExpression ToLinearExpression(const ModelStorage& storage,
|
||||
const SparseCoefficientMap& coeffs,
|
||||
double offset);
|
||||
const LinearExpressionData& expr_data);
|
||||
|
||||
// Converts a `LinearExpression` to the associated "raw ID" format.
|
||||
std::pair<SparseCoefficientMap, double> FromLinearExpression(
|
||||
const LinearExpression& expression);
|
||||
LinearExpressionData FromLinearExpression(const LinearExpression& expression);
|
||||
|
||||
template <typename IdType>
|
||||
std::vector<Variable> AtomicConstraintNonzeroVariables(
|
||||
|
||||
@@ -23,10 +23,14 @@ cc_library(
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/math_opt:callback_cc_proto",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:model_parameters_cc_proto",
|
||||
"//ortools/math_opt:model_update_cc_proto",
|
||||
"//ortools/math_opt:result_cc_proto",
|
||||
"//ortools/math_opt:solution_cc_proto",
|
||||
"//ortools/math_opt:sparse_containers_cc_proto",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
@@ -52,13 +56,14 @@ cc_library(
|
||||
srcs = ["model_summary.cc"],
|
||||
hdrs = ["model_summary.h"],
|
||||
deps = [
|
||||
"//ortools/base",
|
||||
"//ortools/base:linked_hash_map",
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:model_update_cc_proto",
|
||||
"//ortools/util:status_macros",
|
||||
"@com_google_absl//absl/algorithm:container",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
@@ -73,10 +78,10 @@ cc_library(
|
||||
deps = [
|
||||
":non_streamable_solver_init_arguments",
|
||||
":solve_interrupter",
|
||||
"//ortools/base",
|
||||
"//ortools/base:map_util",
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/math_opt:callback_cc_proto",
|
||||
"//ortools/math_opt:infeasible_subsystem_cc_proto",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:model_parameters_cc_proto",
|
||||
"//ortools/math_opt:model_update_cc_proto",
|
||||
@@ -85,7 +90,7 @@ cc_library(
|
||||
"//ortools/port:proto_utils",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
@@ -103,27 +108,26 @@ cc_library(
|
||||
":solve_interrupter",
|
||||
":solver_debug",
|
||||
":solver_interface",
|
||||
"//ortools/base",
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/math_opt:callback_cc_proto",
|
||||
"//ortools/math_opt:infeasible_subsystem_cc_proto",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:model_parameters_cc_proto",
|
||||
"//ortools/math_opt:model_update_cc_proto",
|
||||
"//ortools/math_opt:parameters_cc_proto",
|
||||
"//ortools/math_opt:result_cc_proto",
|
||||
"//ortools/math_opt/validators:callback_validator",
|
||||
"//ortools/math_opt/validators:infeasible_subsystem_validator",
|
||||
"//ortools/math_opt/validators:model_parameters_validator",
|
||||
"//ortools/math_opt/validators:model_validator",
|
||||
"//ortools/math_opt/validators:result_validator",
|
||||
"//ortools/math_opt/validators:solve_parameters_validator",
|
||||
"//ortools/port:proto_utils",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -206,11 +210,21 @@ cc_library(
|
||||
srcs = ["concurrent_calls_guard.cc"],
|
||||
hdrs = ["concurrent_calls_guard.h"],
|
||||
deps = [
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
#"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "empty_bounds",
|
||||
srcs = ["empty_bounds.cc"],
|
||||
hdrs = ["empty_bounds.h"],
|
||||
deps = [
|
||||
"//ortools/math_opt:result_cc_proto",
|
||||
"//ortools/util:fp_roundtrip_conv",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
#include "ortools/math_opt/core/concurrent_calls_guard.h"
|
||||
|
||||
#include "absl/base/thread_annotations.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "absl/log/check.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
|
||||
46
ortools/math_opt/core/empty_bounds.cc
Normal file
46
ortools/math_opt/core/empty_bounds.cc
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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/math_opt/core/empty_bounds.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "ortools/math_opt/result.pb.h"
|
||||
#include "ortools/util/fp_roundtrip_conv.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
constexpr double kInf = std::numeric_limits<double>::infinity();
|
||||
|
||||
SolveResultProto ResultForIntegerInfeasible(const bool is_maximize,
|
||||
const int64_t bad_variable_id,
|
||||
const double lb, const double ub) {
|
||||
SolveResultProto result;
|
||||
result.mutable_termination()->set_reason(TERMINATION_REASON_INFEASIBLE);
|
||||
result.mutable_termination()->set_detail(absl::StrCat(
|
||||
"Problem had one or more integer variables with no integers "
|
||||
"in domain, e.g. integer variable with id: ",
|
||||
bad_variable_id, " had bounds: [", RoundTripDoubleFormat::ToString(lb),
|
||||
", ", RoundTripDoubleFormat::ToString(ub), "]."));
|
||||
result.mutable_solve_stats()->mutable_problem_status()->set_primal_status(
|
||||
FEASIBILITY_STATUS_INFEASIBLE);
|
||||
result.mutable_solve_stats()->mutable_problem_status()->set_dual_status(
|
||||
FEASIBILITY_STATUS_UNDETERMINED);
|
||||
const double objective_value = is_maximize ? -kInf : kInf;
|
||||
result.mutable_solve_stats()->set_best_primal_bound(objective_value);
|
||||
result.mutable_solve_stats()->set_best_dual_bound(-objective_value);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
34
ortools/math_opt/core/empty_bounds.h
Normal file
34
ortools/math_opt/core/empty_bounds.h
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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_MATH_OPT_CORE_EMPTY_BOUNDS_H_
|
||||
#define OR_TOOLS_MATH_OPT_CORE_EMPTY_BOUNDS_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "ortools/math_opt/result.pb.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
// Returns an "infeasible" result for models where the infeasibility is caused
|
||||
// by an integer variable whose bounds are nonempty but contain no integers.
|
||||
//
|
||||
// Callers should make sure to set the SolveResultProto.solve_stats.solve_time
|
||||
// field before returning the result.
|
||||
SolveResultProto ResultForIntegerInfeasible(bool is_maximize,
|
||||
int64_t bad_variable_id, double lb,
|
||||
double ub);
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_CORE_EMPTY_BOUNDS_H_
|
||||
@@ -19,16 +19,18 @@
|
||||
#include <string>
|
||||
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/base/status_builder.h"
|
||||
#include "ortools/math_opt/callback.pb.h"
|
||||
#include "ortools/math_opt/core/sparse_vector_view.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/model_parameters.pb.h"
|
||||
#include "ortools/math_opt/model_update.pb.h"
|
||||
#include "ortools/math_opt/result.pb.h"
|
||||
#include "ortools/math_opt/solution.pb.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
|
||||
namespace operations_research {
|
||||
@@ -69,6 +71,42 @@ SparseVectorFilterPredicate::SparseVectorFilterPredicate(
|
||||
}
|
||||
}
|
||||
|
||||
SparseDoubleVectorProto FilterSparseVector(
|
||||
const SparseDoubleVectorProto& input,
|
||||
const SparseVectorFilterProto& filter) {
|
||||
SparseDoubleVectorProto result;
|
||||
SparseVectorFilterPredicate predicate(filter);
|
||||
for (const auto [id, val] : MakeView(input)) {
|
||||
if (predicate.AcceptsAndUpdate(id, val)) {
|
||||
result.add_ids(id);
|
||||
result.add_values(val);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ApplyAllFilters(const ModelSolveParametersProto& model_solve_params,
|
||||
SolutionProto& solution) {
|
||||
if (model_solve_params.has_variable_values_filter() &&
|
||||
solution.has_primal_solution()) {
|
||||
*solution.mutable_primal_solution()->mutable_variable_values() =
|
||||
FilterSparseVector(solution.primal_solution().variable_values(),
|
||||
model_solve_params.variable_values_filter());
|
||||
}
|
||||
if (model_solve_params.has_dual_values_filter() &&
|
||||
solution.has_dual_solution()) {
|
||||
*solution.mutable_dual_solution()->mutable_dual_values() =
|
||||
FilterSparseVector(solution.dual_solution().dual_values(),
|
||||
model_solve_params.dual_values_filter());
|
||||
}
|
||||
if (model_solve_params.has_reduced_costs_filter() &&
|
||||
solution.has_dual_solution()) {
|
||||
*solution.mutable_dual_solution()->mutable_reduced_costs() =
|
||||
FilterSparseVector(solution.dual_solution().reduced_costs(),
|
||||
model_solve_params.reduced_costs_filter());
|
||||
}
|
||||
}
|
||||
|
||||
absl::flat_hash_set<CallbackEventProto> EventSet(
|
||||
const CallbackRegistrationProto& callback_registration) {
|
||||
// Here we don't use for-range loop since for repeated enum fields, the type
|
||||
@@ -145,7 +183,7 @@ absl::Status ModelIsSupported(const ModelProto& model,
|
||||
if (const SupportType support = support_menu.multi_objectives;
|
||||
support != SupportType::kSupported) {
|
||||
if (!model.auxiliary_objectives().empty()) {
|
||||
return error_status("multi objectives", support);
|
||||
return error_status("multiple objectives", support);
|
||||
}
|
||||
}
|
||||
if (const SupportType support = support_menu.quadratic_objectives;
|
||||
@@ -165,6 +203,12 @@ absl::Status ModelIsSupported(const ModelProto& model,
|
||||
return error_status("quadratic constraints", support);
|
||||
}
|
||||
}
|
||||
if (const SupportType support = support_menu.second_order_cone_constraints;
|
||||
support != SupportType::kSupported) {
|
||||
if (!model.second_order_cone_constraints().empty()) {
|
||||
return error_status("second-order cone constraints", support);
|
||||
}
|
||||
}
|
||||
if (const SupportType support = support_menu.sos1_constraints;
|
||||
support != SupportType::kSupported) {
|
||||
if (!model.sos1_constraints().empty()) {
|
||||
@@ -243,6 +287,12 @@ bool UpdateIsSupported(const ModelUpdateProto& update,
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (support_menu.second_order_cone_constraints != SupportType::kSupported) {
|
||||
if (contains_new_or_deleted_constraints(
|
||||
update.second_order_cone_constraint_updates())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (support_menu.sos1_constraints != SupportType::kSupported) {
|
||||
if (contains_new_or_deleted_constraints(update.sos1_constraint_updates())) {
|
||||
return false;
|
||||
|
||||
@@ -19,17 +19,18 @@
|
||||
#include <type_traits>
|
||||
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "ortools/math_opt/callback.pb.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/model_parameters.pb.h"
|
||||
#include "ortools/math_opt/model_update.pb.h"
|
||||
#include "ortools/math_opt/result.pb.h"
|
||||
#include "ortools/math_opt/solution.pb.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
inline int NumVariables(const VariablesProto& variables) {
|
||||
return variables.ids_size();
|
||||
@@ -89,7 +90,7 @@ class SparseVectorFilterPredicate {
|
||||
// non-optimized builds it will CHECK that this is the case. It updates an
|
||||
// internal counter when filtering by ids.
|
||||
template <typename Value>
|
||||
bool AcceptsAndUpdate(const int64_t id, const Value& value);
|
||||
bool AcceptsAndUpdate(int64_t id, const Value& value);
|
||||
|
||||
private:
|
||||
const SparseVectorFilterProto& filter_;
|
||||
@@ -105,19 +106,39 @@ class SparseVectorFilterPredicate {
|
||||
#endif // NDEBUG
|
||||
};
|
||||
|
||||
// Applies filter to each element of input and returns the elements that remain.
|
||||
//
|
||||
// TODO(b/261603235): this function is not very efficient, decide if this
|
||||
// matters.
|
||||
SparseDoubleVectorProto FilterSparseVector(
|
||||
const SparseDoubleVectorProto& input,
|
||||
const SparseVectorFilterProto& filter);
|
||||
|
||||
// Applies the primal, dual and reduced costs filters from model_solve_params
|
||||
// to the primal solution variable values, dual solution dual values, and dual
|
||||
// solution reduced costs, respectively, and overwriting these values with
|
||||
// the results.
|
||||
//
|
||||
// Warning: solution is modified in place.
|
||||
//
|
||||
// TODO(b/261603235): this function is not very efficient, decide if this
|
||||
// matters.
|
||||
void ApplyAllFilters(const ModelSolveParametersProto& model_solve_params,
|
||||
SolutionProto& solution);
|
||||
|
||||
// Returns the callback_registration.request_registration as a set of enums.
|
||||
absl::flat_hash_set<CallbackEventProto> EventSet(
|
||||
const CallbackRegistrationProto& callback_registration);
|
||||
|
||||
// Sets the reason to TERMINATION_REASON_FEASIBLE if feasible = true and
|
||||
// TERMINATION_REASON_NO_SOLUTION_FOUND otherwise.
|
||||
TerminationProto TerminateForLimit(const LimitProto limit, bool feasible,
|
||||
TerminationProto TerminateForLimit(LimitProto limit, bool feasible,
|
||||
absl::string_view detail = {});
|
||||
|
||||
TerminationProto FeasibleTermination(const LimitProto limit,
|
||||
TerminationProto FeasibleTermination(LimitProto limit,
|
||||
absl::string_view detail = {});
|
||||
|
||||
TerminationProto NoSolutionFoundTermination(const LimitProto limit,
|
||||
TerminationProto NoSolutionFoundTermination(LimitProto limit,
|
||||
absl::string_view detail = {});
|
||||
|
||||
TerminationProto TerminateForReason(TerminationReasonProto reason,
|
||||
@@ -134,6 +155,7 @@ struct SupportedProblemStructures {
|
||||
SupportType multi_objectives = SupportType::kNotSupported;
|
||||
SupportType quadratic_objectives = SupportType::kNotSupported;
|
||||
SupportType quadratic_constraints = SupportType::kNotSupported;
|
||||
SupportType second_order_cone_constraints = SupportType::kNotSupported;
|
||||
SupportType sos1_constraints = SupportType::kNotSupported;
|
||||
SupportType sos2_constraints = SupportType::kNotSupported;
|
||||
SupportType indicator_constraints = SupportType::kNotSupported;
|
||||
@@ -195,7 +217,6 @@ bool SparseVectorFilterPredicate::AcceptsAndUpdate(const int64_t id,
|
||||
return id == filter_.filtered_ids(next_filtered_id_index_);
|
||||
}
|
||||
|
||||
} // namespace math_opt
|
||||
} // namespace operations_research
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_CORE_MATH_OPT_PROTO_UTILS_H_
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
#include <utility>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "ortools/base/linked_hash_map.h"
|
||||
#include "ortools/base/status_builder.h"
|
||||
#include "ortools/base/status_macros.h"
|
||||
@@ -122,6 +122,7 @@ ModelSummary::ModelSummary(const bool check_names)
|
||||
auxiliary_objectives(check_names),
|
||||
linear_constraints(check_names),
|
||||
quadratic_constraints(check_names),
|
||||
second_order_cone_constraints(check_names),
|
||||
sos1_constraints(check_names),
|
||||
sos2_constraints(check_names),
|
||||
indicator_constraints(check_names) {}
|
||||
@@ -149,6 +150,10 @@ absl::StatusOr<ModelSummary> ModelSummary::Create(const ModelProto& model,
|
||||
RETURN_IF_ERROR(internal::UpdateBiMapFromMappedData(
|
||||
{}, model.quadratic_constraints(), summary.quadratic_constraints))
|
||||
<< "ModelProto.quadratic_constraints are invalid";
|
||||
RETURN_IF_ERROR(internal::UpdateBiMapFromMappedData(
|
||||
{}, model.second_order_cone_constraints(),
|
||||
summary.second_order_cone_constraints))
|
||||
<< "ModelProto.second_order_cone_constraints are invalid";
|
||||
RETURN_IF_ERROR(internal::UpdateBiMapFromMappedData(
|
||||
{}, model.sos1_constraints(), summary.sos1_constraints))
|
||||
<< "ModelProto.sos1_constraints are invalid";
|
||||
@@ -185,6 +190,12 @@ absl::Status ModelSummary::Update(const ModelUpdateProto& model_update) {
|
||||
model_update.quadratic_constraint_updates().new_constraints(),
|
||||
quadratic_constraints))
|
||||
<< "invalid quadratic constraints";
|
||||
RETURN_IF_ERROR(internal::UpdateBiMapFromMappedData(
|
||||
model_update.second_order_cone_constraint_updates()
|
||||
.deleted_constraint_ids(),
|
||||
model_update.second_order_cone_constraint_updates().new_constraints(),
|
||||
second_order_cone_constraints))
|
||||
<< "invalid second-order cone constraints";
|
||||
RETURN_IF_ERROR(internal::UpdateBiMapFromMappedData(
|
||||
model_update.sos1_constraint_updates().deleted_constraint_ids(),
|
||||
model_update.sos1_constraint_updates().new_constraints(),
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "ortools/base/linked_hash_map.h"
|
||||
#include "ortools/base/status_macros.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
@@ -125,8 +125,6 @@ class IdNameBiMap {
|
||||
nonempty_name_to_id_;
|
||||
};
|
||||
|
||||
// TODO(b/232619901): In the guide for how to add new constraints, include how
|
||||
// this class must updated.
|
||||
struct ModelSummary {
|
||||
explicit ModelSummary(bool check_names = true);
|
||||
static absl::StatusOr<ModelSummary> Create(const ModelProto& model,
|
||||
@@ -138,6 +136,7 @@ struct ModelSummary {
|
||||
IdNameBiMap auxiliary_objectives;
|
||||
IdNameBiMap linear_constraints;
|
||||
IdNameBiMap quadratic_constraints;
|
||||
IdNameBiMap second_order_cone_constraints;
|
||||
IdNameBiMap sos1_constraints;
|
||||
IdNameBiMap sos2_constraints;
|
||||
IdNameBiMap indicator_constraints;
|
||||
|
||||
@@ -13,22 +13,17 @@
|
||||
|
||||
#include "ortools/math_opt/core/solver.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/base/thread_annotations.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/synchronization/mutex.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/base/integral_types.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/base/status_macros.h"
|
||||
#include "ortools/math_opt/callback.pb.h"
|
||||
#include "ortools/math_opt/core/concurrent_calls_guard.h"
|
||||
@@ -36,11 +31,13 @@
|
||||
#include "ortools/math_opt/core/non_streamable_solver_init_arguments.h"
|
||||
#include "ortools/math_opt/core/solver_debug.h"
|
||||
#include "ortools/math_opt/core/solver_interface.h"
|
||||
#include "ortools/math_opt/infeasible_subsystem.pb.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/model_update.pb.h"
|
||||
#include "ortools/math_opt/parameters.pb.h"
|
||||
#include "ortools/math_opt/result.pb.h"
|
||||
#include "ortools/math_opt/validators/callback_validator.h"
|
||||
#include "ortools/math_opt/validators/infeasible_subsystem_validator.h"
|
||||
#include "ortools/math_opt/validators/model_parameters_validator.h"
|
||||
#include "ortools/math_opt/validators/model_validator.h"
|
||||
#include "ortools/math_opt/validators/result_validator.h"
|
||||
@@ -66,8 +63,8 @@ absl::Status ToInternalError(const absl::Status original) {
|
||||
// previous call to one of them failed.
|
||||
absl::Status PreviousFatalFailureOccurred() {
|
||||
return absl::InvalidArgumentError(
|
||||
"a previous call to Solve() or Update() failed, the Solver can't be used "
|
||||
"anymore");
|
||||
"a previous call to Solve(), InfeasibleSubsystem(), or Update() failed, "
|
||||
"the Solver can't be used anymore");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -187,6 +184,48 @@ absl::StatusOr<bool> Solver::Update(const ModelUpdateProto& model_update) {
|
||||
return true;
|
||||
}
|
||||
|
||||
absl::StatusOr<InfeasibleSubsystemResultProto> Solver::InfeasibleSubsystem(
|
||||
const InfeasibleSubsystemArgs& infeasible_subsystem_args) {
|
||||
ASSIGN_OR_RETURN(const auto guard,
|
||||
ConcurrentCallsGuard::TryAcquire(concurrent_calls_tracker_));
|
||||
|
||||
if (fatal_failure_occurred_) {
|
||||
return PreviousFatalFailureOccurred();
|
||||
}
|
||||
CHECK(underlying_solver_ != nullptr);
|
||||
|
||||
// We will reset it in code paths where no error occur.
|
||||
fatal_failure_occurred_ = true;
|
||||
|
||||
RETURN_IF_ERROR(ValidateSolveParameters(infeasible_subsystem_args.parameters))
|
||||
<< "invalid parameters";
|
||||
|
||||
ASSIGN_OR_RETURN(const InfeasibleSubsystemResultProto result,
|
||||
underlying_solver_->InfeasibleSubsystem(
|
||||
infeasible_subsystem_args.parameters,
|
||||
infeasible_subsystem_args.message_callback,
|
||||
infeasible_subsystem_args.interrupter));
|
||||
|
||||
// We consider errors in `result` to be internal errors, but
|
||||
// `ValidateInfeasibleSubsystemResult()` will return an InvalidArgumentError.
|
||||
// So here we convert the error.
|
||||
RETURN_IF_ERROR(ToInternalError(
|
||||
ValidateInfeasibleSubsystemResult(result, model_summary_)));
|
||||
|
||||
fatal_failure_occurred_ = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
absl::StatusOr<InfeasibleSubsystemResultProto>
|
||||
Solver::NonIncrementalInfeasibleSubsystem(
|
||||
const ModelProto& model, const SolverTypeProto solver_type,
|
||||
const InitArgs& init_args,
|
||||
const InfeasibleSubsystemArgs& infeasible_subsystem_args) {
|
||||
ASSIGN_OR_RETURN(std::unique_ptr<Solver> solver,
|
||||
Solver::New(solver_type, model, init_args));
|
||||
return solver->InfeasibleSubsystem(infeasible_subsystem_args);
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
absl::Status ValidateInitArgs(const Solver::InitArgs& init_args,
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "ortools/math_opt/callback.pb.h"
|
||||
#include "ortools/math_opt/core/concurrent_calls_guard.h"
|
||||
#include "ortools/math_opt/core/model_summary.h"
|
||||
#include "ortools/math_opt/core/solve_interrupter.h"
|
||||
#include "ortools/math_opt/core/solver_interface.h"
|
||||
#include "ortools/math_opt/infeasible_subsystem.pb.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/model_parameters.pb.h"
|
||||
#include "ortools/math_opt/model_update.pb.h"
|
||||
@@ -102,6 +102,22 @@ class Solver {
|
||||
SolveInterrupter* interrupter = nullptr;
|
||||
};
|
||||
|
||||
// Arguments used when calling InfeasibleSubsystem().
|
||||
struct InfeasibleSubsystemArgs {
|
||||
SolveParametersProto parameters;
|
||||
|
||||
// An optional callback for messages emitted by the solver.
|
||||
//
|
||||
// When set it enables the solver messages and ignores the `enable_output`
|
||||
// in solve parameters; messages are redirected to the callback and not
|
||||
// printed on stdout/stderr/logs anymore.
|
||||
MessageCallback message_callback = nullptr;
|
||||
|
||||
// An optional interrupter that the solver can use to interrupt the solve
|
||||
// early.
|
||||
SolveInterrupter* interrupter = nullptr;
|
||||
};
|
||||
|
||||
// A shortcut for calling Solver::New() and then Solver::Solve().
|
||||
static absl::StatusOr<SolveResultProto> NonIncrementalSolve(
|
||||
const ModelProto& model, SolverTypeProto solver_type,
|
||||
@@ -134,6 +150,18 @@ class Solver {
|
||||
// license).
|
||||
absl::StatusOr<bool> Update(const ModelUpdateProto& model_update);
|
||||
|
||||
// Computes an infeasible subsystem of `model`.
|
||||
absl::StatusOr<InfeasibleSubsystemResultProto> InfeasibleSubsystem(
|
||||
const InfeasibleSubsystemArgs& infeasible_subsystem_args);
|
||||
|
||||
// A shortcut for calling Solver::New() and then
|
||||
// Solver()::InfeasibleSubsystem()
|
||||
static absl::StatusOr<InfeasibleSubsystemResultProto>
|
||||
NonIncrementalInfeasibleSubsystem(
|
||||
const ModelProto& model, SolverTypeProto solver_type,
|
||||
const InitArgs& init_args,
|
||||
const InfeasibleSubsystemArgs& infeasible_subsystem_args);
|
||||
|
||||
private:
|
||||
Solver(std::unique_ptr<SolverInterface> underlying_solver,
|
||||
ModelSummary model_summary);
|
||||
|
||||
@@ -20,12 +20,13 @@
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/ascii.h"
|
||||
#include "absl/strings/match.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/base/map_util.h"
|
||||
#include "ortools/base/status_builder.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
@@ -34,6 +35,7 @@
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
namespace {} // namespace
|
||||
|
||||
AllSolversRegistry* AllSolversRegistry::Instance() {
|
||||
static AllSolversRegistry* const instance = new AllSolversRegistry;
|
||||
@@ -66,7 +68,8 @@ absl::StatusOr<std::unique_ptr<SolverInterface>> AllSolversRegistry::Create(
|
||||
name = absl::StrCat("unknown(", static_cast<int>(solver_type), ")");
|
||||
}
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< "solver type " << name << " is not registered";
|
||||
<< "solver type " << name << " is not registered"
|
||||
<< ", support for this solver has not been compiled";
|
||||
}
|
||||
return (*factory)(model, init_args);
|
||||
}
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
|
||||
#include "absl/base/attributes.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "ortools/math_opt/callback.pb.h"
|
||||
#include "ortools/math_opt/core/non_streamable_solver_init_arguments.h"
|
||||
#include "ortools/math_opt/core/solve_interrupter.h"
|
||||
#include "ortools/math_opt/infeasible_subsystem.pb.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/model_parameters.pb.h"
|
||||
#include "ortools/math_opt/model_update.pb.h"
|
||||
@@ -135,6 +135,24 @@ class SolverInterface {
|
||||
// The implementation should assume the input ModelUpdate is valid and is free
|
||||
// to assert if this is not the case.
|
||||
virtual absl::StatusOr<bool> Update(const ModelUpdateProto& model_update) = 0;
|
||||
|
||||
// Computes a infeasible subsystem of the model (including all updates).
|
||||
//
|
||||
// All input arguments are ensured (by solver.cc) to be valid. Furthermore,
|
||||
// since all parameters are references or functions (which could be a lambda
|
||||
// expression), the implementation should not keep a reference or copy of
|
||||
// them, as they may become invalid reference after the invocation if this
|
||||
// function.
|
||||
//
|
||||
// The parameters `message_cb` and `interrupter` are optional. They are
|
||||
// nullptr when not set.
|
||||
//
|
||||
// When parameter `message_cb` is not null and the underlying solver does not
|
||||
// supports message callbacks, it must return an InvalidArgumentError with the
|
||||
// message internal::kMessageCallbackNotSupported.
|
||||
virtual absl::StatusOr<InfeasibleSubsystemResultProto> InfeasibleSubsystem(
|
||||
const SolveParametersProto& parameters, MessageCallback message_cb,
|
||||
SolveInterrupter* interrupter) = 0;
|
||||
};
|
||||
|
||||
class AllSolversRegistry {
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
// 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/math_opt/core/sparse_collection_matchers.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
|
||||
SparseDoubleVectorProto MakeSparseDoubleVector(
|
||||
std::initializer_list<std::pair<int64_t, double>> pairs) {
|
||||
SparseDoubleVectorProto ret;
|
||||
for (const auto [id, value] : pairs) {
|
||||
ret.add_ids(id);
|
||||
ret.add_values(value);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SparseBoolVectorProto MakeSparseBoolVector(
|
||||
std::initializer_list<std::pair<int64_t, bool>> pairs) {
|
||||
SparseBoolVectorProto ret;
|
||||
for (const auto [id, value] : pairs) {
|
||||
ret.add_ids(id);
|
||||
ret.add_values(value);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SparseDoubleMatrixProto MakeSparseDoubleMatrix(
|
||||
std::initializer_list<std::tuple<int64_t, int64_t, double>> values) {
|
||||
SparseDoubleMatrixProto ret;
|
||||
for (const auto [row, col, coefficient] : values) {
|
||||
ret.add_row_ids(row);
|
||||
ret.add_column_ids(col);
|
||||
ret.add_coefficients(coefficient);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace math_opt
|
||||
} // namespace operations_research
|
||||
@@ -1,85 +0,0 @@
|
||||
// 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_MATH_OPT_CORE_SPARSE_COLLECTION_MATCHERS_H_
|
||||
#define OR_TOOLS_MATH_OPT_CORE_SPARSE_COLLECTION_MATCHERS_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/math_opt/core/sparse_vector_view.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
|
||||
SparseDoubleVectorProto MakeSparseDoubleVector(
|
||||
std::initializer_list<std::pair<int64_t, double>> pairs);
|
||||
|
||||
SparseBoolVectorProto MakeSparseBoolVector(
|
||||
std::initializer_list<std::pair<int64_t, bool>> pairs);
|
||||
|
||||
SparseDoubleMatrixProto MakeSparseDoubleMatrix(
|
||||
std::initializer_list<std::tuple<int64_t, int64_t, double>> values);
|
||||
|
||||
// Type of the argument of SparseVectorMatcher.
|
||||
template <typename T>
|
||||
using Pairs = std::initializer_list<std::pair<int64_t, const T>>;
|
||||
|
||||
// Here `pairs` must be a Pairs<T>.
|
||||
//
|
||||
// Usage:
|
||||
// EXPECT_THAT(v, SparseVectorMatcher(Pairs<double>{}));
|
||||
// EXPECT_THAT(v, SparseVectorMatcher(Pairs<double>{{2, 3.0}, {3, 2.0}}));
|
||||
MATCHER_P(SparseVectorMatcher, pairs, "") {
|
||||
const auto iterable = MakeView(arg);
|
||||
const std::vector v(iterable.begin(), iterable.end());
|
||||
const std::vector<typename decltype(v)::value_type> expected(pairs.begin(),
|
||||
pairs.end());
|
||||
|
||||
return ::testing::ExplainMatchResult(::testing::ContainerEq(expected), v,
|
||||
result_listener);
|
||||
}
|
||||
|
||||
// Type of the argument of SparseDoubleMatrixMatcher.
|
||||
using Coefficient = std::tuple<int64_t, int64_t, const double>;
|
||||
using Coefficients = std::initializer_list<Coefficient>;
|
||||
|
||||
// Here `coefficients` must be a Coefficients<T>.
|
||||
//
|
||||
// Usage:
|
||||
// EXPECT_THAT(v, SparseDoubleMatrixMatcher(Coefficients{}));
|
||||
// EXPECT_THAT(v, SparseDoubleMatrixMatcher(Coefficients{{2, 1, 3.0}, {3,
|
||||
// 0, 2.0}}));
|
||||
MATCHER_P(SparseDoubleMatrixMatcher, coefficients, "") {
|
||||
std::vector<Coefficient> v;
|
||||
for (int i = 0; i < arg.row_ids_size(); ++i) {
|
||||
v.emplace_back(arg.row_ids(i), arg.column_ids(i), arg.coefficients(i));
|
||||
}
|
||||
const std::vector<Coefficient> expected(coefficients.begin(),
|
||||
coefficients.end());
|
||||
|
||||
return ::testing::ExplainMatchResult(::testing::ContainerEq(expected), v,
|
||||
result_listener);
|
||||
}
|
||||
|
||||
} // namespace math_opt
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_CORE_SPARSE_COLLECTION_MATCHERS_H_
|
||||
@@ -128,9 +128,13 @@ class SparseVectorView {
|
||||
int values_size() const { return values_.size(); }
|
||||
const T& values(int index) const { return values_[index]; }
|
||||
|
||||
// It should be possible to construct an IndexType from an integer
|
||||
template <typename IndexType>
|
||||
absl::flat_hash_map<IndexType, T> as_map();
|
||||
// Returns the map corresponding to this sparse vector.
|
||||
//
|
||||
// It should be possible to construct KeyType::IdType from an int64_t and
|
||||
// KeyType from a Storage pointer and the build id. See cpp/key_types.h for
|
||||
// details.
|
||||
template <typename KeyType, typename Storage>
|
||||
absl::flat_hash_map<KeyType, T> as_map(const Storage* storage);
|
||||
|
||||
private:
|
||||
absl::Span<const int64_t> ids_;
|
||||
@@ -233,13 +237,15 @@ typename SparseVectorView<T>::const_iterator SparseVectorView<T>::end() const {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename IndexType>
|
||||
absl::flat_hash_map<IndexType, T> SparseVectorView<T>::as_map() {
|
||||
absl::flat_hash_map<IndexType, T> result;
|
||||
template <typename KeyType, typename Storage>
|
||||
absl::flat_hash_map<KeyType, T> SparseVectorView<T>::as_map(
|
||||
const Storage* storage) {
|
||||
absl::flat_hash_map<KeyType, T> result;
|
||||
CHECK_EQ(ids_size(), values_size());
|
||||
result.reserve(ids_size());
|
||||
for (const auto& [id, value] : *this) {
|
||||
gtl::InsertOrDie(&result, IndexType(id), value);
|
||||
gtl::InsertOrDie(&result, KeyType(storage, typename KeyType::IdType(id)),
|
||||
value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ cc_library(
|
||||
deps = [
|
||||
":basis_status",
|
||||
":linear_constraint",
|
||||
":objective",
|
||||
":variable_and_expressions",
|
||||
"//ortools/base",
|
||||
"//ortools/base:status_macros",
|
||||
@@ -54,9 +55,11 @@ cc_library(
|
||||
"//ortools/math_opt/validators:ids_validator",
|
||||
"//ortools/math_opt/validators:sparse_vector_validator",
|
||||
"//ortools/util:status_macros",
|
||||
"@com_google_absl//absl/algorithm:container",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/types:span",
|
||||
"@com_google_protobuf//:protobuf",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -67,58 +70,50 @@ cc_library(
|
||||
deps = [
|
||||
":key_types",
|
||||
":linear_constraint",
|
||||
":objective",
|
||||
":update_tracker",
|
||||
":variable_and_expressions",
|
||||
"//ortools/base",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/math_opt:model_cc_proto",
|
||||
"//ortools/math_opt:model_update_cc_proto",
|
||||
"//ortools/math_opt/constraints/indicator:indicator_constraint",
|
||||
"//ortools/math_opt/constraints/quadratic:quadratic_constraint",
|
||||
"//ortools/math_opt/constraints/second_order_cone:second_order_cone_constraint",
|
||||
"//ortools/math_opt/constraints/sos:sos1_constraint",
|
||||
"//ortools/math_opt/constraints/sos:sos2_constraint",
|
||||
"//ortools/math_opt/constraints/util:model_util",
|
||||
"//ortools/math_opt/storage:linear_expression_data",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/storage:model_storage_types",
|
||||
"//ortools/math_opt/storage:sparse_coefficient_map",
|
||||
"//ortools/math_opt/storage:sparse_matrix",
|
||||
"//ortools/util:fp_roundtrip_conv",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "id_map",
|
||||
hdrs = ["id_map.h"],
|
||||
deps = [
|
||||
":key_types",
|
||||
"//ortools/base",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt/core:arrow_operator_proxy",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "variable_and_expressions",
|
||||
srcs = ["variable_and_expressions.cc"],
|
||||
hdrs = ["variable_and_expressions.h"],
|
||||
deps = [
|
||||
":formatters",
|
||||
":id_map",
|
||||
":key_types",
|
||||
"//ortools/base",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/base:map_util",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/storage:model_storage_types",
|
||||
"//ortools/util:fp_roundtrip_conv",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -126,13 +121,14 @@ cc_library(
|
||||
name = "linear_constraint",
|
||||
hdrs = ["linear_constraint.h"],
|
||||
deps = [
|
||||
":id_map",
|
||||
":key_types",
|
||||
":variable_and_expressions",
|
||||
"//ortools/base",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt/constraints/util:model_util",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/storage:model_storage_types",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -144,9 +140,9 @@ cc_library(
|
||||
":basis_status",
|
||||
":enums",
|
||||
":linear_constraint",
|
||||
":objective",
|
||||
":sparse_containers",
|
||||
":variable_and_expressions",
|
||||
"//ortools/base",
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/math_opt:result_cc_proto",
|
||||
"//ortools/math_opt:solution_cc_proto",
|
||||
@@ -157,8 +153,10 @@ cc_library(
|
||||
"//ortools/math_opt/validators:sparse_vector_validator",
|
||||
"//ortools/util:status_macros",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/types:optional",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
@@ -194,10 +192,11 @@ cc_library(
|
||||
name = "map_filter",
|
||||
hdrs = ["map_filter.h"],
|
||||
deps = [
|
||||
":id_set",
|
||||
"//ortools/base:intops",
|
||||
":key_types",
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/math_opt:sparse_containers_cc_proto",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"@com_google_absl//absl/algorithm:container",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -219,6 +218,7 @@ cc_library(
|
||||
"//ortools/math_opt/core:sparse_vector_view",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/time",
|
||||
@@ -230,21 +230,12 @@ cc_library(
|
||||
name = "key_types",
|
||||
hdrs = ["key_types.h"],
|
||||
deps = [
|
||||
"//ortools/base",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"@com_google_absl//absl/algorithm:container",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "id_set",
|
||||
hdrs = ["id_set.h"],
|
||||
deps = [
|
||||
":key_types",
|
||||
"//ortools/base",
|
||||
"//ortools/math_opt/core:arrow_operator_proxy",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -255,12 +246,18 @@ cc_library(
|
||||
deps = [
|
||||
":linear_constraint",
|
||||
":map_filter",
|
||||
":model",
|
||||
":solution",
|
||||
":sparse_containers",
|
||||
":variable_and_expressions",
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/math_opt:model_parameters_cc_proto",
|
||||
"//ortools/math_opt:solution_cc_proto",
|
||||
"//ortools/math_opt:sparse_containers_cc_proto",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/util:status_macros",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_protobuf//:protobuf",
|
||||
],
|
||||
)
|
||||
@@ -322,7 +319,11 @@ cc_library(
|
||||
":message_callback",
|
||||
":model_solve_parameters",
|
||||
":parameters",
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/math_opt/core:solve_interrupter",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/status",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -331,22 +332,30 @@ cc_library(
|
||||
srcs = ["solve.cc"],
|
||||
hdrs = ["solve.h"],
|
||||
deps = [
|
||||
":callback",
|
||||
":enums",
|
||||
":infeasible_subsystem_arguments",
|
||||
":infeasible_subsystem_result",
|
||||
":model",
|
||||
":model_solve_parameters",
|
||||
":parameters",
|
||||
":solve_arguments",
|
||||
":solve_result",
|
||||
":solver_init_arguments",
|
||||
":streamable_solver_init_arguments",
|
||||
":update_result",
|
||||
"//ortools/base",
|
||||
":update_tracker",
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/math_opt:callback_cc_proto",
|
||||
"//ortools/math_opt:infeasible_subsystem_cc_proto",
|
||||
"//ortools/math_opt:parameters_cc_proto",
|
||||
"//ortools/math_opt/core:solver",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/util:status_macros",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -367,17 +376,19 @@ cc_library(
|
||||
hdrs = ["parameters.h"],
|
||||
deps = [
|
||||
":enums",
|
||||
"//ortools/base",
|
||||
"//ortools/base:linked_hash_map",
|
||||
"//ortools/base:protoutil",
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/glop:parameters_cc_proto",
|
||||
"//ortools/gscip:gscip_cc_proto",
|
||||
"//ortools/math_opt:parameters_cc_proto",
|
||||
"//ortools/math_opt/solvers:glpk_cc_proto",
|
||||
"//ortools/math_opt/solvers:gurobi_cc_proto",
|
||||
"//ortools/port:proto_utils",
|
||||
"//ortools/sat:sat_parameters_cc_proto",
|
||||
"//ortools/util:status_macros",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/time",
|
||||
@@ -389,7 +400,7 @@ cc_library(
|
||||
name = "enums",
|
||||
hdrs = ["enums.h"],
|
||||
deps = [
|
||||
"//ortools/base",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
@@ -416,3 +427,59 @@ cc_library(
|
||||
hdrs = ["update_result.h"],
|
||||
deps = ["//ortools/math_opt:model_update_cc_proto"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "objective",
|
||||
srcs = ["objective.cc"],
|
||||
hdrs = ["objective.h"],
|
||||
deps = [
|
||||
":key_types",
|
||||
":variable_and_expressions",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/storage:model_storage_types",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "infeasible_subsystem_result",
|
||||
srcs = ["infeasible_subsystem_result.cc"],
|
||||
hdrs = ["infeasible_subsystem_result.h"],
|
||||
deps = [
|
||||
":enums",
|
||||
":key_types",
|
||||
":linear_constraint",
|
||||
":solve_result",
|
||||
":variable_and_expressions",
|
||||
"//ortools/base:status_macros",
|
||||
"//ortools/math_opt:infeasible_subsystem_cc_proto",
|
||||
"//ortools/math_opt:result_cc_proto",
|
||||
"//ortools/math_opt/constraints/indicator:indicator_constraint",
|
||||
"//ortools/math_opt/constraints/quadratic:quadratic_constraint",
|
||||
"//ortools/math_opt/constraints/second_order_cone:second_order_cone_constraint",
|
||||
"//ortools/math_opt/constraints/sos:sos1_constraint",
|
||||
"//ortools/math_opt/constraints/sos:sos2_constraint",
|
||||
"//ortools/math_opt/storage:model_storage",
|
||||
"//ortools/math_opt/validators:infeasible_subsystem_validator",
|
||||
"//ortools/util:status_macros",
|
||||
"@com_google_absl//absl/algorithm:container",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "infeasible_subsystem_arguments",
|
||||
hdrs = ["infeasible_subsystem_arguments.h"],
|
||||
deps = [
|
||||
":message_callback",
|
||||
":parameters",
|
||||
"//ortools/math_opt/core:solve_interrupter",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -91,13 +91,9 @@ CallbackData::CallbackData(const ModelStorage* storage,
|
||||
|
||||
absl::Status CallbackRegistration::CheckModelStorage(
|
||||
const ModelStorage* const expected_storage) const {
|
||||
RETURN_IF_ERROR(
|
||||
internal::CheckModelStorage(/*storage=*/mip_node_filter.storage(),
|
||||
/*expected_storage=*/expected_storage))
|
||||
RETURN_IF_ERROR(mip_node_filter.CheckModelStorage(expected_storage))
|
||||
<< "invalid mip_node_filter";
|
||||
RETURN_IF_ERROR(
|
||||
internal::CheckModelStorage(/*storage=*/mip_solution_filter.storage(),
|
||||
/*expected_storage=*/expected_storage))
|
||||
RETURN_IF_ERROR(mip_solution_filter.CheckModelStorage(expected_storage))
|
||||
<< "invalid mip_solution_filter";
|
||||
return absl::OkStatus();
|
||||
}
|
||||
@@ -125,9 +121,11 @@ absl::Status CallbackResult::CheckModelStorage(
|
||||
<< "invalid new_constraints";
|
||||
}
|
||||
for (const VariableMap<double>& solution : suggested_solutions) {
|
||||
RETURN_IF_ERROR(internal::CheckModelStorage(
|
||||
/*storage=*/solution.storage(), /*expected_storage=*/expected_storage))
|
||||
<< "invalid suggested_solutions";
|
||||
for (const auto& [v, _] : solution) {
|
||||
RETURN_IF_ERROR(internal::CheckModelStorage(
|
||||
/*storage=*/v.storage(), /*expected_storage=*/expected_storage))
|
||||
<< "invalid variable " << v << " in suggested_solutions";
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -83,9 +83,9 @@
|
||||
#include <ostream>
|
||||
#include <type_traits>
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "absl/log/check.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
|
||||
@@ -1,659 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// IWYU pragma: private, include "ortools/math_opt/cpp/math_opt.h"
|
||||
// IWYU pragma: friend "ortools/math_opt/cpp/.*"
|
||||
|
||||
// A faster version of flat_hash_map for Variable and LinearConstraint keys.
|
||||
#ifndef OR_TOOLS_MATH_OPT_CPP_ID_MAP_H_
|
||||
#define OR_TOOLS_MATH_OPT_CPP_ID_MAP_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/core/arrow_operator_proxy.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/cpp/key_types.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
|
||||
// Similar to a absl::flat_hash_map<K, V> for K as Variable or LinearConstraint.
|
||||
//
|
||||
// Important differences:
|
||||
// * The storage is more efficient, as we store the underlying ids directly.
|
||||
// * The consequence of that is that the keys are usually returned by value in
|
||||
// situations where the flat_hash_map would return references.
|
||||
// * You cannot mix variables/constraints from multiple models in these maps.
|
||||
// Doing so results in a CHECK failure.
|
||||
//
|
||||
// Implementation notes:
|
||||
// * Emptying the map (with clear() or erase()) resets the underlying model to
|
||||
// nullptr, enabling reusing the same instance with a different model.
|
||||
// * Operator= and swap() support operating with different models by
|
||||
// respectively replacing or swapping it.
|
||||
// * For details requirements on K, see key_types.h.
|
||||
//
|
||||
// See also IdSet for the equivalent class for sets.
|
||||
template <typename K, typename V>
|
||||
class IdMap {
|
||||
public:
|
||||
using IdType = typename K::IdType;
|
||||
using StorageType = absl::flat_hash_map<IdType, V>;
|
||||
using key_type = K;
|
||||
using mapped_type = V;
|
||||
using value_type = std::pair<const K, V>;
|
||||
using size_type = typename StorageType::size_type;
|
||||
using difference_type = typename StorageType::difference_type;
|
||||
using reference = std::pair<const K, V&>;
|
||||
using const_reference = std::pair<const K, const V&>;
|
||||
using pointer = void;
|
||||
using const_pointer = void;
|
||||
|
||||
class iterator {
|
||||
public:
|
||||
using value_type = IdMap::value_type;
|
||||
using reference = IdMap::reference;
|
||||
using pointer = IdMap::pointer;
|
||||
using difference_type = IdMap::difference_type;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
iterator() = default;
|
||||
|
||||
inline reference operator*() const;
|
||||
inline internal::ArrowOperatorProxy<reference> operator->() const;
|
||||
inline iterator& operator++();
|
||||
inline iterator operator++(int);
|
||||
|
||||
friend bool operator==(const iterator& lhs, const iterator& rhs) {
|
||||
return lhs.storage_iterator_ == rhs.storage_iterator_;
|
||||
}
|
||||
friend bool operator!=(const iterator& lhs, const iterator& rhs) {
|
||||
return lhs.storage_iterator_ != rhs.storage_iterator_;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class IdMap;
|
||||
|
||||
inline iterator(const IdMap* map,
|
||||
typename StorageType::iterator storage_iterator);
|
||||
|
||||
const IdMap* map_ = nullptr;
|
||||
typename StorageType::iterator storage_iterator_;
|
||||
};
|
||||
|
||||
class const_iterator {
|
||||
public:
|
||||
using value_type = IdMap::value_type;
|
||||
using reference = IdMap::const_reference;
|
||||
using pointer = IdMap::const_pointer;
|
||||
using difference_type = IdMap::difference_type;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
const_iterator() = default;
|
||||
inline const_iterator(const iterator& non_const_iterator); // NOLINT
|
||||
|
||||
inline reference operator*() const;
|
||||
inline internal::ArrowOperatorProxy<reference> operator->() const;
|
||||
inline const_iterator& operator++();
|
||||
inline const_iterator operator++(int);
|
||||
|
||||
friend bool operator==(const const_iterator& lhs,
|
||||
const const_iterator& rhs) {
|
||||
return lhs.storage_iterator_ == rhs.storage_iterator_;
|
||||
}
|
||||
friend bool operator!=(const const_iterator& lhs,
|
||||
const const_iterator& rhs) {
|
||||
return lhs.storage_iterator_ != rhs.storage_iterator_;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class IdMap;
|
||||
|
||||
inline const_iterator(
|
||||
const IdMap* map,
|
||||
typename StorageType::const_iterator storage_iterator);
|
||||
|
||||
const IdMap* map_ = nullptr;
|
||||
typename StorageType::const_iterator storage_iterator_;
|
||||
};
|
||||
|
||||
IdMap() = default;
|
||||
template <typename InputIt>
|
||||
inline IdMap(InputIt first, InputIt last);
|
||||
inline IdMap(std::initializer_list<value_type> ilist);
|
||||
|
||||
// Typically for internal use only.
|
||||
inline IdMap(const ModelStorage* storage, StorageType values);
|
||||
|
||||
inline const_iterator cbegin() const;
|
||||
inline const_iterator begin() const;
|
||||
inline iterator begin();
|
||||
|
||||
inline const_iterator cend() const;
|
||||
inline const_iterator end() const;
|
||||
inline iterator end();
|
||||
|
||||
bool empty() const { return map_.empty(); }
|
||||
size_type size() const { return map_.size(); }
|
||||
inline void clear();
|
||||
void reserve(size_type count) { map_.reserve(count); }
|
||||
|
||||
inline std::pair<iterator, bool> insert(std::pair<K, V> k_v);
|
||||
template <typename InputIt>
|
||||
inline void insert(InputIt first, InputIt last);
|
||||
inline void insert(std::initializer_list<value_type> ilist);
|
||||
|
||||
template <typename M>
|
||||
inline std::pair<iterator, bool> insert_or_assign(const K& k, M&& v);
|
||||
|
||||
inline std::pair<iterator, bool> emplace(const K& k, V v);
|
||||
template <typename... Args>
|
||||
inline std::pair<iterator, bool> try_emplace(const K& k, Args&&... args);
|
||||
|
||||
// Returns the number of elements erased (zero or one).
|
||||
inline size_type erase(const K& k);
|
||||
// In STL erase(const_iterator) and erase(iterator) both return an
|
||||
// iterator. But flat_hash_map instead has void return types. So here we also
|
||||
// use void.
|
||||
//
|
||||
// In flat_hash_map, both erase(const_iterator) and erase(iterator) are
|
||||
// defined since there is also the erase<K>(const K&) that exists and that
|
||||
// would be used. Since we don't have this overload, we can rely on the
|
||||
// automatic cast of the iterator in const_iterator.
|
||||
inline void erase(const_iterator pos);
|
||||
inline iterator erase(const_iterator first, const_iterator last);
|
||||
|
||||
inline void swap(IdMap& other);
|
||||
|
||||
inline const V& at(const K& k) const;
|
||||
inline V& at(const K& k);
|
||||
inline V& operator[](const K& k);
|
||||
inline size_type count(const K& k) const;
|
||||
inline bool contains(const K& k) const;
|
||||
inline iterator find(const K& k);
|
||||
inline const_iterator find(const K& k) const;
|
||||
inline std::pair<iterator, iterator> equal_range(const K& k);
|
||||
inline std::pair<const_iterator, const_iterator> equal_range(
|
||||
const K& k) const;
|
||||
|
||||
// Updates the values in this map by adding the value of the corresponding
|
||||
// keys in the other map. For keys only in the other map, insert their value.
|
||||
//
|
||||
// This function is only available when type V supports operator+=.
|
||||
//
|
||||
// This is equivalent to (but is more efficient than):
|
||||
// for (const auto pair : other) {
|
||||
// (*this)[pair.first] += pair.second;
|
||||
// }
|
||||
//
|
||||
// This function CHECK that all the keys in the two maps have the same model.
|
||||
inline void Add(const IdMap& other);
|
||||
|
||||
// Updates the values in this map by subtracting the value of the
|
||||
// corresponding keys in the other map. For keys only in the other map, insert
|
||||
// the opposite of their value.
|
||||
//
|
||||
// This function is only available when type V supports operator-=.
|
||||
//
|
||||
// This is equivalent to (but is more efficient than):
|
||||
// for (const auto pair : other) {
|
||||
// (*this)[pair.first] -= pair.second;
|
||||
// }
|
||||
//
|
||||
// This function CHECK that all the keys in the two maps have the same model.
|
||||
inline void Subtract(const IdMap& other);
|
||||
|
||||
inline std::vector<V> Values(absl::Span<const K> keys) const;
|
||||
inline absl::flat_hash_map<K, V> Values(
|
||||
const absl::flat_hash_set<K>& keys) const;
|
||||
|
||||
inline std::vector<K> SortedKeys() const;
|
||||
|
||||
// Returns the values in sorted KEY order.
|
||||
inline std::vector<V> SortedValues() const;
|
||||
|
||||
const StorageType& raw_map() const { return map_; }
|
||||
const ModelStorage* storage() const { return storage_; }
|
||||
|
||||
friend bool operator==(const IdMap& lhs, const IdMap& rhs) {
|
||||
return lhs.storage_ == rhs.storage_ && lhs.map_ == rhs.map_;
|
||||
}
|
||||
friend bool operator!=(const IdMap& lhs, const IdMap& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
inline std::vector<IdType> SortedIds() const;
|
||||
// CHECKs that storage_ and k.storage() matches when this map is not empty
|
||||
// (i.e. its storage_ is not null). When it is empty, simply check that
|
||||
// k.storage() is not null.
|
||||
inline void CheckModel(const K& k) const;
|
||||
// Sets storage_ to k.storage() if this map is empty (i.e. its storage_ is
|
||||
// null). Else CHECK that it has the same model. It also CHECK that
|
||||
// k.storage() is not null.
|
||||
inline void CheckOrSetModel(const K& k);
|
||||
// Sets storage_ to other.storage_ if this map is empty (i.e. its storage_ is
|
||||
// null). Else if the other map is not empty, CHECK that it has the same
|
||||
// model.
|
||||
inline void CheckOrSetModel(const IdMap& other);
|
||||
|
||||
// Invariant: storage == nullptr if and only if map_.empty().
|
||||
const ModelStorage* storage_ = nullptr;
|
||||
StorageType map_;
|
||||
};
|
||||
|
||||
// Calls a.swap(b).
|
||||
//
|
||||
// This function is used for making MapId "swappable".
|
||||
// Ref: https://en.cppreference.com/w/cpp/named_req/Swappable.
|
||||
template <typename K, typename V>
|
||||
void swap(IdMap<K, V>& a, IdMap<K, V>& b) {
|
||||
a.swap(b);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Inline implementations
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// IdMap::iterator
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::reference IdMap<K, V>::iterator::operator*() const {
|
||||
return reference(K(map_->storage_, storage_iterator_->first),
|
||||
storage_iterator_->second);
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
internal::ArrowOperatorProxy<typename IdMap<K, V>::iterator::reference>
|
||||
IdMap<K, V>::iterator::operator->() const {
|
||||
return internal::ArrowOperatorProxy<reference>(**this);
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::iterator& IdMap<K, V>::iterator::operator++() {
|
||||
++storage_iterator_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::iterator IdMap<K, V>::iterator::operator++(int) {
|
||||
iterator ret = *this;
|
||||
++(*this);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
IdMap<K, V>::iterator::iterator(const IdMap* map,
|
||||
typename StorageType::iterator storage_iterator)
|
||||
: map_(map), storage_iterator_(std::move(storage_iterator)) {}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// IdMap::const_iterator
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename K, typename V>
|
||||
IdMap<K, V>::const_iterator::const_iterator(const iterator& non_const_iterator)
|
||||
: map_(non_const_iterator.map_),
|
||||
storage_iterator_(non_const_iterator.storage_iterator_) {}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::const_iterator::reference
|
||||
IdMap<K, V>::const_iterator::operator*() const {
|
||||
return reference(K(map_->storage_, storage_iterator_->first),
|
||||
storage_iterator_->second);
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
internal::ArrowOperatorProxy<typename IdMap<K, V>::const_iterator::reference>
|
||||
IdMap<K, V>::const_iterator::operator->() const {
|
||||
return internal::ArrowOperatorProxy<reference>(**this);
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::const_iterator&
|
||||
IdMap<K, V>::const_iterator::operator++() {
|
||||
++storage_iterator_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::const_iterator IdMap<K, V>::const_iterator::operator++(
|
||||
int) {
|
||||
const_iterator ret = *this;
|
||||
++(*this);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
IdMap<K, V>::const_iterator::const_iterator(
|
||||
const IdMap* map, typename StorageType::const_iterator storage_iterator)
|
||||
: map_(map), storage_iterator_(std::move(storage_iterator)) {}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// IdMap
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename K, typename V>
|
||||
IdMap<K, V>::IdMap(const ModelStorage* storage, StorageType values)
|
||||
: storage_(values.empty() ? nullptr : storage), map_(std::move(values)) {
|
||||
if (!map_.empty()) {
|
||||
CHECK(storage_ != nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
template <typename InputIt>
|
||||
IdMap<K, V>::IdMap(InputIt first, InputIt last) {
|
||||
insert(first, last);
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
IdMap<K, V>::IdMap(std::initializer_list<value_type> ilist) {
|
||||
insert(ilist);
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::const_iterator IdMap<K, V>::cbegin() const {
|
||||
return const_iterator(this, map_.cbegin());
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::const_iterator IdMap<K, V>::begin() const {
|
||||
return cbegin();
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::iterator IdMap<K, V>::begin() {
|
||||
return iterator(this, map_.begin());
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::const_iterator IdMap<K, V>::cend() const {
|
||||
return const_iterator(this, map_.cend());
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::const_iterator IdMap<K, V>::end() const {
|
||||
return cend();
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::iterator IdMap<K, V>::end() {
|
||||
return iterator(this, map_.end());
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void IdMap<K, V>::clear() {
|
||||
storage_ = nullptr;
|
||||
map_.clear();
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
std::pair<typename IdMap<K, V>::iterator, bool> IdMap<K, V>::insert(
|
||||
std::pair<K, V> k_v) {
|
||||
return emplace(k_v.first, std::move(k_v.second));
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
template <typename InputIt>
|
||||
void IdMap<K, V>::insert(const InputIt first, const InputIt last) {
|
||||
for (InputIt it = first; it != last; ++it) {
|
||||
insert(*it);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void IdMap<K, V>::insert(std::initializer_list<value_type> ilist) {
|
||||
insert(ilist.begin(), ilist.end());
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
template <typename M>
|
||||
std::pair<typename IdMap<K, V>::iterator, bool> IdMap<K, V>::insert_or_assign(
|
||||
const K& k, M&& v) {
|
||||
CheckOrSetModel(k);
|
||||
auto initial_ret = map_.insert_or_assign(k.typed_id(), std::forward<M>(v));
|
||||
return std::make_pair(iterator(this, std::move(initial_ret.first)),
|
||||
initial_ret.second);
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
std::pair<typename IdMap<K, V>::iterator, bool> IdMap<K, V>::emplace(const K& k,
|
||||
V v) {
|
||||
CheckOrSetModel(k);
|
||||
auto initial_ret = map_.emplace(k.typed_id(), std::move(v));
|
||||
return std::make_pair(iterator(this, std::move(initial_ret.first)),
|
||||
initial_ret.second);
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
template <typename... Args>
|
||||
std::pair<typename IdMap<K, V>::iterator, bool> IdMap<K, V>::try_emplace(
|
||||
const K& k, Args&&... args) {
|
||||
CheckOrSetModel(k);
|
||||
auto initial_ret =
|
||||
map_.try_emplace(k.typed_id(), std::forward<Args>(args)...);
|
||||
return std::make_pair(iterator(this, std::move(initial_ret.first)),
|
||||
initial_ret.second);
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::size_type IdMap<K, V>::erase(const K& k) {
|
||||
CheckModel(k);
|
||||
const size_type ret = map_.erase(k.typed_id());
|
||||
if (map_.empty()) {
|
||||
storage_ = nullptr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void IdMap<K, V>::erase(const const_iterator pos) {
|
||||
map_.erase(pos.storage_iterator_);
|
||||
if (map_.empty()) {
|
||||
storage_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::iterator IdMap<K, V>::erase(const const_iterator first,
|
||||
const const_iterator last) {
|
||||
auto ret = map_.erase(first.storage_iterator_, last.storage_iterator_);
|
||||
if (map_.empty()) {
|
||||
storage_ = nullptr;
|
||||
}
|
||||
return iterator(this, std::move(ret));
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void IdMap<K, V>::swap(IdMap& other) {
|
||||
using std::swap;
|
||||
swap(storage_, other.storage_);
|
||||
swap(map_, other.map_);
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
const V& IdMap<K, V>::at(const K& k) const {
|
||||
CheckModel(k);
|
||||
return map_.at(k.typed_id());
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
V& IdMap<K, V>::at(const K& k) {
|
||||
CheckModel(k);
|
||||
return map_.at(k.typed_id());
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
V& IdMap<K, V>::operator[](const K& k) {
|
||||
CheckOrSetModel(k);
|
||||
return map_[k.typed_id()];
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::size_type IdMap<K, V>::count(const K& k) const {
|
||||
CheckModel(k);
|
||||
return map_.count(k.typed_id());
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
bool IdMap<K, V>::contains(const K& k) const {
|
||||
CheckModel(k);
|
||||
return map_.contains(k.typed_id());
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::iterator IdMap<K, V>::find(const K& k) {
|
||||
CheckModel(k);
|
||||
return iterator(this, map_.find(k.typed_id()));
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
typename IdMap<K, V>::const_iterator IdMap<K, V>::find(const K& k) const {
|
||||
CheckModel(k);
|
||||
return const_iterator(this, map_.find(k.typed_id()));
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
std::pair<typename IdMap<K, V>::iterator, typename IdMap<K, V>::iterator>
|
||||
IdMap<K, V>::equal_range(const K& k) {
|
||||
const auto it = find(k);
|
||||
if (it == end()) {
|
||||
return {it, it};
|
||||
}
|
||||
return {it, std::next(it)};
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
std::pair<typename IdMap<K, V>::const_iterator,
|
||||
typename IdMap<K, V>::const_iterator>
|
||||
IdMap<K, V>::equal_range(const K& k) const {
|
||||
const auto it = find(k);
|
||||
if (it == end()) {
|
||||
return {it, it};
|
||||
}
|
||||
return {it, std::next(it)};
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void IdMap<K, V>::Add(const IdMap& other) {
|
||||
CheckOrSetModel(other);
|
||||
for (const auto& pair : other.map_) {
|
||||
map_[pair.first] += pair.second;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void IdMap<K, V>::Subtract(const IdMap& other) {
|
||||
CheckOrSetModel(other);
|
||||
for (const auto& pair : other.map_) {
|
||||
map_[pair.first] -= pair.second;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
std::vector<V> IdMap<K, V>::Values(const absl::Span<const K> keys) const {
|
||||
std::vector<V> result;
|
||||
result.reserve(keys.size());
|
||||
for (const K key : keys) {
|
||||
result.push_back(at(key));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
absl::flat_hash_map<K, V> IdMap<K, V>::Values(
|
||||
const absl::flat_hash_set<K>& keys) const {
|
||||
absl::flat_hash_map<K, V> result;
|
||||
for (const K key : keys) {
|
||||
result[key] = at(key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
std::vector<K> IdMap<K, V>::SortedKeys() const {
|
||||
std::vector<K> result;
|
||||
result.reserve(map_.size());
|
||||
for (const IdType id : SortedIds()) {
|
||||
result.push_back(K(storage_, id));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
std::vector<V> IdMap<K, V>::SortedValues() const {
|
||||
std::vector<V> result;
|
||||
result.reserve(map_.size());
|
||||
for (const IdType id : SortedIds()) {
|
||||
result.push_back(map_.at(id));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
std::vector<typename K::IdType> IdMap<K, V>::SortedIds() const {
|
||||
std::vector<IdType> result;
|
||||
result.reserve(map_.size());
|
||||
for (const auto& [id, _] : map_) {
|
||||
result.push_back(id);
|
||||
}
|
||||
std::sort(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void IdMap<K, V>::CheckModel(const K& k) const {
|
||||
CHECK(k.storage() != nullptr) << internal::kKeyHasNullModelStorage;
|
||||
CHECK(storage_ == nullptr || storage_ == k.storage())
|
||||
<< internal::kObjectsFromOtherModelStorage;
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void IdMap<K, V>::CheckOrSetModel(const K& k) {
|
||||
CHECK(k.storage() != nullptr) << internal::kKeyHasNullModelStorage;
|
||||
if (storage_ == nullptr) {
|
||||
storage_ = k.storage();
|
||||
} else {
|
||||
CHECK_EQ(storage_, k.storage()) << internal::kObjectsFromOtherModelStorage;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void IdMap<K, V>::CheckOrSetModel(const IdMap& other) {
|
||||
if (storage_ == nullptr) {
|
||||
storage_ = other.storage_;
|
||||
} else if (other.storage_ != nullptr) {
|
||||
CHECK_EQ(storage_, other.storage_)
|
||||
<< internal::kObjectsFromOtherModelStorage;
|
||||
} else {
|
||||
// By construction when other is not empty, it has a non null `storage_`.
|
||||
DCHECK(other.empty());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace math_opt
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_CPP_ID_MAP_H_
|
||||
@@ -1,377 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// IWYU pragma: private, include "ortools/math_opt/cpp/math_opt.h"
|
||||
// IWYU pragma: friend "ortools/math_opt/cpp/.*"
|
||||
|
||||
#ifndef OR_TOOLS_MATH_OPT_CPP_ID_SET_H_
|
||||
#define OR_TOOLS_MATH_OPT_CPP_ID_SET_H_
|
||||
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "ortools/math_opt/core/arrow_operator_proxy.h"
|
||||
#include "ortools/math_opt/cpp/key_types.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
|
||||
// Similar to a absl::flat_hash_set<K> for K as Variable or LinearConstraint.
|
||||
//
|
||||
// Important differences:
|
||||
// * The storage is more efficient, as we store the underlying ids directly.
|
||||
// * The consequence of that is that the keys are usually returned by value in
|
||||
// situations where the flat_hash_set would return references.
|
||||
// * You cannot mix variables/constraints from multiple models in these maps.
|
||||
// Doing so results in a CHECK failure.
|
||||
//
|
||||
// Implementation notes:
|
||||
// * Emptying the set (with clear() or erase()) resets the underlying model to
|
||||
// nullptr, enabling reusing the same instance with a different model.
|
||||
// * Operator= and swap() support operating with different models by
|
||||
// respectively replacing or swapping it.
|
||||
// * For details requirements on K, see key_types.h.
|
||||
//
|
||||
// See also IdMap for the equivalent class for maps.
|
||||
template <typename K>
|
||||
class IdSet {
|
||||
public:
|
||||
using IdType = typename K::IdType;
|
||||
using StorageType = absl::flat_hash_set<IdType>;
|
||||
using key_type = K;
|
||||
using value_type = key_type;
|
||||
using size_type = typename StorageType::size_type;
|
||||
using difference_type = typename StorageType::difference_type;
|
||||
using reference = K;
|
||||
using const_reference = const K;
|
||||
using pointer = void;
|
||||
using const_pointer = void;
|
||||
|
||||
class const_iterator {
|
||||
public:
|
||||
using value_type = IdSet::value_type;
|
||||
using reference = IdSet::const_reference;
|
||||
using pointer = IdSet::const_pointer;
|
||||
using difference_type = IdSet::difference_type;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
const_iterator() = default;
|
||||
|
||||
inline const_reference operator*() const;
|
||||
inline internal::ArrowOperatorProxy<reference> operator->() const;
|
||||
inline const_iterator& operator++();
|
||||
inline const_iterator operator++(int);
|
||||
|
||||
friend bool operator==(const const_iterator& lhs,
|
||||
const const_iterator& rhs) {
|
||||
return lhs.storage_iterator_ == rhs.storage_iterator_;
|
||||
}
|
||||
friend bool operator!=(const const_iterator& lhs,
|
||||
const const_iterator& rhs) {
|
||||
return lhs.storage_iterator_ != rhs.storage_iterator_;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class IdSet;
|
||||
|
||||
inline const_iterator(
|
||||
const IdSet* set,
|
||||
typename StorageType::const_iterator storage_iterator);
|
||||
|
||||
const IdSet* set_ = nullptr;
|
||||
typename StorageType::const_iterator storage_iterator_;
|
||||
};
|
||||
|
||||
// All iterators on sets are const; but STL still defines the `iterator`
|
||||
// type. The `flat_hash_set` defines two classes the but the policy makes both
|
||||
// constant. Here to simplify the code we use the same type.
|
||||
using iterator = const_iterator;
|
||||
|
||||
IdSet() = default;
|
||||
template <typename InputIt>
|
||||
inline IdSet(InputIt first, InputIt last);
|
||||
inline IdSet(std::initializer_list<value_type> ilist);
|
||||
|
||||
// Typically for internal use only.
|
||||
inline IdSet(const ModelStorage* storage, StorageType values);
|
||||
|
||||
inline const_iterator cbegin() const;
|
||||
inline const_iterator begin() const;
|
||||
|
||||
inline const_iterator cend() const;
|
||||
inline const_iterator end() const;
|
||||
|
||||
bool empty() const { return set_.empty(); }
|
||||
size_type size() const { return set_.size(); }
|
||||
inline void clear();
|
||||
void reserve(size_type count) { set_.reserve(count); }
|
||||
|
||||
inline std::pair<const_iterator, bool> insert(const K& k);
|
||||
template <typename InputIt>
|
||||
inline void insert(InputIt first, InputIt last);
|
||||
inline void insert(std::initializer_list<value_type> ilist);
|
||||
|
||||
inline std::pair<const_iterator, bool> emplace(const K& k);
|
||||
|
||||
// Returns the number of elements erased (zero or one).
|
||||
inline size_type erase(const K& k);
|
||||
// In STL erase(const_iterator) returns an iterator. But flat_hash_set instead
|
||||
// has void return types. So here we also use void.
|
||||
inline void erase(const_iterator pos);
|
||||
inline const_iterator erase(const_iterator first, const_iterator last);
|
||||
|
||||
inline void swap(IdSet& other);
|
||||
|
||||
inline size_type count(const K& k) const;
|
||||
inline bool contains(const K& k) const;
|
||||
inline const_iterator find(const K& k) const;
|
||||
inline std::pair<const_iterator, const_iterator> equal_range(
|
||||
const K& k) const;
|
||||
|
||||
const StorageType& raw_set() const { return set_; }
|
||||
const ModelStorage* storage() const { return storage_; }
|
||||
|
||||
friend bool operator==(const IdSet& lhs, const IdSet& rhs) {
|
||||
return lhs.storage_ == rhs.storage_ && lhs.set_ == rhs.set_;
|
||||
}
|
||||
friend bool operator!=(const IdSet& lhs, const IdSet& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
// CHECKs that storage_ and k.storage() matches when this set is not empty
|
||||
// (i.e. its storage_ is not null). When it is empty, simply check that
|
||||
// k.storage() is not null.
|
||||
inline void CheckModel(const K& k) const;
|
||||
// Sets storage_ to k.storage() if this set is empty (i.e. its storage_ is
|
||||
// null). Else CHECK that it has the same storage. It also CHECK that
|
||||
// k.storage() is not null.
|
||||
inline void CheckOrSetModel(const K& k);
|
||||
|
||||
// Invariant: storage == nullptr if and only if set_.empty().
|
||||
const ModelStorage* storage_ = nullptr;
|
||||
StorageType set_;
|
||||
};
|
||||
|
||||
// Calls a.swap(b).
|
||||
//
|
||||
// This function is used for making IdSet "swappable".
|
||||
// Ref: https://en.cppreference.com/w/cpp/named_req/Swappable.
|
||||
template <typename K>
|
||||
void swap(IdSet<K>& a, IdSet<K>& b) {
|
||||
a.swap(b);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Inline implementations
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// IdSet::const_iterator
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename K>
|
||||
typename IdSet<K>::const_iterator::reference
|
||||
IdSet<K>::const_iterator::operator*() const {
|
||||
return K(set_->storage_, *storage_iterator_);
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
internal::ArrowOperatorProxy<typename IdSet<K>::const_iterator::reference>
|
||||
IdSet<K>::const_iterator::operator->() const {
|
||||
return internal::ArrowOperatorProxy<reference>(**this);
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
typename IdSet<K>::const_iterator& IdSet<K>::const_iterator::operator++() {
|
||||
++storage_iterator_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
typename IdSet<K>::const_iterator IdSet<K>::const_iterator::operator++(int) {
|
||||
const_iterator ret = *this;
|
||||
++(*this);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
IdSet<K>::const_iterator::const_iterator(
|
||||
const IdSet* set, typename StorageType::const_iterator storage_iterator)
|
||||
: set_(set), storage_iterator_(std::move(storage_iterator)) {}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// IdSet
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename K>
|
||||
IdSet<K>::IdSet(const ModelStorage* storage, StorageType values)
|
||||
: storage_(values.empty() ? nullptr : storage), set_(std::move(values)) {
|
||||
if (!set_.empty()) {
|
||||
CHECK(storage_ != nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
template <typename InputIt>
|
||||
IdSet<K>::IdSet(InputIt first, InputIt last) {
|
||||
insert(first, last);
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
IdSet<K>::IdSet(std::initializer_list<value_type> ilist) {
|
||||
insert(ilist);
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
typename IdSet<K>::const_iterator IdSet<K>::cbegin() const {
|
||||
return const_iterator(this, set_.cbegin());
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
typename IdSet<K>::const_iterator IdSet<K>::begin() const {
|
||||
return cbegin();
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
typename IdSet<K>::const_iterator IdSet<K>::cend() const {
|
||||
return const_iterator(this, set_.cend());
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
typename IdSet<K>::const_iterator IdSet<K>::end() const {
|
||||
return cend();
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
void IdSet<K>::clear() {
|
||||
storage_ = nullptr;
|
||||
set_.clear();
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
std::pair<typename IdSet<K>::const_iterator, bool> IdSet<K>::insert(
|
||||
const K& k) {
|
||||
return emplace(k);
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
template <typename InputIt>
|
||||
void IdSet<K>::insert(const InputIt first, const InputIt last) {
|
||||
for (InputIt it = first; it != last; ++it) {
|
||||
insert(*it);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
void IdSet<K>::insert(std::initializer_list<value_type> ilist) {
|
||||
insert(ilist.begin(), ilist.end());
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
std::pair<typename IdSet<K>::const_iterator, bool> IdSet<K>::emplace(
|
||||
const K& k) {
|
||||
CheckOrSetModel(k);
|
||||
auto initial_ret = set_.emplace(k.typed_id());
|
||||
return std::make_pair(const_iterator(this, std::move(initial_ret.first)),
|
||||
initial_ret.second);
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
typename IdSet<K>::size_type IdSet<K>::erase(const K& k) {
|
||||
CheckModel(k);
|
||||
const size_type ret = set_.erase(k.typed_id());
|
||||
if (set_.empty()) {
|
||||
storage_ = nullptr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
void IdSet<K>::erase(const const_iterator pos) {
|
||||
set_.erase(pos.storage_iterator_);
|
||||
if (set_.empty()) {
|
||||
storage_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
typename IdSet<K>::const_iterator IdSet<K>::erase(const const_iterator first,
|
||||
const const_iterator last) {
|
||||
auto ret = set_.erase(first.storage_iterator_, last.storage_iterator_);
|
||||
if (set_.empty()) {
|
||||
storage_ = nullptr;
|
||||
}
|
||||
return const_iterator(this, std::move(ret));
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
void IdSet<K>::swap(IdSet& other) {
|
||||
using std::swap;
|
||||
swap(storage_, other.storage_);
|
||||
swap(set_, other.set_);
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
typename IdSet<K>::size_type IdSet<K>::count(const K& k) const {
|
||||
CheckModel(k);
|
||||
return set_.count(k.typed_id());
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
bool IdSet<K>::contains(const K& k) const {
|
||||
CheckModel(k);
|
||||
return set_.contains(k.typed_id());
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
typename IdSet<K>::const_iterator IdSet<K>::find(const K& k) const {
|
||||
CheckModel(k);
|
||||
return const_iterator(this, set_.find(k.typed_id()));
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
std::pair<typename IdSet<K>::const_iterator, typename IdSet<K>::const_iterator>
|
||||
IdSet<K>::equal_range(const K& k) const {
|
||||
const auto it = find(k);
|
||||
if (it == end()) {
|
||||
return {it, it};
|
||||
}
|
||||
return {it, std::next(it)};
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
void IdSet<K>::CheckModel(const K& k) const {
|
||||
CHECK(k.storage() != nullptr) << internal::kKeyHasNullModelStorage;
|
||||
CHECK(storage_ == nullptr || storage_ == k.storage())
|
||||
<< internal::kObjectsFromOtherModelStorage;
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
void IdSet<K>::CheckOrSetModel(const K& k) {
|
||||
CHECK(k.storage() != nullptr) << internal::kKeyHasNullModelStorage;
|
||||
if (storage_ == nullptr) {
|
||||
storage_ = k.storage();
|
||||
} else {
|
||||
CHECK_EQ(storage_, k.storage()) << internal::kObjectsFromOtherModelStorage;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace math_opt
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_CPP_ID_SET_H_
|
||||
67
ortools/math_opt/cpp/infeasible_subsystem_arguments.h
Normal file
67
ortools/math_opt/cpp/infeasible_subsystem_arguments.h
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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_MATH_OPT_CPP_INFEASIBLE_SUBSYSTEM_ARGUMENTS_H_
|
||||
#define OR_TOOLS_MATH_OPT_CPP_INFEASIBLE_SUBSYSTEM_ARGUMENTS_H_
|
||||
|
||||
#include "ortools/math_opt/core/solve_interrupter.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/cpp/message_callback.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/cpp/parameters.h" // IWYU pragma: export
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
// Arguments passed to InfeasibleSubsystem() to control the solver.
|
||||
struct InfeasibleSubsystemArguments {
|
||||
// Model independent parameters, e.g. time limit.
|
||||
SolveParameters parameters;
|
||||
|
||||
// An optional callback for messages emitted by the solver.
|
||||
//
|
||||
// When set it enables the solver messages and ignores the `enable_output` in
|
||||
// solve parameters; messages are redirected to the callback and not printed
|
||||
// on stdout/stderr/logs anymore.
|
||||
//
|
||||
// See PrinterMessageCallback() for logging to stdout/stderr.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// // To print messages to stdout with a prefix.
|
||||
// ASSIGN_OR_RETURN(
|
||||
// const InfeasibleSubsystemResult result,
|
||||
// InfeasibleSubsystem(model, SolverType::kGurobi,
|
||||
// { .message_callback = PrinterMessageCallback(std::cout,
|
||||
// "logs| "); });
|
||||
MessageCallback message_callback = nullptr;
|
||||
|
||||
// An optional interrupter that the solver can use to interrupt the solve
|
||||
// early.
|
||||
//
|
||||
// Usage:
|
||||
// auto interrupter = std::make_shared<SolveInterrupter>();
|
||||
//
|
||||
// // Use another thread to trigger the interrupter.
|
||||
// RunInOtherThread([interrupter](){
|
||||
// ... wait for something that should interrupt the solve ...
|
||||
// interrupter->Interrupt();
|
||||
// });
|
||||
//
|
||||
// ASSIGN_OR_RETURN(const InfeasibleSubsystemResult result,
|
||||
// InfeasibleSubsystem(model, SolverType::kGurobi,
|
||||
// { .interrupter = interrupter.get() });
|
||||
//
|
||||
SolveInterrupter* interrupter = nullptr;
|
||||
};
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_CPP_INFEASIBLE_SUBSYSTEM_ARGUMENTS_H_
|
||||
305
ortools/math_opt/cpp/infeasible_subsystem_result.cc
Normal file
305
ortools/math_opt/cpp/infeasible_subsystem_result.cc
Normal file
@@ -0,0 +1,305 @@
|
||||
// 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/math_opt/cpp/infeasible_subsystem_result.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/algorithm/container.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/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "ortools/base/status_macros.h"
|
||||
#include "ortools/math_opt/cpp/enums.h"
|
||||
#include "ortools/math_opt/cpp/key_types.h"
|
||||
#include "ortools/math_opt/cpp/solve_result.h"
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
#include "ortools/math_opt/infeasible_subsystem.pb.h"
|
||||
#include "ortools/math_opt/result.pb.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
#include "ortools/math_opt/validators/infeasible_subsystem_validator.h"
|
||||
#include "ortools/util/status_macros.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
ModelSubset::Bounds ModelSubset::Bounds::FromProto(
|
||||
const ModelSubsetProto::Bounds& bounds_proto) {
|
||||
return {.lower = bounds_proto.lower(), .upper = bounds_proto.upper()};
|
||||
}
|
||||
|
||||
ModelSubsetProto::Bounds ModelSubset::Bounds::Proto() const {
|
||||
ModelSubsetProto::Bounds proto;
|
||||
proto.set_lower(lower);
|
||||
proto.set_upper(upper);
|
||||
return proto;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const ModelSubset::Bounds& bounds) {
|
||||
const auto bool_to_str = [](const bool b) { return b ? "true" : "false"; };
|
||||
out << "{lower: " << bool_to_str(bounds.lower)
|
||||
<< ", upper: " << bool_to_str(bounds.upper) << "}";
|
||||
return out;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename K>
|
||||
absl::Status BoundsMapProtoToCpp(
|
||||
const google::protobuf::Map<int64_t, ModelSubsetProto::Bounds>& source,
|
||||
absl::flat_hash_map<K, ModelSubset::Bounds>& target,
|
||||
const ModelStorage* const model,
|
||||
bool (ModelStorage::*const contains_strong_id)(typename K::IdType id) const,
|
||||
const absl::string_view object_name) {
|
||||
for (const auto& [raw_id, bounds_proto] : source) {
|
||||
const typename K::IdType strong_id(raw_id);
|
||||
if (!(model->*contains_strong_id)(strong_id)) {
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< "no " << object_name << " with id: " << raw_id;
|
||||
}
|
||||
target.insert(
|
||||
{K(model, strong_id), ModelSubset::Bounds::FromProto(bounds_proto)});
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
absl::Status RepeatedIdsProtoToCpp(
|
||||
const google::protobuf::RepeatedField<int64_t>& source,
|
||||
absl::flat_hash_set<K>& target, const ModelStorage* const model,
|
||||
bool (ModelStorage::*const contains_strong_id)(typename K::IdType id) const,
|
||||
const absl::string_view object_name) {
|
||||
for (const int64_t raw_id : source) {
|
||||
const typename K::IdType strong_id(raw_id);
|
||||
if (!(model->*contains_strong_id)(strong_id)) {
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< "no " << object_name << " with id: " << raw_id;
|
||||
}
|
||||
target.insert(K(model, strong_id));
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
google::protobuf::Map<int64_t, ModelSubsetProto::Bounds> BoundsMapCppToProto(
|
||||
const absl::flat_hash_map<K, ModelSubset::Bounds> source) {
|
||||
google::protobuf::Map<int64_t, ModelSubsetProto::Bounds> result;
|
||||
for (const auto& [key, bounds] : source) {
|
||||
result.insert({key.id(), bounds.Proto()});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
google::protobuf::RepeatedField<int64_t> RepeatedIdsCppToProto(
|
||||
const absl::flat_hash_set<K>& source) {
|
||||
google::protobuf::RepeatedField<int64_t> result;
|
||||
for (const auto object : source) {
|
||||
result.Add(object.id());
|
||||
}
|
||||
absl::c_sort(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
absl::StatusOr<ModelSubset> ModelSubset::FromProto(
|
||||
const ModelStorage* const model, const ModelSubsetProto& proto) {
|
||||
ModelSubset model_subset;
|
||||
RETURN_IF_ERROR(BoundsMapProtoToCpp(proto.variable_bounds(),
|
||||
model_subset.variable_bounds, model,
|
||||
&ModelStorage::has_variable, "variable"))
|
||||
<< "element of variable_bounds";
|
||||
RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
|
||||
proto.variable_integrality(), model_subset.variable_integrality, model,
|
||||
&ModelStorage::has_variable, "variable"))
|
||||
<< "element of variable_integrality";
|
||||
RETURN_IF_ERROR(BoundsMapProtoToCpp(
|
||||
proto.linear_constraints(), model_subset.linear_constraints, model,
|
||||
&ModelStorage::has_linear_constraint, "linear constraint"));
|
||||
RETURN_IF_ERROR(BoundsMapProtoToCpp(
|
||||
proto.quadratic_constraints(), model_subset.quadratic_constraints, model,
|
||||
&ModelStorage::has_constraint, "quadratic constraint"));
|
||||
RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
|
||||
proto.second_order_cone_constraints(),
|
||||
model_subset.second_order_cone_constraints, model,
|
||||
&ModelStorage::has_constraint, "second-order cone constraint"));
|
||||
RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
|
||||
proto.sos1_constraints(), model_subset.sos1_constraints, model,
|
||||
&ModelStorage::has_constraint, "SOS1 constraint"));
|
||||
RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
|
||||
proto.sos2_constraints(), model_subset.sos2_constraints, model,
|
||||
&ModelStorage::has_constraint, "SOS2 constraint"));
|
||||
RETURN_IF_ERROR(RepeatedIdsProtoToCpp(
|
||||
proto.indicator_constraints(), model_subset.indicator_constraints, model,
|
||||
&ModelStorage::has_constraint, "indicator constraint"));
|
||||
return model_subset;
|
||||
}
|
||||
|
||||
ModelSubsetProto ModelSubset::Proto() const {
|
||||
ModelSubsetProto proto;
|
||||
*proto.mutable_variable_bounds() = BoundsMapCppToProto(variable_bounds);
|
||||
*proto.mutable_variable_integrality() =
|
||||
RepeatedIdsCppToProto(variable_integrality);
|
||||
*proto.mutable_linear_constraints() = BoundsMapCppToProto(linear_constraints);
|
||||
*proto.mutable_quadratic_constraints() =
|
||||
BoundsMapCppToProto(quadratic_constraints);
|
||||
*proto.mutable_second_order_cone_constraints() =
|
||||
RepeatedIdsCppToProto(second_order_cone_constraints);
|
||||
*proto.mutable_sos1_constraints() = RepeatedIdsCppToProto(sos1_constraints);
|
||||
*proto.mutable_sos2_constraints() = RepeatedIdsCppToProto(sos2_constraints);
|
||||
*proto.mutable_indicator_constraints() =
|
||||
RepeatedIdsCppToProto(indicator_constraints);
|
||||
return proto;
|
||||
}
|
||||
|
||||
absl::Status ModelSubset::CheckModelStorage(
|
||||
const ModelStorage* const expected_storage) const {
|
||||
const auto validate_map_keys =
|
||||
[expected_storage](const auto& map,
|
||||
const absl::string_view name) -> absl::Status {
|
||||
for (const auto& [key, unused] : map) {
|
||||
RETURN_IF_ERROR(
|
||||
internal::CheckModelStorage(key.storage(), expected_storage))
|
||||
<< "invalid key " << key << " in " << name;
|
||||
}
|
||||
return absl::OkStatus();
|
||||
};
|
||||
const auto validate_set_elements =
|
||||
[expected_storage](const auto& set,
|
||||
const absl::string_view name) -> absl::Status {
|
||||
for (const auto entry : set) {
|
||||
RETURN_IF_ERROR(
|
||||
internal::CheckModelStorage(entry.storage(), expected_storage))
|
||||
<< "invalid entry " << entry << " in " << name;
|
||||
}
|
||||
return absl::OkStatus();
|
||||
};
|
||||
|
||||
RETURN_IF_ERROR(validate_map_keys(variable_bounds, "variable_bounds"));
|
||||
RETURN_IF_ERROR(
|
||||
validate_set_elements(variable_integrality, "variable_integrality"));
|
||||
RETURN_IF_ERROR(validate_map_keys(linear_constraints, "linear_constraints"));
|
||||
RETURN_IF_ERROR(
|
||||
validate_map_keys(quadratic_constraints, "quadratic_constraints"));
|
||||
RETURN_IF_ERROR(validate_set_elements(second_order_cone_constraints,
|
||||
"second_order_cone_constraints"));
|
||||
RETURN_IF_ERROR(validate_set_elements(sos1_constraints, "sos1_constraints"));
|
||||
RETURN_IF_ERROR(validate_set_elements(sos2_constraints, "sos2_constraints"));
|
||||
RETURN_IF_ERROR(
|
||||
validate_set_elements(indicator_constraints, "indicator_constraints"));
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
bool ModelSubset::empty() const {
|
||||
return variable_bounds.empty() && variable_integrality.empty() &&
|
||||
linear_constraints.empty() && quadratic_constraints.empty() &&
|
||||
second_order_cone_constraints.empty() && sos1_constraints.empty() &&
|
||||
sos2_constraints.empty() && indicator_constraints.empty();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const ModelSubset& model_subset) {
|
||||
const auto stream_bounds_map = [&out](const auto& map,
|
||||
const absl::string_view name) {
|
||||
out << name << ": {"
|
||||
<< absl::StrJoin(SortedKeys(map), ", ",
|
||||
[map](std::string* out, const auto& key) {
|
||||
absl::StrAppendFormat(
|
||||
out, "{%s, %s}", absl::FormatStreamed(key),
|
||||
absl::FormatStreamed(map.at(key)));
|
||||
})
|
||||
<< "}";
|
||||
};
|
||||
const auto stream_set = [&out](const auto& set,
|
||||
const absl::string_view name) {
|
||||
out << name << ": {"
|
||||
<< absl::StrJoin(SortedElements(set), ", ", absl::StreamFormatter())
|
||||
<< "}";
|
||||
};
|
||||
|
||||
out << "{";
|
||||
stream_bounds_map(model_subset.variable_bounds, "variable_bounds");
|
||||
out << ", ";
|
||||
stream_set(model_subset.variable_integrality, "variable_integrality");
|
||||
out << ", ";
|
||||
stream_bounds_map(model_subset.linear_constraints, "linear_constraints");
|
||||
out << ", ";
|
||||
stream_bounds_map(model_subset.quadratic_constraints,
|
||||
"quadratic_constraints");
|
||||
out << ", ";
|
||||
stream_set(model_subset.second_order_cone_constraints,
|
||||
"second_order_cone_constraints");
|
||||
out << ", ";
|
||||
stream_set(model_subset.sos1_constraints, "sos1_constraints");
|
||||
out << ", ";
|
||||
stream_set(model_subset.sos2_constraints, "sos2_constraints");
|
||||
out << ", ";
|
||||
stream_set(model_subset.indicator_constraints, "indicator_constraints");
|
||||
out << "}";
|
||||
return out;
|
||||
}
|
||||
|
||||
absl::StatusOr<InfeasibleSubsystemResult> InfeasibleSubsystemResult::FromProto(
|
||||
const ModelStorage* const model,
|
||||
const InfeasibleSubsystemResultProto& result_proto) {
|
||||
InfeasibleSubsystemResult result;
|
||||
const std::optional<FeasibilityStatus> feasibility =
|
||||
EnumFromProto(result_proto.feasibility());
|
||||
if (!feasibility.has_value()) {
|
||||
return absl::InvalidArgumentError(
|
||||
"InfeasibleSubsystemResultProto.feasibility must be specified");
|
||||
}
|
||||
// We intentionally call this validator after checking `feasibility` so that
|
||||
// we can return a friendlier message for UNSPECIFIED.
|
||||
RETURN_IF_ERROR(ValidateInfeasibleSubsystemResultNoModel(result_proto));
|
||||
result.feasibility = *feasibility;
|
||||
OR_ASSIGN_OR_RETURN3(
|
||||
result.infeasible_subsystem,
|
||||
ModelSubset::FromProto(model, result_proto.infeasible_subsystem()),
|
||||
_ << "invalid InfeasibleSubsystemResultProto.infeasible_subsystem");
|
||||
result.is_minimal = result_proto.is_minimal();
|
||||
return result;
|
||||
}
|
||||
|
||||
InfeasibleSubsystemResultProto InfeasibleSubsystemResult::Proto() const {
|
||||
InfeasibleSubsystemResultProto proto;
|
||||
proto.set_feasibility(EnumToProto(feasibility));
|
||||
if (!infeasible_subsystem.empty()) {
|
||||
*proto.mutable_infeasible_subsystem() = infeasible_subsystem.Proto();
|
||||
}
|
||||
proto.set_is_minimal(is_minimal);
|
||||
return proto;
|
||||
}
|
||||
|
||||
absl::Status InfeasibleSubsystemResult::CheckModelStorage(
|
||||
const ModelStorage* const expected_storage) const {
|
||||
return infeasible_subsystem.CheckModelStorage(expected_storage);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out,
|
||||
const InfeasibleSubsystemResult& result) {
|
||||
out << "{feasibility: " << result.feasibility
|
||||
<< ", infeasible_subsystem: " << result.infeasible_subsystem
|
||||
<< ", is_minimal: " << (result.is_minimal ? "true" : "false") << "}";
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
129
ortools/math_opt/cpp/infeasible_subsystem_result.h
Normal file
129
ortools/math_opt/cpp/infeasible_subsystem_result.h
Normal file
@@ -0,0 +1,129 @@
|
||||
// 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_MATH_OPT_CPP_INFEASIBLE_SUBSYSTEM_RESULT_H_
|
||||
#define OR_TOOLS_MATH_OPT_CPP_INFEASIBLE_SUBSYSTEM_RESULT_H_
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#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 "ortools/math_opt/constraints/indicator/indicator_constraint.h"
|
||||
#include "ortools/math_opt/constraints/quadratic/quadratic_constraint.h"
|
||||
#include "ortools/math_opt/constraints/second_order_cone/second_order_cone_constraint.h"
|
||||
#include "ortools/math_opt/constraints/sos/sos1_constraint.h"
|
||||
#include "ortools/math_opt/constraints/sos/sos2_constraint.h"
|
||||
#include "ortools/math_opt/cpp/linear_constraint.h"
|
||||
#include "ortools/math_opt/cpp/solve_result.h"
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
#include "ortools/math_opt/infeasible_subsystem.pb.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
// Represents a subset of the constraints (including variable bounds and
|
||||
// integrality) of a `Model`.
|
||||
//
|
||||
// The fields contain `Variable` and Constraint objects which retain pointers
|
||||
// back to their associated `Model`. Therefore, a `ModelSubset` should not
|
||||
// outlive the `Model` it is in reference to.
|
||||
struct ModelSubset {
|
||||
struct Bounds {
|
||||
static Bounds FromProto(const ModelSubsetProto::Bounds& bounds_proto);
|
||||
ModelSubsetProto::Bounds Proto() const;
|
||||
|
||||
bool empty() const { return !lower && !upper; }
|
||||
|
||||
bool lower = false;
|
||||
bool upper = false;
|
||||
};
|
||||
|
||||
// Returns the `ModelSubset` equivalent to `proto`.
|
||||
//
|
||||
// Returns an error when `model` does not contain a variable or constraint
|
||||
// associated with an index present in `proto`.
|
||||
static absl::StatusOr<ModelSubset> FromProto(const ModelStorage* model,
|
||||
const ModelSubsetProto& proto);
|
||||
|
||||
// Returns the proto equivalent of this object.
|
||||
//
|
||||
// The caller should use CheckModelStorage() as this function does not check
|
||||
// internal consistency of the referenced variables and constraints.
|
||||
ModelSubsetProto Proto() const;
|
||||
|
||||
// Returns a failure if the `Variable` and Constraints contained in the fields
|
||||
// do not belong to the input expected_storage (which must not be nullptr).
|
||||
absl::Status CheckModelStorage(const ModelStorage* expected_storage) const;
|
||||
|
||||
// True if this object corresponds to the empty subset.
|
||||
bool empty() const;
|
||||
|
||||
absl::flat_hash_map<Variable, Bounds> variable_bounds;
|
||||
absl::flat_hash_set<Variable> variable_integrality;
|
||||
absl::flat_hash_map<LinearConstraint, Bounds> linear_constraints;
|
||||
absl::flat_hash_map<QuadraticConstraint, Bounds> quadratic_constraints;
|
||||
absl::flat_hash_set<SecondOrderConeConstraint> second_order_cone_constraints;
|
||||
absl::flat_hash_set<Sos1Constraint> sos1_constraints;
|
||||
absl::flat_hash_set<Sos2Constraint> sos2_constraints;
|
||||
absl::flat_hash_set<IndicatorConstraint> indicator_constraints;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const ModelSubset::Bounds& bounds);
|
||||
std::ostream& operator<<(std::ostream& out, const ModelSubset& model_subset);
|
||||
|
||||
struct InfeasibleSubsystemResult {
|
||||
// Returns the `InfeasibleSubsystemResult` equivalent to `proto`.
|
||||
//
|
||||
// Returns an error when:
|
||||
// * `model` does not contain a variable or constraint associated with an
|
||||
// index present in `proto.infeasible_subsystem`.
|
||||
// * ValidateInfeasibleSubsystemResultNoModel(result_proto) fails.
|
||||
static absl::StatusOr<InfeasibleSubsystemResult> FromProto(
|
||||
const ModelStorage* model,
|
||||
const InfeasibleSubsystemResultProto& result_proto);
|
||||
|
||||
// Returns the proto equivalent of this object.
|
||||
//
|
||||
// The caller should use CheckModelStorage() before calling this function as
|
||||
// it does not check internal consistency of the referenced variables and
|
||||
// constraints.
|
||||
InfeasibleSubsystemResultProto Proto() const;
|
||||
|
||||
// Returns a failure if this object contains references to a model other than
|
||||
// `expected_storage` (which must not be nullptr).
|
||||
absl::Status CheckModelStorage(const ModelStorage* expected_storage) const;
|
||||
|
||||
// The primal feasibility status of the model, as determined by the solver.
|
||||
FeasibilityStatus feasibility = FeasibilityStatus::kUndetermined;
|
||||
|
||||
// An infeasible subsystem of the input model. Set if `feasible` is
|
||||
// kInfeasible, and empty otherwise. The IDs correspond to those constraints
|
||||
// included in the infeasible subsystem. Submessages with `Bounds` values
|
||||
// indicate which side of a potentially ranged constraint are included in the
|
||||
// subsystem: lower bound, upper bound, or both.
|
||||
ModelSubset infeasible_subsystem;
|
||||
|
||||
// True if the solver has certified that the returned infeasible subsystem is
|
||||
// minimal (i.e., the instance is feasible if any additional constraint is
|
||||
// removed).
|
||||
bool is_minimal = false;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& out,
|
||||
const InfeasibleSubsystemResult& result);
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_CPP_INFEASIBLE_SUBSYSTEM_RESULT_H_
|
||||
@@ -17,14 +17,11 @@
|
||||
// This header defines the common properties of "key types" and some related
|
||||
// constants.
|
||||
//
|
||||
// MathOpt provides optimized custom collections for variables and
|
||||
// constraints. This file contains implementation details for these custom
|
||||
// collections and should not be needed by users.
|
||||
//
|
||||
// Key types are types that are used as identifiers in the C++ interface where
|
||||
// the ModelStorage is using typed integers. They are pairs of (storage,
|
||||
// typed_index) where `storage` is a pointer on an ModelStorage and
|
||||
// `typed_index` is the typed integer type used in ModelStorage.
|
||||
// `typed_index` is the typed integer type used in ModelStorage (or a pair of
|
||||
// typed integers for QuadraticTermKey).
|
||||
//
|
||||
// A key type K must match the following requirements:
|
||||
// - K::IdType is a value type used for indices.
|
||||
@@ -35,17 +32,119 @@
|
||||
// It must return a non-null pointer.
|
||||
// - K::IdType is a valid key for absl::flat_hash_map or absl::flat_hash_set
|
||||
// (supports hash and ==).
|
||||
//
|
||||
// These requirements are met by Variable and LinearConstraint.
|
||||
// - the is_key_type_v<> below should include them.
|
||||
#ifndef OR_TOOLS_MATH_OPT_CPP_KEY_TYPES_H_
|
||||
#define OR_TOOLS_MATH_OPT_CPP_KEY_TYPES_H_
|
||||
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
// Forward declarations of types implementing the keys interface defined at the
|
||||
// top of this file.
|
||||
class Variable;
|
||||
class LinearConstraint;
|
||||
class QuadraticConstraint;
|
||||
class SecondOrderConeConstraint;
|
||||
class Sos1Constraint;
|
||||
class Sos2Constraint;
|
||||
class IndicatorConstraint;
|
||||
class QuadraticTermKey;
|
||||
class Objective;
|
||||
|
||||
// True for types in MathOpt that implements the keys interface defined at the
|
||||
// top of this file.
|
||||
//
|
||||
// This is used in conjunction with std::enable_if_t<> to prevent Argument
|
||||
// Dependent Lookup (ADL) from selecting some overload defined in MathOpt
|
||||
// because one of the template type is in MathOpt. For example the SortedKeys()
|
||||
// function below could be selected as a valid overload in another namespace if
|
||||
// the values in the hash map are in the math_opt namespace.
|
||||
template <typename T>
|
||||
constexpr inline bool is_key_type_v =
|
||||
(std::is_same_v<T, Variable> || std::is_same_v<T, LinearConstraint> ||
|
||||
std::is_same_v<T, QuadraticConstraint> ||
|
||||
std::is_same_v<T, SecondOrderConeConstraint> ||
|
||||
std::is_same_v<T, Sos1Constraint> || std::is_same_v<T, Sos2Constraint> ||
|
||||
std::is_same_v<T, IndicatorConstraint> ||
|
||||
std::is_same_v<T, QuadraticTermKey> || std::is_same_v<T, Objective>);
|
||||
|
||||
// Returns the keys of the map sorted by their (storage(), type_id()).
|
||||
//
|
||||
// Implementation note: here we must use std::enable_if to prevent Argument
|
||||
// Dependent Lookup (ADL) from selecting this overload for maps which values are
|
||||
// in MathOpt but keys are not.
|
||||
template <typename Map,
|
||||
typename = std::enable_if_t<is_key_type_v<typename Map::key_type>>>
|
||||
std::vector<typename Map::key_type> SortedKeys(const Map& map) {
|
||||
using K = typename Map::key_type;
|
||||
std::vector<K> result;
|
||||
result.reserve(map.size());
|
||||
for (const typename Map::const_reference item : map) {
|
||||
result.push_back(item.first);
|
||||
}
|
||||
absl::c_sort(result, [](const K& lhs, const K& rhs) {
|
||||
if (lhs.storage() != rhs.storage()) {
|
||||
return lhs.storage() < rhs.storage();
|
||||
}
|
||||
return lhs.typed_id() < rhs.typed_id();
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns the elements of the set sorted by their (storage(), type_id()).
|
||||
//
|
||||
// Implementation note: here we must use std::enable_if to prevent Argument
|
||||
// Dependent Lookup (ADL) from selecting this overload for maps which values are
|
||||
// in MathOpt but keys are not.
|
||||
template <typename Set,
|
||||
typename = std::enable_if_t<is_key_type_v<typename Set::key_type>>>
|
||||
std::vector<typename Set::key_type> SortedElements(const Set& set) {
|
||||
using K = typename Set::key_type;
|
||||
std::vector<K> result;
|
||||
result.reserve(set.size());
|
||||
for (const typename Set::const_reference item : set) {
|
||||
result.push_back(item);
|
||||
}
|
||||
absl::c_sort(result, [](const K& lhs, const K& rhs) {
|
||||
if (lhs.storage() != rhs.storage()) {
|
||||
return lhs.storage() < rhs.storage();
|
||||
}
|
||||
return lhs.typed_id() < rhs.typed_id();
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns the values corresponding to the keys. Keys must be present in the
|
||||
// input map.
|
||||
//
|
||||
// The keys must be in a type convertible to absl::Span<const K>.
|
||||
//
|
||||
// Implementation note: here we must use std::enable_if to prevent Argument
|
||||
// Dependent Lookup (ADL) from selecting this overload for maps which values are
|
||||
// in MathOpt but keys are not.
|
||||
template <typename Map, typename Keys,
|
||||
typename = std::enable_if_t<is_key_type_v<typename Map::key_type>>>
|
||||
std::vector<typename Map::mapped_type> Values(const Map& map,
|
||||
const Keys& keys) {
|
||||
using K = typename Map::key_type;
|
||||
const absl::Span<const K> keys_span = keys;
|
||||
std::vector<typename Map::mapped_type> result;
|
||||
result.reserve(keys_span.size());
|
||||
for (const K& key : keys_span) {
|
||||
result.push_back(map.at(key));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
// The CHECK message to use when a KeyType::storage() is nullptr.
|
||||
@@ -79,7 +178,6 @@ inline absl::Status CheckModelStorage(
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace math_opt
|
||||
} // namespace operations_research
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_CPP_KEY_TYPES_H_
|
||||
|
||||
@@ -19,15 +19,15 @@
|
||||
#define OR_TOOLS_MATH_OPT_CPP_LINEAR_CONSTRAINT_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/constraints/util/model_util.h"
|
||||
#include "ortools/math_opt/cpp/id_map.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/cpp/key_types.h"
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
@@ -79,10 +79,8 @@ class LinearConstraint {
|
||||
LinearConstraintId id_;
|
||||
};
|
||||
|
||||
// Implements the API of std::unordered_map<LinearConstraint, V>, but forbids
|
||||
// LinearConstraints from different models in the same map.
|
||||
template <typename V>
|
||||
using LinearConstraintMap = IdMap<LinearConstraint, V>;
|
||||
using LinearConstraintMap = absl::flat_hash_map<LinearConstraint, V>;
|
||||
|
||||
// Streams the name of the constraint, as registered upon constraint creation,
|
||||
// or a short default if none was provided.
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
|
||||
#include "ortools/math_opt/cpp/id_set.h"
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "ortools/base/status_macros.h"
|
||||
#include "ortools/math_opt/cpp/key_types.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
|
||||
@@ -89,13 +91,17 @@ struct MapFilter {
|
||||
// filter.emplace(decision_vars.begin(), decision_vars.end());
|
||||
//
|
||||
// Prefer using MakeSkipAllFilter() or MakeKeepKeysFilter() when appropriate.
|
||||
std::optional<IdSet<KeyType>> filtered_keys;
|
||||
std::optional<absl::flat_hash_set<KeyType>> filtered_keys;
|
||||
|
||||
// Returns the model of filtered keys. It returns a non-null value if and only
|
||||
// if the filtered_keys is set and non-empty.
|
||||
inline const ModelStorage* storage() const;
|
||||
// Returns a failure if the keys don't belong to the input expected_storage
|
||||
// (which must not be nullptr).
|
||||
inline absl::Status CheckModelStorage(
|
||||
const ModelStorage* expected_storage) const;
|
||||
|
||||
// Returns the proto corresponding to this filter.
|
||||
//
|
||||
// The caller should use CheckModelStorage() as this function does not check
|
||||
// internal consistency of the referenced variables and constraints.
|
||||
SparseVectorFilterProto Proto() const;
|
||||
};
|
||||
|
||||
@@ -159,23 +165,32 @@ MapFilter<KeyType> MakeKeepKeysFilter(std::initializer_list<KeyType> keys) {
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename KeyType>
|
||||
const ModelStorage* MapFilter<KeyType>::storage() const {
|
||||
return filtered_keys ? filtered_keys->storage() : nullptr;
|
||||
absl::Status MapFilter<KeyType>::CheckModelStorage(
|
||||
const ModelStorage* expected_storage) const {
|
||||
if (!filtered_keys.has_value()) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
for (const KeyType& k : filtered_keys.value()) {
|
||||
RETURN_IF_ERROR(internal::CheckModelStorage(
|
||||
/*storage=*/k.storage(),
|
||||
/*expected_storage=*/expected_storage));
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
template <typename KeyType>
|
||||
SparseVectorFilterProto MapFilter<KeyType>::Proto() const {
|
||||
SparseVectorFilterProto ret;
|
||||
ret.set_skip_zero_values(skip_zero_values);
|
||||
if (filtered_keys) {
|
||||
if (filtered_keys.has_value()) {
|
||||
ret.set_filter_by_ids(true);
|
||||
const auto filtered_ids = ret.mutable_filtered_ids();
|
||||
filtered_ids->Reserve(filtered_keys->size());
|
||||
for (const auto id : filtered_keys->raw_set()) {
|
||||
filtered_ids->Add(id.value());
|
||||
auto& filtered_ids = *ret.mutable_filtered_ids();
|
||||
filtered_ids.Reserve(static_cast<int>(filtered_keys.value().size()));
|
||||
for (const auto k : filtered_keys.value()) {
|
||||
filtered_ids.Add(k.typed_id().value());
|
||||
}
|
||||
// Iteration on the set is random but we want the proto to be stable.
|
||||
std::sort(filtered_ids->begin(), filtered_ids->end());
|
||||
absl::c_sort(filtered_ids);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,844 +0,0 @@
|
||||
// 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/math_opt/cpp/matchers.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/math_opt/cpp/math_opt.h"
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::testing::AllOf;
|
||||
using ::testing::AllOfArray;
|
||||
using ::testing::AnyOf;
|
||||
using ::testing::AnyOfArray;
|
||||
using ::testing::Contains;
|
||||
using ::testing::DoubleNear;
|
||||
using ::testing::Eq;
|
||||
using ::testing::ExplainMatchResult;
|
||||
using ::testing::Field;
|
||||
using ::testing::IsEmpty;
|
||||
using ::testing::Matcher;
|
||||
using ::testing::MatcherInterface;
|
||||
using ::testing::MatchResultListener;
|
||||
using ::testing::Optional;
|
||||
using ::testing::PrintToString;
|
||||
using ::testing::Property;
|
||||
} // namespace
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Printing
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
struct Printer {
|
||||
explicit Printer(const T& t) : value(t) {}
|
||||
|
||||
const T& value;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const Printer& printer) {
|
||||
os << PrintToString(printer.value);
|
||||
return os;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Printer<T> Print(const T& t) {
|
||||
return Printer<T>(t);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void PrintTo(const Termination& termination, std::ostream* os) {
|
||||
*os << "{reason: " << termination.reason;
|
||||
if (termination.limit.has_value()) {
|
||||
*os << ", limit: " << *termination.limit;
|
||||
}
|
||||
*os << ", detail: " << Print(termination.detail) << "}";
|
||||
}
|
||||
|
||||
void PrintTo(const PrimalSolution& primal_solution, std::ostream* const os) {
|
||||
*os << "{variable_values: " << Print(primal_solution.variable_values)
|
||||
<< ", objective_value: " << Print(primal_solution.objective_value)
|
||||
<< ", feasibility_status: " << Print(primal_solution.feasibility_status)
|
||||
<< "}";
|
||||
}
|
||||
|
||||
void PrintTo(const DualSolution& dual_solution, std::ostream* const os) {
|
||||
*os << "{dual_values: " << Print(dual_solution.dual_values)
|
||||
<< ", reduced_costs: " << Print(dual_solution.reduced_costs)
|
||||
<< ", objective_value: " << Print(dual_solution.objective_value)
|
||||
<< ", feasibility_status: " << Print(dual_solution.feasibility_status)
|
||||
<< "}";
|
||||
}
|
||||
|
||||
void PrintTo(const PrimalRay& primal_ray, std::ostream* const os) {
|
||||
*os << "{variable_values: " << Print(primal_ray.variable_values) << "}";
|
||||
}
|
||||
|
||||
void PrintTo(const DualRay& dual_ray, std::ostream* const os) {
|
||||
*os << "{dual_values: " << Print(dual_ray.dual_values)
|
||||
<< ", reduced_costs: " << Print(dual_ray.reduced_costs) << "}";
|
||||
}
|
||||
|
||||
void PrintTo(const Basis& basis, std::ostream* const os) {
|
||||
*os << "{variable_status: " << Print(basis.variable_status)
|
||||
<< ", constraint_status: " << Print(basis.constraint_status)
|
||||
<< ", basic_dual_feasibility: " << Print(basis.basic_dual_feasibility)
|
||||
<< "}";
|
||||
}
|
||||
|
||||
void PrintTo(const Solution& solution, std::ostream* const os) {
|
||||
*os << "{primal_solution: " << Print(solution.primal_solution)
|
||||
<< ", dual_solution: " << Print(solution.dual_solution)
|
||||
<< ", basis: " << Print(solution.basis) << "}";
|
||||
}
|
||||
|
||||
void PrintTo(const SolveResult& result, std::ostream* const os) {
|
||||
*os << "{termination: " << Print(result.termination)
|
||||
<< ", solve_stats: " << Print(result.solve_stats)
|
||||
<< ", solutions: " << Print(result.solutions)
|
||||
<< ", primal_rays: " << Print(result.primal_rays)
|
||||
<< ", dual_rays: " << Print(result.dual_rays) << "}";
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// IdMap Matchers
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename K>
|
||||
class IdMapMatcher : public MatcherInterface<IdMap<K, double>> {
|
||||
public:
|
||||
IdMapMatcher(IdMap<K, double> expected, const bool all_keys,
|
||||
const double tolerance)
|
||||
: expected_(std::move(expected)),
|
||||
all_keys_(all_keys),
|
||||
tolerance_(tolerance) {
|
||||
for (const auto [k, v] : expected_) {
|
||||
CHECK(!std::isnan(v)) << "Illegal NaN for key: " << k;
|
||||
}
|
||||
}
|
||||
|
||||
bool MatchAndExplain(IdMap<K, double> actual,
|
||||
MatchResultListener* const os) const override {
|
||||
for (const auto& [key, value] : expected_) {
|
||||
if (!actual.contains(key)) {
|
||||
*os << "expected key " << key << " not found";
|
||||
return false;
|
||||
}
|
||||
if (!(std::abs(value - actual.at(key)) <= tolerance_)) {
|
||||
*os << "value for key " << key
|
||||
<< " not within tolerance, expected: " << value
|
||||
<< " but found: " << actual.at(key);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Post condition: expected_ is a subset of actual.
|
||||
if (all_keys_ && expected_.size() != actual.size()) {
|
||||
for (const auto& [key, value] : actual) {
|
||||
if (!expected_.contains(key)) {
|
||||
*os << "found unexpected key " << key << " in actual";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// expected_ subset of actual && expected_.size() != actual.size() implies
|
||||
// that there is a member A of actual not in expected. When the loop above
|
||||
// hits A, it will return, thus this line is unreachable.
|
||||
LOG(FATAL) << "unreachable";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DescribeTo(std::ostream* const os) const override {
|
||||
if (all_keys_) {
|
||||
*os << "has identical keys to ";
|
||||
} else {
|
||||
*os << "keys are contained in ";
|
||||
}
|
||||
PrintTo(expected_, os);
|
||||
*os << " and values within " << tolerance_;
|
||||
}
|
||||
|
||||
void DescribeNegationTo(std::ostream* const os) const override {
|
||||
if (all_keys_) {
|
||||
*os << "either keys differ from ";
|
||||
} else {
|
||||
*os << "either has a key not in ";
|
||||
}
|
||||
PrintTo(expected_, os);
|
||||
*os << " or a value differs by more than " << tolerance_;
|
||||
}
|
||||
|
||||
private:
|
||||
const IdMap<K, double> expected_;
|
||||
const bool all_keys_;
|
||||
const double tolerance_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Matcher<VariableMap<double>> IsNearlySubsetOf(VariableMap<double> expected,
|
||||
double tolerance) {
|
||||
return Matcher<VariableMap<double>>(new IdMapMatcher<Variable>(
|
||||
std::move(expected), /*all_keys=*/false, tolerance));
|
||||
}
|
||||
|
||||
Matcher<VariableMap<double>> IsNear(VariableMap<double> expected,
|
||||
const double tolerance) {
|
||||
return Matcher<VariableMap<double>>(new IdMapMatcher<Variable>(
|
||||
std::move(expected), /*all_keys=*/true, tolerance));
|
||||
}
|
||||
|
||||
Matcher<LinearConstraintMap<double>> IsNearlySubsetOf(
|
||||
LinearConstraintMap<double> expected, double tolerance) {
|
||||
return Matcher<LinearConstraintMap<double>>(
|
||||
new IdMapMatcher<LinearConstraint>(std::move(expected),
|
||||
/*all_keys=*/false, tolerance));
|
||||
}
|
||||
|
||||
Matcher<LinearConstraintMap<double>> IsNear(
|
||||
LinearConstraintMap<double> expected, const double tolerance) {
|
||||
return Matcher<LinearConstraintMap<double>>(
|
||||
new IdMapMatcher<LinearConstraint>(std::move(expected), /*all_keys=*/true,
|
||||
tolerance));
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
Matcher<IdMap<K, double>> IsNear(IdMap<K, double> expected,
|
||||
const double tolerance) {
|
||||
return Matcher<IdMap<K, double>>(
|
||||
new IdMapMatcher<K>(std::move(expected), /*all_keys=*/true, tolerance));
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
Matcher<IdMap<K, double>> IsNearlySubsetOf(IdMap<K, double> expected,
|
||||
const double tolerance) {
|
||||
return Matcher<IdMap<K, double>>(
|
||||
new IdMapMatcher<K>(std::move(expected), /*all_keys=*/false, tolerance));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Matchers for LinearExpression and QuadraticExpression
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testing::Matcher<LinearExpression> IsIdentical(LinearExpression expected) {
|
||||
return LinearExpressionIsNear(expected, 0.0);
|
||||
}
|
||||
|
||||
testing::Matcher<LinearExpression> LinearExpressionIsNear(
|
||||
const LinearExpression expected, const double tolerance) {
|
||||
CHECK(!std::isnan(expected.offset())) << "Illegal NaN-valued offset";
|
||||
return AllOf(
|
||||
Property("storage", &LinearExpression::storage, Eq(expected.storage())),
|
||||
Property("offset", &LinearExpression::offset,
|
||||
testing::DoubleNear(expected.offset(), tolerance)),
|
||||
Property("terms", &LinearExpression::terms,
|
||||
IsNear(expected.terms(), tolerance)));
|
||||
}
|
||||
|
||||
namespace {
|
||||
testing::Matcher<BoundedLinearExpression> IsNearForSign(
|
||||
const BoundedLinearExpression& expected, const double tolerance) {
|
||||
return AllOf(Property("upper_bound_minus_offset",
|
||||
&BoundedLinearExpression::upper_bound_minus_offset,
|
||||
testing::DoubleNear(expected.upper_bound_minus_offset(),
|
||||
tolerance)),
|
||||
Property("lower_bound_minus_offset",
|
||||
&BoundedLinearExpression::lower_bound_minus_offset,
|
||||
testing::DoubleNear(expected.lower_bound_minus_offset(),
|
||||
tolerance)),
|
||||
Field("expression", &BoundedLinearExpression::expression,
|
||||
Property("terms", &LinearExpression::terms,
|
||||
IsNear(expected.expression.terms(), tolerance))));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
testing::Matcher<BoundedLinearExpression> IsNearlyEquivalent(
|
||||
const BoundedLinearExpression& expected, const double tolerance) {
|
||||
const BoundedLinearExpression expected_negation(
|
||||
-expected.expression, /*lower_bound=*/-expected.upper_bound,
|
||||
/*upper_bound=*/-expected.lower_bound);
|
||||
return AnyOf(IsNearForSign(expected, tolerance),
|
||||
IsNearForSign(expected_negation, tolerance));
|
||||
}
|
||||
|
||||
testing::Matcher<QuadraticExpression> IsIdentical(
|
||||
QuadraticExpression expected) {
|
||||
CHECK(!std::isnan(expected.offset())) << "Illegal NaN-valued offset";
|
||||
return AllOf(
|
||||
Property("storage", &QuadraticExpression::storage,
|
||||
Eq(expected.storage())),
|
||||
Property("offset", &QuadraticExpression::offset,
|
||||
testing::Eq(expected.offset())),
|
||||
Property("linear_terms", &QuadraticExpression::linear_terms,
|
||||
IsNear(expected.linear_terms(), /*tolerance=*/0)),
|
||||
Property("quadratic_terms", &QuadraticExpression::quadratic_terms,
|
||||
IsNear(expected.quadratic_terms(), /*tolerance=*/0)));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Matcher helpers
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename RayType>
|
||||
class RayMatcher : public MatcherInterface<RayType> {
|
||||
public:
|
||||
RayMatcher(RayType expected, const double tolerance)
|
||||
: expected_(std::move(expected)), tolerance_(tolerance) {}
|
||||
void DescribeTo(std::ostream* os) const final {
|
||||
*os << "after L_inf normalization, is within tolerance: " << tolerance_
|
||||
<< " of expected: ";
|
||||
PrintTo(expected_, os);
|
||||
}
|
||||
void DescribeNegationTo(std::ostream* const os) const final {
|
||||
*os << "after L_inf normalization, is not within tolerance: " << tolerance_
|
||||
<< " of expected: ";
|
||||
PrintTo(expected_, os);
|
||||
}
|
||||
|
||||
protected:
|
||||
const RayType expected_;
|
||||
const double tolerance_;
|
||||
};
|
||||
|
||||
// Alias to use the std::optional templated adaptor.
|
||||
Matcher<double> IsNear(double expected, const double tolerance) {
|
||||
return DoubleNear(expected, tolerance);
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
Matcher<std::optional<Type>> IsNear(std::optional<Type> expected,
|
||||
const double tolerance) {
|
||||
if (expected.has_value()) {
|
||||
return Optional(IsNear(*expected, tolerance));
|
||||
}
|
||||
return testing::Eq(std::nullopt);
|
||||
}
|
||||
|
||||
// Custom std::optional for basis.
|
||||
Matcher<std::optional<Basis>> BasisIs(const std::optional<Basis>& expected) {
|
||||
if (expected.has_value()) {
|
||||
return Optional(BasisIs(*expected));
|
||||
}
|
||||
return testing::Eq(std::nullopt);
|
||||
}
|
||||
|
||||
testing::Matcher<std::vector<Solution>> IsNear(
|
||||
const std::vector<Solution>& expected_solutions,
|
||||
const SolutionMatcherOptions options) {
|
||||
if (expected_solutions.empty()) {
|
||||
return IsEmpty();
|
||||
}
|
||||
std::vector<Matcher<Solution>> matchers;
|
||||
for (const Solution& sol : expected_solutions) {
|
||||
matchers.push_back(IsNear(sol, options));
|
||||
}
|
||||
return ::testing::ElementsAreArray(matchers);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Matchers for Solutions
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Matcher<PrimalSolution> IsNear(PrimalSolution expected,
|
||||
const double tolerance) {
|
||||
return AllOf(Field("variable_values", &PrimalSolution::variable_values,
|
||||
IsNear(expected.variable_values, tolerance)),
|
||||
Field("objective_value", &PrimalSolution::objective_value,
|
||||
IsNear(expected.objective_value, tolerance)),
|
||||
Field("feasibility_status", &PrimalSolution::feasibility_status,
|
||||
expected.feasibility_status));
|
||||
}
|
||||
|
||||
Matcher<DualSolution> IsNear(DualSolution expected, const double tolerance) {
|
||||
return AllOf(Field("dual_values", &DualSolution::dual_values,
|
||||
IsNear(expected.dual_values, tolerance)),
|
||||
Field("reduced_costs", &DualSolution::reduced_costs,
|
||||
IsNear(expected.reduced_costs, tolerance)),
|
||||
Field("objective_value", &DualSolution::objective_value,
|
||||
IsNear(expected.objective_value, tolerance)),
|
||||
Field("feasibility_status", &DualSolution::feasibility_status,
|
||||
expected.feasibility_status));
|
||||
}
|
||||
|
||||
Matcher<Basis> BasisIs(const Basis& expected) {
|
||||
return AllOf(Field("variable_status", &Basis::variable_status,
|
||||
expected.variable_status),
|
||||
Field("constraint_status", &Basis::constraint_status,
|
||||
expected.constraint_status),
|
||||
Field("basic_dual_feasibility", &Basis::basic_dual_feasibility,
|
||||
expected.basic_dual_feasibility));
|
||||
}
|
||||
|
||||
Matcher<Solution> IsNear(Solution expected,
|
||||
const SolutionMatcherOptions options) {
|
||||
std::vector<Matcher<Solution>> to_check;
|
||||
if (options.check_primal) {
|
||||
to_check.push_back(
|
||||
Field("primal_solution", &Solution::primal_solution,
|
||||
IsNear(expected.primal_solution, options.tolerance)));
|
||||
}
|
||||
if (options.check_dual) {
|
||||
to_check.push_back(
|
||||
Field("dual_solution", &Solution::dual_solution,
|
||||
IsNear(expected.dual_solution, options.tolerance)));
|
||||
}
|
||||
if (options.check_basis) {
|
||||
to_check.push_back(
|
||||
Field("basis", &Solution::basis, BasisIs(expected.basis)));
|
||||
}
|
||||
return AllOfArray(to_check);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Primal Ray Matcher
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename K>
|
||||
double InfinityNorm(const IdMap<K, double>& vector) {
|
||||
double infinity_norm = 0.0;
|
||||
for (auto [id, value] : vector) {
|
||||
infinity_norm = std::max(infinity_norm, std::abs(value));
|
||||
}
|
||||
return infinity_norm;
|
||||
}
|
||||
|
||||
// Returns a normalized primal ray.
|
||||
//
|
||||
// The normalization is done using infinity norm:
|
||||
//
|
||||
// ray / ||ray||_inf
|
||||
//
|
||||
// If the input ray norm is zero, the ray is returned unchanged.
|
||||
PrimalRay NormalizePrimalRay(PrimalRay ray) {
|
||||
const double norm = InfinityNorm(ray.variable_values);
|
||||
if (norm != 0.0) {
|
||||
for (auto entry : ray.variable_values) {
|
||||
entry.second /= norm;
|
||||
}
|
||||
}
|
||||
return ray;
|
||||
}
|
||||
|
||||
class PrimalRayMatcher : public RayMatcher<PrimalRay> {
|
||||
public:
|
||||
PrimalRayMatcher(PrimalRay expected, const double tolerance)
|
||||
: RayMatcher(std::move(expected), tolerance) {}
|
||||
|
||||
bool MatchAndExplain(PrimalRay actual,
|
||||
MatchResultListener* const os) const override {
|
||||
auto normalized_actual = NormalizePrimalRay(actual);
|
||||
auto normalized_expected = NormalizePrimalRay(expected_);
|
||||
if (os->IsInterested()) {
|
||||
*os << "actual normalized: " << PrintToString(normalized_actual)
|
||||
<< ", expected normalized: " << PrintToString(normalized_expected);
|
||||
}
|
||||
return ExplainMatchResult(
|
||||
IsNear(normalized_expected.variable_values, tolerance_),
|
||||
normalized_actual.variable_values, os);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Matcher<PrimalRay> IsNear(PrimalRay expected, const double tolerance) {
|
||||
return Matcher<PrimalRay>(
|
||||
new PrimalRayMatcher(std::move(expected), tolerance));
|
||||
}
|
||||
|
||||
Matcher<PrimalRay> PrimalRayIsNear(VariableMap<double> expected_var_values,
|
||||
const double tolerance) {
|
||||
PrimalRay expected;
|
||||
expected.variable_values = std::move(expected_var_values);
|
||||
return IsNear(expected, tolerance);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Dual Ray Matcher
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns a normalized dual ray.
|
||||
//
|
||||
// The normalization is done using infinity norm:
|
||||
//
|
||||
// ray / ||ray||_inf
|
||||
//
|
||||
// If the input ray norm is zero, the ray is returned unchanged.
|
||||
DualRay NormalizeDualRay(DualRay ray) {
|
||||
const double norm =
|
||||
std::max(InfinityNorm(ray.dual_values), InfinityNorm(ray.reduced_costs));
|
||||
if (norm != 0.0) {
|
||||
for (auto entry : ray.dual_values) {
|
||||
entry.second /= norm;
|
||||
}
|
||||
for (auto entry : ray.reduced_costs) {
|
||||
entry.second /= norm;
|
||||
}
|
||||
}
|
||||
return ray;
|
||||
}
|
||||
|
||||
class DualRayMatcher : public RayMatcher<DualRay> {
|
||||
public:
|
||||
DualRayMatcher(DualRay expected, const double tolerance)
|
||||
: RayMatcher(std::move(expected), tolerance) {}
|
||||
|
||||
bool MatchAndExplain(DualRay actual, MatchResultListener* os) const override {
|
||||
auto normalized_actual = NormalizeDualRay(actual);
|
||||
auto normalized_expected = NormalizeDualRay(expected_);
|
||||
if (os->IsInterested()) {
|
||||
*os << "actual normalized: " << PrintToString(normalized_actual)
|
||||
<< ", expected normalized: " << PrintToString(normalized_expected);
|
||||
}
|
||||
return ExplainMatchResult(
|
||||
IsNear(normalized_expected.dual_values, tolerance_),
|
||||
normalized_actual.dual_values, os) &&
|
||||
ExplainMatchResult(
|
||||
IsNear(normalized_expected.reduced_costs, tolerance_),
|
||||
normalized_actual.reduced_costs, os);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Matcher<DualRay> IsNear(DualRay expected, const double tolerance) {
|
||||
return Matcher<DualRay>(new DualRayMatcher(std::move(expected), tolerance));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// SolveResult termination reason matchers
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Matcher<SolveResult> TerminatesWithOneOf(
|
||||
const std::vector<TerminationReason>& allowed) {
|
||||
return Field("termination", &SolveResult::termination,
|
||||
Field("reason", &Termination::reason, AnyOfArray(allowed)));
|
||||
}
|
||||
|
||||
Matcher<SolveResult> TerminatesWith(const TerminationReason expected) {
|
||||
return Field("termination", &SolveResult::termination,
|
||||
Field("reason", &Termination::reason, expected));
|
||||
}
|
||||
|
||||
namespace {
|
||||
testing::Matcher<SolveResult> LimitIs(const Limit expected,
|
||||
const bool allow_limit_undetermined) {
|
||||
if (allow_limit_undetermined) {
|
||||
return Field("termination", &SolveResult::termination,
|
||||
Field("limit", &Termination::limit,
|
||||
AnyOf(Limit::kUndetermined, expected)));
|
||||
}
|
||||
return Field("termination", &SolveResult::termination,
|
||||
Field("limit", &Termination::limit, expected));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
testing::Matcher<SolveResult> TerminatesWithLimit(
|
||||
const Limit expected, const bool allow_limit_undetermined) {
|
||||
std::vector<Matcher<SolveResult>> matchers;
|
||||
matchers.push_back(LimitIs(expected, allow_limit_undetermined));
|
||||
matchers.push_back(TerminatesWithOneOf(
|
||||
{TerminationReason::kFeasible, TerminationReason::kNoSolutionFound}));
|
||||
return ::testing::AllOfArray(matchers);
|
||||
}
|
||||
|
||||
testing::Matcher<SolveResult> TerminatesWithReasonFeasible(
|
||||
const Limit expected, const bool allow_limit_undetermined) {
|
||||
std::vector<Matcher<SolveResult>> matchers;
|
||||
matchers.push_back(LimitIs(expected, allow_limit_undetermined));
|
||||
matchers.push_back(TerminatesWith(TerminationReason::kFeasible));
|
||||
return ::testing::AllOfArray(matchers);
|
||||
}
|
||||
|
||||
testing::Matcher<SolveResult> TerminatesWithReasonNoSolutionFound(
|
||||
const Limit expected, const bool allow_limit_undetermined) {
|
||||
std::vector<Matcher<SolveResult>> matchers;
|
||||
matchers.push_back(LimitIs(expected, allow_limit_undetermined));
|
||||
matchers.push_back(TerminatesWith(TerminationReason::kNoSolutionFound));
|
||||
return ::testing::AllOfArray(matchers);
|
||||
}
|
||||
|
||||
template <typename MatcherType>
|
||||
std::string MatcherToStringImpl(const MatcherType& matcher, const bool negate) {
|
||||
std::ostringstream os;
|
||||
if (negate) {
|
||||
matcher.DescribeNegationTo(&os);
|
||||
} else {
|
||||
matcher.DescribeTo(&os);
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string MatcherToString(const Matcher<T>& matcher, bool negate) {
|
||||
return MatcherToStringImpl(matcher, negate);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
// Polymorphic matchers do not always define DescribeTo,
|
||||
// The <T> type may not be a matcher, but it will implement DescribeTo.
|
||||
// clang-format on
|
||||
template <typename T>
|
||||
std::string MatcherToString(const ::testing::PolymorphicMatcher<T>& matcher,
|
||||
bool negate) {
|
||||
return MatcherToStringImpl(matcher.impl(), negate);
|
||||
}
|
||||
|
||||
MATCHER_P(FirstElementIs, first_element_matcher,
|
||||
(negation
|
||||
? absl::StrCat("is empty or first element ",
|
||||
MatcherToString(first_element_matcher, true))
|
||||
: absl::StrCat("has at least one element and first element ",
|
||||
MatcherToString(first_element_matcher, false)))) {
|
||||
return ExplainMatchResult(UnorderedElementsAre(first_element_matcher),
|
||||
absl::MakeSpan(arg).subspan(0, 1), result_listener);
|
||||
}
|
||||
|
||||
Matcher<Termination> ReasonIs(TerminationReason reason) {
|
||||
return Field("reason", &Termination::reason, reason);
|
||||
}
|
||||
|
||||
Matcher<Termination> ReasonIsOptimal() {
|
||||
return ReasonIs(TerminationReason::kOptimal);
|
||||
}
|
||||
|
||||
Matcher<SolveResult> IsOptimal(const std::optional<double> expected_objective,
|
||||
const double tolerance) {
|
||||
std::vector<Matcher<SolveResult>> matchers;
|
||||
matchers.push_back(
|
||||
Field("termination", &SolveResult::termination, ReasonIsOptimal()));
|
||||
if (expected_objective.has_value()) {
|
||||
matchers.push_back(Field(
|
||||
"solutions", &SolveResult::solutions,
|
||||
FirstElementIs(Field(
|
||||
"primal_solution", &Solution::primal_solution,
|
||||
Optional(Field("objective_value", &PrimalSolution::objective_value,
|
||||
IsNear(*expected_objective, tolerance)))))));
|
||||
}
|
||||
return ::testing::AllOfArray(matchers);
|
||||
}
|
||||
|
||||
Matcher<SolveResult> IsOptimalWithSolution(
|
||||
const double expected_objective,
|
||||
const VariableMap<double> expected_variable_values,
|
||||
const double tolerance) {
|
||||
return AllOf(
|
||||
IsOptimal(std::make_optional(expected_objective), tolerance),
|
||||
HasSolution(
|
||||
PrimalSolution{.variable_values = expected_variable_values,
|
||||
.objective_value = expected_objective,
|
||||
.feasibility_status = SolutionStatus::kFeasible},
|
||||
tolerance));
|
||||
}
|
||||
|
||||
Matcher<SolveResult> IsOptimalWithDualSolution(
|
||||
const double expected_objective,
|
||||
const LinearConstraintMap<double> expected_dual_values,
|
||||
const VariableMap<double> expected_reduced_costs, const double tolerance) {
|
||||
return AllOf(
|
||||
IsOptimal(std::make_optional(expected_objective), tolerance),
|
||||
HasDualSolution(
|
||||
DualSolution{
|
||||
.dual_values = expected_dual_values,
|
||||
.reduced_costs = expected_reduced_costs,
|
||||
.objective_value = std::make_optional(expected_objective),
|
||||
.feasibility_status = SolutionStatus::kFeasible},
|
||||
tolerance));
|
||||
}
|
||||
|
||||
Matcher<SolveResult> HasSolution(PrimalSolution expected,
|
||||
const double tolerance) {
|
||||
return ::testing::Field(
|
||||
"solutions", &SolveResult::solutions,
|
||||
Contains(Field("primal_solution", &Solution::primal_solution,
|
||||
Optional(IsNear(std::move(expected), tolerance)))));
|
||||
}
|
||||
|
||||
Matcher<SolveResult> HasDualSolution(DualSolution expected,
|
||||
const double tolerance) {
|
||||
return ::testing::Field(
|
||||
"solutions", &SolveResult::solutions,
|
||||
Contains(Field("dual_solution", &Solution::dual_solution,
|
||||
Optional(IsNear(std::move(expected), tolerance)))));
|
||||
}
|
||||
|
||||
Matcher<SolveResult> HasPrimalRay(PrimalRay expected, const double tolerance) {
|
||||
return ::testing::Field("primal_rays", &SolveResult::primal_rays,
|
||||
Contains(IsNear(std::move(expected), tolerance)));
|
||||
}
|
||||
|
||||
Matcher<SolveResult> HasPrimalRay(VariableMap<double> expected_vars,
|
||||
const double tolerance) {
|
||||
PrimalRay ray;
|
||||
ray.variable_values = std::move(expected_vars);
|
||||
return HasPrimalRay(std::move(ray), tolerance);
|
||||
}
|
||||
|
||||
Matcher<SolveResult> HasDualRay(DualRay expected, const double tolerance) {
|
||||
return ::testing::Field("dual_rays", &SolveResult::dual_rays,
|
||||
Contains(IsNear(std::move(expected), tolerance)));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool MightTerminateWithRays(const TerminationReason reason) {
|
||||
switch (reason) {
|
||||
case TerminationReason::kInfeasibleOrUnbounded:
|
||||
case TerminationReason::kUnbounded:
|
||||
case TerminationReason::kInfeasible:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TerminationReason> CompatibleReasons(
|
||||
const TerminationReason expected, const bool inf_or_unb_soft_match) {
|
||||
if (!inf_or_unb_soft_match) {
|
||||
return {expected};
|
||||
}
|
||||
switch (expected) {
|
||||
case TerminationReason::kUnbounded:
|
||||
return {TerminationReason::kUnbounded,
|
||||
TerminationReason::kInfeasibleOrUnbounded};
|
||||
case TerminationReason::kInfeasible:
|
||||
return {TerminationReason::kInfeasible,
|
||||
TerminationReason::kInfeasibleOrUnbounded};
|
||||
case TerminationReason::kInfeasibleOrUnbounded:
|
||||
return {TerminationReason::kUnbounded, TerminationReason::kInfeasible,
|
||||
TerminationReason::kInfeasibleOrUnbounded};
|
||||
default:
|
||||
return {expected};
|
||||
}
|
||||
}
|
||||
|
||||
Matcher<std::vector<Solution>> CheckSolutions(
|
||||
const std::vector<Solution>& expected_solutions,
|
||||
const SolveResultMatcherOptions& options) {
|
||||
if (options.first_solution_only && !expected_solutions.empty()) {
|
||||
return FirstElementIs(
|
||||
IsNear(expected_solutions[0],
|
||||
SolutionMatcherOptions{.tolerance = options.tolerance,
|
||||
.check_primal = true,
|
||||
.check_dual = options.check_dual,
|
||||
.check_basis = options.check_basis}));
|
||||
}
|
||||
return IsNear(expected_solutions,
|
||||
SolutionMatcherOptions{.tolerance = options.tolerance,
|
||||
.check_primal = true,
|
||||
.check_dual = options.check_dual,
|
||||
.check_basis = options.check_basis});
|
||||
}
|
||||
|
||||
template <typename RayType>
|
||||
Matcher<std::vector<RayType>> AnyRayNear(
|
||||
const std::vector<RayType>& expected_rays, const double tolerance) {
|
||||
std::vector<Matcher<RayType>> matchers;
|
||||
for (const RayType& ray : expected_rays) {
|
||||
matchers.push_back(IsNear(ray, tolerance));
|
||||
}
|
||||
return ::testing::Contains(::testing::AnyOfArray(matchers));
|
||||
}
|
||||
|
||||
template <typename RayType>
|
||||
Matcher<std::vector<RayType>> AllRaysNear(
|
||||
const std::vector<RayType>& expected_rays, const double tolerance) {
|
||||
std::vector<Matcher<RayType>> matchers;
|
||||
for (const RayType& ray : expected_rays) {
|
||||
matchers.push_back(IsNear(ray, tolerance));
|
||||
}
|
||||
return ::testing::UnorderedElementsAreArray(matchers);
|
||||
}
|
||||
|
||||
template <typename RayType>
|
||||
Matcher<std::vector<RayType>> CheckRays(
|
||||
const std::vector<RayType>& expected_rays, const double tolerance,
|
||||
bool check_all) {
|
||||
if (expected_rays.empty()) {
|
||||
return ::testing::IsEmpty();
|
||||
}
|
||||
if (check_all) {
|
||||
return AllRaysNear(expected_rays, tolerance);
|
||||
}
|
||||
return AnyRayNear(expected_rays, tolerance);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Matcher<SolveResult> IsConsistentWith(
|
||||
const SolveResult& expected, const SolveResultMatcherOptions& options) {
|
||||
std::vector<Matcher<SolveResult>> to_check;
|
||||
to_check.push_back(TerminatesWithOneOf(CompatibleReasons(
|
||||
expected.termination.reason, options.inf_or_unb_soft_match)));
|
||||
const bool skip_solution =
|
||||
MightTerminateWithRays(expected.termination.reason) &&
|
||||
!options.check_solutions_if_inf_or_unbounded;
|
||||
if (!skip_solution) {
|
||||
to_check.push_back(Field("solutions", &SolveResult::solutions,
|
||||
CheckSolutions(expected.solutions, options)));
|
||||
}
|
||||
if (options.check_rays) {
|
||||
to_check.push_back(Field("primal_rays", &SolveResult::primal_rays,
|
||||
CheckRays(expected.primal_rays, options.tolerance,
|
||||
!options.first_solution_only)));
|
||||
to_check.push_back(Field("dual_rays", &SolveResult::dual_rays,
|
||||
CheckRays(expected.dual_rays, options.tolerance,
|
||||
!options.first_solution_only)));
|
||||
}
|
||||
|
||||
return AllOfArray(to_check);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Rarely used
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Matcher<UpdateResult> DidUpdate() {
|
||||
return ::testing::Field("did_update", &UpdateResult::did_update,
|
||||
::testing::IsTrue());
|
||||
}
|
||||
|
||||
} // namespace math_opt
|
||||
} // namespace operations_research
|
||||
@@ -1,471 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// Matchers for MathOpt types, specifically SolveResult and nested fields.
|
||||
//
|
||||
// The matchers defined here are useful for writing unit tests checking that the
|
||||
// result of Solve(), absl::StatusOr<SolveResult>, meets expectations. We give
|
||||
// some examples below. All code is assumed with the following setup:
|
||||
//
|
||||
// namespace operations_research::math_opt {
|
||||
// using ::testing::status::IsOkAndHolds;
|
||||
//
|
||||
// Model model;
|
||||
// const Variable x = model.AddContinuousVariable(0.0, 1.0);
|
||||
// const Variable y = model.AddContinuousVariable(0.0, 1.0);
|
||||
// const LinearConstraint c = model.AddLinearConstraint(x + y <= 1);
|
||||
// model.Maximize(2*x + y);
|
||||
//
|
||||
// Example 1.a: result is OK, optimal, and objective value approximately 42.
|
||||
// EXPECT_THAT(Solve(model, SOLVER_TYPE_GLOP), IsOkAndHolds(IsOptimal(42)));
|
||||
//
|
||||
// Example 1.b: equivalent to 1.a.
|
||||
// ASSERT_OK_AND_ASSIGN(const SolveResult result,
|
||||
// Solve(model, SOLVER_TYPE_GLOP));
|
||||
// EXPECT_THAT(result, IsOptimal(42));
|
||||
//
|
||||
// Example 2: result is OK, optimal, and best solution is x=1, y=0.
|
||||
// ASSERT_OK_AND_ASSIGN(const SolveResult result,
|
||||
// Solve(model, SOLVER_TYPE_GLOP));
|
||||
// ASSERT_THAT(result, IsOptimal());
|
||||
// EXPECT_THAT(result.variable_value(), IsNear({{x, 1}, {y, 0}});
|
||||
// Note: the second ASSERT ensures that if the solution is not optimal, then
|
||||
// result.variable_value() will not run (the function will crash if the solver
|
||||
// didn't find a solution). Further, MathOpt guarantees there is a solution
|
||||
// when the termination reason is optimal.
|
||||
//
|
||||
// Example 3: result is OK, check the solution without specifying termination.
|
||||
// ASSERT_OK_AND_ASSIGN(const SolveResult result,
|
||||
// Solve(model, SOLVER_TYPE_GLOP));
|
||||
// EXPECT_THAT(result, HasBestSolution({{x, 1}, {y, 0}}));
|
||||
//
|
||||
// Example 4: multiple possible termination reason, primal ray optional:
|
||||
// ASSERT_OK_AND_ASSIGN(const SolveResult result,
|
||||
// Solve(model, SOLVER_TYPE_GLOP));
|
||||
// ASSERT_THAT(result, TerminatesWithOneOf(
|
||||
// TerminationReason::kUnbounded,
|
||||
// TerminationReason::kInfeasibleOrUnbounded));
|
||||
// if(!result.primal_rays.empty()) {
|
||||
// EXPECT_THAT(result.primal_rays[0], PrimalRayIsNear({{x, 1,}, {y, 0}}));
|
||||
// }
|
||||
//
|
||||
//
|
||||
// Tips on writing good tests:
|
||||
// * Use ASSERT_OK_AND_ASSIGN(const SolveResult result, Solve(...)) to ensure
|
||||
// the test terminates immediately if Solve() does not return OK.
|
||||
// * If you ASSERT_THAT(result, IsOptimal()), you can assume that you have a
|
||||
// feasible primal solution afterwards. Otherwise, make no assumptions on
|
||||
// the contents of result (e.g. do not assume result contains a primal ray
|
||||
// just because the termination reason was UNBOUNDED).
|
||||
// * For problems that are infeasible, the termination reasons INFEASIBLE and
|
||||
// DUAL_INFEASIBLE are both possible. Likewise, for unbounded problems, you
|
||||
// can get both UNBOUNDED and DUAL_INFEASIBLE. See TerminatesWithOneOf()
|
||||
// below to make assertions in this case. Note also that some solvers have
|
||||
// solver specific parameters to ensure that DUAL_INFEASIBLE will not be
|
||||
// returned (e.g. for Gurobi, use DualReductions or InfUnbdInfo).
|
||||
// * The objective value and variable values should always be compared up to
|
||||
// a tolerance, even if your decision variables are integer. The matchers
|
||||
// defined have a configurable tolerance with default value 1e-5.
|
||||
// * Primal and dual rays are unique only up to a constant scaling. The
|
||||
// matchers provided rescale both expected and actual before comparing.
|
||||
// * Take care on problems with multiple optimal solutions. Do not rely on a
|
||||
// particular solution being returned in your test, as the test will break
|
||||
// when we upgrade the solver.
|
||||
//
|
||||
// This file also defines functions to let gunit print various MathOpt types.
|
||||
//
|
||||
// To see the error messages these matchers generate, run
|
||||
// blaze test experimental/users/rander/math_opt:matchers_error_messages
|
||||
// which is a fork of matchers_test.cc where the assertions are all negated
|
||||
// (note that every test should fail).
|
||||
#ifndef OR_TOOLS_MATH_OPT_CPP_MATCHERS_H_
|
||||
#define OR_TOOLS_MATH_OPT_CPP_MATCHERS_H_
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/math_opt/cpp/linear_constraint.h"
|
||||
#include "ortools/math_opt/cpp/math_opt.h"
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
|
||||
constexpr double kMatcherDefaultTolerance = 1e-5;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Matchers for IdMap
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Checks that the maps have identical keys and values within tolerance. This
|
||||
// factory will CHECK-fail if expected contains any NaN values.
|
||||
testing::Matcher<VariableMap<double>> IsNear(
|
||||
VariableMap<double> expected, double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Checks that the keys of actual are a subset of the keys of expected, and that
|
||||
// for all shared keys, the values are within tolerance. This factory will
|
||||
// CHECK-fail if expected contains any NaN values, and any NaN values in the
|
||||
// expression compared against will result in the matcher failing.
|
||||
testing::Matcher<VariableMap<double>> IsNearlySubsetOf(
|
||||
VariableMap<double> expected, double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Checks that the maps have identical keys and values within tolerance. This
|
||||
// factory will CHECK-fail if expected contains any NaN values, and any NaN
|
||||
// values in the expression compared against will result in the matcher failing.
|
||||
testing::Matcher<LinearConstraintMap<double>> IsNear(
|
||||
LinearConstraintMap<double> expected,
|
||||
double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Checks that the keys of actual are a subset of the keys of expected, and that
|
||||
// for all shared keys, the values are within tolerance. This factory will
|
||||
// CHECK-fail if expected contains any NaN values, and any NaN values in the
|
||||
// expression compared against will result in the matcher failing.
|
||||
testing::Matcher<LinearConstraintMap<double>> IsNearlySubsetOf(
|
||||
LinearConstraintMap<double> expected,
|
||||
double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Checks that the maps have identical keys and values within tolerance. Works
|
||||
// for VariableMap, LinearConstraintMap, among other realizations of IdMap. This
|
||||
// factory will CHECK-fail if expected contains any NaN values, and any NaN
|
||||
// values in the expression compared against will result in the matcher failing.
|
||||
template <typename K>
|
||||
testing::Matcher<IdMap<K, double>> IsNear(
|
||||
IdMap<K, double> expected,
|
||||
const double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Checks that the keys of actual are a subset of the keys of expected, and that
|
||||
// for all shared keys, the values are within tolerance. Works for VariableMap,
|
||||
// LinearConstraintMap, among other realizations of IdMap. This factory will
|
||||
// CHECK-fail if expected contains any NaN values, and any NaN values in the
|
||||
// expression compared against will result in the matcher failing.
|
||||
template <typename K>
|
||||
testing::Matcher<IdMap<K, double>> IsNearlySubsetOf(
|
||||
IdMap<K, double> expected,
|
||||
const double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Matchers for various Variable expressions (e.g. LinearExpression)
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Checks that the expressions are structurally identical (i.e., internal maps
|
||||
// have the same keys and storage, coefficients are exactly equal). This factory
|
||||
// will CHECK-fail if expected contains any NaN values, and any NaN values in
|
||||
// the expression compared against will result in the matcher failing.
|
||||
testing::Matcher<LinearExpression> IsIdentical(LinearExpression expected);
|
||||
|
||||
testing::Matcher<LinearExpression> LinearExpressionIsNear(
|
||||
LinearExpression expected, double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Checks that the bounded linear expression is equivalent to expected, where
|
||||
// equivalence is maintained by:
|
||||
// * adding alpha to the lower bound, the linear expression and upper bound
|
||||
// * multiplying the lower bound, linear expression, by -1 (and flipping the
|
||||
// inequalities).
|
||||
// Note that, as implemented, we do not allow for arbitrary multiplicative
|
||||
// rescalings (this makes additive tolerance complicated).
|
||||
testing::Matcher<BoundedLinearExpression> IsNearlyEquivalent(
|
||||
const BoundedLinearExpression& expected,
|
||||
double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Checks that the expressions are structurally identical (i.e., internal maps
|
||||
// have the same keys and storage, coefficients are exactly equal). This factory
|
||||
// will CHECK-fail if expected contains any NaN values, and any NaN values in
|
||||
// the expression compared against will result in the matcher failing.
|
||||
testing::Matcher<QuadraticExpression> IsIdentical(QuadraticExpression expected);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Matchers for solutions
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Options for IsNear(Solution).
|
||||
struct SolutionMatcherOptions {
|
||||
double tolerance = kMatcherDefaultTolerance;
|
||||
bool check_primal = true;
|
||||
bool check_dual = true;
|
||||
bool check_basis = true;
|
||||
};
|
||||
|
||||
testing::Matcher<Solution> IsNear(Solution expected,
|
||||
SolutionMatcherOptions options = {});
|
||||
|
||||
// Checks variables match and variable/objective values are within tolerance and
|
||||
// feasibility statuses are identical.
|
||||
testing::Matcher<PrimalSolution> IsNear(
|
||||
PrimalSolution expected, double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Checks dual variables, reduced costs and objective are within tolerance and
|
||||
// feasibility statuses are identical.
|
||||
testing::Matcher<DualSolution> IsNear(
|
||||
DualSolution expected, double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
testing::Matcher<Basis> BasisIs(const Basis& expected);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Matchers for a Rays
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Checks variables match and that after rescaling, variable values are within
|
||||
// tolerance.
|
||||
testing::Matcher<PrimalRay> IsNear(PrimalRay expected,
|
||||
double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Checks variables match and that after rescaling, variable values are within
|
||||
// tolerance.
|
||||
testing::Matcher<PrimalRay> PrimalRayIsNear(
|
||||
VariableMap<double> expected_var_values,
|
||||
double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Checks that dual variables and reduced costs are defined for the same
|
||||
// set of Variables/LinearConstraints, and that their rescaled values are within
|
||||
// tolerance.
|
||||
testing::Matcher<DualRay> IsNear(DualRay expected,
|
||||
double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Matchers for a Termination
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testing::Matcher<Termination> ReasonIs(TerminationReason reason);
|
||||
|
||||
testing::Matcher<Termination> ReasonIsOptimal();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Matchers for a SolveResult
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Checks the following:
|
||||
// * The termination reason is optimal.
|
||||
// * If expected_objective contains a value, there is at least one feasible
|
||||
// solution and that solution has an objective value within tolerance of
|
||||
// expected_objective.
|
||||
testing::Matcher<SolveResult> IsOptimal(
|
||||
std::optional<double> expected_objective = std::nullopt,
|
||||
double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
testing::Matcher<SolveResult> IsOptimalWithSolution(
|
||||
double expected_objective, VariableMap<double> expected_variable_values,
|
||||
double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
testing::Matcher<SolveResult> IsOptimalWithDualSolution(
|
||||
double expected_objective, LinearConstraintMap<double> expected_dual_values,
|
||||
VariableMap<double> expected_reduced_costs,
|
||||
double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Checks the following:
|
||||
// * The result has the expected termination reason.
|
||||
testing::Matcher<SolveResult> TerminatesWith(TerminationReason expected);
|
||||
|
||||
// Checks that the result has one of the allowed termination reasons.
|
||||
testing::Matcher<SolveResult> TerminatesWithOneOf(
|
||||
const std::vector<TerminationReason>& allowed);
|
||||
|
||||
// Checks the following:
|
||||
// * The result has termination reason kFeasible or kNoSolutionFound.
|
||||
// * The limit is expected, or is kUndetermined if allow_limit_undetermined.
|
||||
testing::Matcher<SolveResult> TerminatesWithLimit(
|
||||
Limit expected, bool allow_limit_undetermined = false);
|
||||
|
||||
// Checks the following:
|
||||
// * The result has termination reason kFeasible.
|
||||
// * The limit is expected, or is kUndetermined if allow_limit_undetermined.
|
||||
testing::Matcher<SolveResult> TerminatesWithReasonFeasible(
|
||||
Limit expected, bool allow_limit_undetermined = false);
|
||||
|
||||
// Checks the following:
|
||||
// * The result has termination reason kNoSolutionFound.
|
||||
// * The limit is expected, or is kUndetermined if allow_limit_undetermined.
|
||||
testing::Matcher<SolveResult> TerminatesWithReasonNoSolutionFound(
|
||||
Limit expected, bool allow_limit_undetermined = false);
|
||||
|
||||
// SolveResult has a primal solution matching expected within tolerance.
|
||||
testing::Matcher<SolveResult> HasSolution(
|
||||
PrimalSolution expected, double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// SolveResult has a dual solution matching expected within
|
||||
// tolerance.
|
||||
testing::Matcher<SolveResult> HasDualSolution(
|
||||
DualSolution expected, double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Actual SolveResult contains a primal ray that matches expected within
|
||||
// tolerance.
|
||||
testing::Matcher<SolveResult> HasPrimalRay(
|
||||
PrimalRay expected, double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Actual SolveResult contains a primal ray with variable values equivalent to
|
||||
// (under L_inf scaling) expected_vars up to tolerance.
|
||||
testing::Matcher<SolveResult> HasPrimalRay(
|
||||
VariableMap<double> expected_vars,
|
||||
double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Actual SolveResult contains a dual ray that matches expected within
|
||||
// tolerance.
|
||||
testing::Matcher<SolveResult> HasDualRay(
|
||||
DualRay expected, double tolerance = kMatcherDefaultTolerance);
|
||||
|
||||
// Configures SolveResult matcher IsConsistentWith() below.
|
||||
struct SolveResultMatcherOptions {
|
||||
double tolerance = 1e-5;
|
||||
bool first_solution_only = true;
|
||||
bool check_dual = true;
|
||||
bool check_rays = true;
|
||||
|
||||
// If the expected result has termination reason kInfeasible, kUnbounded, or
|
||||
// kDualInfeasasible, the primal solution, dual solution, and basis are
|
||||
// ignored unless check_solutions_if_inf_or_unbounded is true.
|
||||
//
|
||||
// TODO(b/201099290): this is perhaps not a good default. Gurobi as
|
||||
// implemented is returning primal solutions for both unbounded and
|
||||
// infeasible problems. We need to add unit tests that inspect this value
|
||||
// and turn them on one solver at a time with a new parameter on
|
||||
// SimpleLpTestParameters.
|
||||
bool check_solutions_if_inf_or_unbounded = false;
|
||||
bool check_basis = false;
|
||||
|
||||
// In linear programming, the following outcomes are all possible
|
||||
//
|
||||
// Primal LP | Dual LP | Possible MathOpt Termination Reasons
|
||||
// -----------------------------------------------------------------
|
||||
// 1. Infeasible | Unbounded | kInfeasible
|
||||
// 2. Optimal | Optimal | kOptimal
|
||||
// 3. Unbounded | Infeasible | kUnbounded, kInfeasibleOrUnbounded
|
||||
// 4. Infeasible | Infeasible | kInfeasible, kInfeasibleOrUnbounded
|
||||
//
|
||||
// (Above "Optimal" means that an optimal solution exists. This is a statement
|
||||
// about the existence of optimal solutions and certificates of
|
||||
// infeasibility/unboundedness, not about the outcome of applying any
|
||||
// particular algorithm.)
|
||||
//
|
||||
// When writing your unit test, you can typically tell which case of 1-4 you
|
||||
// are in, but in cases 3-4 you do not know which termination reason will be
|
||||
// returned. In some situations, it may not be clear if you are in case 1 or
|
||||
// case 4 as well.
|
||||
//
|
||||
// When inf_or_unb_soft_match=false, the matcher must exactly specify the
|
||||
// status returned by the solver. For cases 3-4, this is implementation
|
||||
// dependent and we do not recommend this. When
|
||||
// inf_or_unb_soft_match=true:
|
||||
// * kInfeasible can also match kInfeasibleOrUnbounded
|
||||
// * kUnbounded can also match kInfeasibleOrUnbounded
|
||||
// * kInfeasibleOrUnbounded can also match kInfeasible and kUnbounded.
|
||||
// For case 2, inf_or_unb_soft_match has no effect.
|
||||
//
|
||||
// To build the strongest possible matcher (accepting the minimal set of
|
||||
// termination reasons):
|
||||
// * If you know you are in case 1, se inf_or_unb_soft_match=false
|
||||
// (soft_match=true over-matches)
|
||||
// * For case 3, use inf_or_unb_soft_match=false and
|
||||
// termination_reason=kUnbounded (kInfeasibleOrUnbounded over-matches).
|
||||
// * For case 4 (or if you are unsure of case 1 vs case 4), use
|
||||
// inf_or_unb_soft_match=true and
|
||||
// termination_reason=kInfeasible (kInfeasibleOrUnbounded over-matches).
|
||||
// * If you cannot tell if you are in case 3 or case 4, use
|
||||
// inf_or_unb_soft_match=true and termination reason
|
||||
// kInfeasibleOrUnbounded.
|
||||
//
|
||||
// If the above is too complicated, always setting
|
||||
// inf_or_unb_soft_match=true and using any of the expected MathOpt
|
||||
// termination reasons from the above table will give a matcher that is
|
||||
// slightly too lenient.
|
||||
bool inf_or_unb_soft_match = true;
|
||||
};
|
||||
|
||||
// Tests that two SolveResults are equivalent. Basic use:
|
||||
//
|
||||
// SolveResult expected;
|
||||
// // Fill in expected...
|
||||
// ASSERT_OK_AND_ASSIGN(SolveResult actual, Solve(model, solver_type));
|
||||
// EXPECT_THAT(actual, IsConsistentWith(expected));
|
||||
//
|
||||
// Equivalence is defined as follows:
|
||||
// * The termination reasons are the same.
|
||||
// - For infeasible and unbounded problems, see
|
||||
// options.inf_or_unb_soft_match.
|
||||
// * The solve stats are ignored.
|
||||
// * For both primal and dual solutions, either expected and actual are
|
||||
// both empty, or their first entries satisfy IsNear() at options.tolerance.
|
||||
// - Not checked if options.check_solutions_if_inf_or_unbounded and the
|
||||
// problem is infeasible or unbounded (default).
|
||||
// - If options.first_solution_only is false, check the entire list of
|
||||
// solutions matches in the same order.
|
||||
// - Dual solution is not checked if options.check_dual=false
|
||||
// * For both the primal and dual rays, either expected and actual are both
|
||||
// empty, or any ray in expected IsNear() any ray in actual (which is up
|
||||
// to a rescaling) at options.tolerance.
|
||||
// - Not checked if options.check_rays=false
|
||||
// - If options.first_solution_only is false, check the entire list of
|
||||
// solutions matches in the same order.
|
||||
// * The basis is not checked by default. If enabled, checked with BasisIs().
|
||||
// - Enable with options.check_basis
|
||||
//
|
||||
// This function is symmetric in that:
|
||||
// EXPECT_THAT(actual, IsConsistentWith(expected));
|
||||
// EXPECT_THAT(expected, IsConsistentWith(actual));
|
||||
// agree on matching, they only differ in strings produced. Per gmock
|
||||
// conventions, prefer the former.
|
||||
//
|
||||
// For problems with either primal or dual infeasibility, see
|
||||
// SolveResultMatcherOptions::inf_or_unb_soft_match for guidance on how to
|
||||
// best set the termination reason and inf_or_unb_soft_match.
|
||||
testing::Matcher<SolveResult> IsConsistentWith(
|
||||
const SolveResult& expected, const SolveResultMatcherOptions& options = {});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Rarely used
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Actual UpdateResult.did_update is true.
|
||||
testing::Matcher<UpdateResult> DidUpdate();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Implementation details
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO(b/200835670): use the << operator on Termination instead once it
|
||||
// supports quoting/escaping on termination.detail.
|
||||
void PrintTo(const Termination& termination, std::ostream* os);
|
||||
void PrintTo(const PrimalSolution& primal_solution, std::ostream* os);
|
||||
void PrintTo(const DualSolution& dual_solution, std::ostream* os);
|
||||
void PrintTo(const PrimalRay& primal_ray, std::ostream* os);
|
||||
void PrintTo(const DualRay& dual_ray, std::ostream* os);
|
||||
void PrintTo(const Basis& basis, std::ostream* os);
|
||||
void PrintTo(const Solution& solution, std::ostream* os);
|
||||
void PrintTo(const SolveResult& result, std::ostream* os);
|
||||
|
||||
// We do not want to rely on ::testing::internal::ContainerPrinter because we
|
||||
// want to sort the keys.
|
||||
template <typename K, typename V>
|
||||
void PrintTo(const IdMap<K, V>& id_map, std::ostream* const os) {
|
||||
constexpr int kMaxPrint = 10;
|
||||
int num_added = 0;
|
||||
*os << "{";
|
||||
for (const K k : id_map.SortedKeys()) {
|
||||
if (num_added > 0) {
|
||||
*os << ", ";
|
||||
}
|
||||
if (num_added >= kMaxPrint) {
|
||||
*os << "...(size=" << id_map.size() << ")";
|
||||
break;
|
||||
}
|
||||
*os << "{" << k << ", " << ::testing::PrintToString(id_map.at(k)) << "}";
|
||||
++num_added;
|
||||
}
|
||||
*os << "}";
|
||||
}
|
||||
|
||||
} // namespace math_opt
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_CPP_MATCHERS_H_
|
||||
@@ -47,6 +47,21 @@ class PrinterMessageCallbackImpl {
|
||||
const std::string prefix_;
|
||||
};
|
||||
|
||||
class VectorMessageCallbackImpl {
|
||||
public:
|
||||
explicit VectorMessageCallbackImpl(std::vector<std::string>* const sink)
|
||||
: sink_(ABSL_DIE_IF_NULL(sink)) {}
|
||||
|
||||
void Call(const std::vector<std::string>& messages) {
|
||||
const absl::MutexLock lock(&mutex_);
|
||||
sink_->insert(sink_->end(), messages.begin(), messages.end());
|
||||
}
|
||||
|
||||
private:
|
||||
absl::Mutex mutex_;
|
||||
std::vector<std::string>* const sink_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
MessageCallback PrinterMessageCallback(std::ostream& output_stream,
|
||||
@@ -60,4 +75,14 @@ MessageCallback PrinterMessageCallback(std::ostream& output_stream,
|
||||
[=](const std::vector<std::string>& messages) { impl->Call(messages); };
|
||||
}
|
||||
|
||||
MessageCallback VectorMessageCallback(std::vector<std::string>* sink) {
|
||||
CHECK(sink != nullptr);
|
||||
// Here we must use an std::shared_ptr since std::function requires that its
|
||||
// input is copyable. And VectorMessageCallbackImpl can't be copyable since it
|
||||
// uses an absl::Mutex that is not.
|
||||
const auto impl = std::make_shared<VectorMessageCallbackImpl>(sink);
|
||||
return
|
||||
[=](const std::vector<std::string>& messages) { impl->Call(messages); };
|
||||
}
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -49,6 +50,16 @@ using MessageCallback = std::function<void(const std::vector<std::string>&)>;
|
||||
MessageCallback PrinterMessageCallback(std::ostream& output_stream = std::cout,
|
||||
absl::string_view prefix = "");
|
||||
|
||||
// Returns a message callback function that aggregates all messages in the
|
||||
// provided vector.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// std::vector<std::string> msgs;
|
||||
// SolveArguments args;
|
||||
// args.message_callback = VectorMessageCallback(&msgs);
|
||||
MessageCallback VectorMessageCallback(std::vector<std::string>* sink);
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_CPP_MESSAGE_CALLBACK_H_
|
||||
|
||||
@@ -22,24 +22,27 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "ortools/base/status_macros.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/constraints/indicator/indicator_constraint.h"
|
||||
#include "ortools/math_opt/constraints/quadratic/quadratic_constraint.h"
|
||||
#include "ortools/math_opt/constraints/second_order_cone/second_order_cone_constraint.h"
|
||||
#include "ortools/math_opt/constraints/sos/sos1_constraint.h"
|
||||
#include "ortools/math_opt/constraints/sos/sos2_constraint.h"
|
||||
#include "ortools/math_opt/constraints/util/model_util.h"
|
||||
#include "ortools/math_opt/cpp/linear_constraint.h"
|
||||
#include "ortools/math_opt/cpp/update_tracker.h"
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
#include "ortools/math_opt/storage/linear_expression_data.h"
|
||||
#include "ortools/math_opt/storage/model_storage.h"
|
||||
#include "ortools/math_opt/storage/model_storage_types.h"
|
||||
#include "ortools/math_opt/storage/sparse_coefficient_map.h"
|
||||
#include "ortools/math_opt/storage/sparse_matrix.h"
|
||||
#include "ortools/util/fp_roundtrip_conv.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
@@ -71,8 +74,9 @@ LinearConstraint Model::AddLinearConstraint(
|
||||
const LinearConstraintId constraint = storage()->AddLinearConstraint(
|
||||
bounded_expr.lower_bound_minus_offset(),
|
||||
bounded_expr.upper_bound_minus_offset(), name);
|
||||
for (auto [variable, coef] : bounded_expr.expression.raw_terms()) {
|
||||
storage()->set_linear_constraint_coefficient(constraint, variable, coef);
|
||||
for (const auto& [variable, coef] : bounded_expr.expression.terms()) {
|
||||
storage()->set_linear_constraint_coefficient(constraint,
|
||||
variable.typed_id(), coef);
|
||||
}
|
||||
return LinearConstraint(storage(), constraint);
|
||||
}
|
||||
@@ -138,78 +142,135 @@ std::vector<LinearConstraint> Model::SortedLinearConstraints() const {
|
||||
void Model::SetObjective(const LinearExpression& objective,
|
||||
const bool is_maximize) {
|
||||
CheckOptionalModel(objective.storage());
|
||||
storage()->clear_objective();
|
||||
storage()->set_is_maximize(is_maximize);
|
||||
storage()->set_objective_offset(objective.offset());
|
||||
for (auto [var, coef] : objective.raw_terms()) {
|
||||
storage()->set_linear_objective_coefficient(var, coef);
|
||||
storage()->clear_objective(kPrimaryObjectiveId);
|
||||
storage()->set_is_maximize(kPrimaryObjectiveId, is_maximize);
|
||||
storage()->set_objective_offset(kPrimaryObjectiveId, objective.offset());
|
||||
for (const auto& [var, coef] : objective.terms()) {
|
||||
storage()->set_linear_objective_coefficient(kPrimaryObjectiveId,
|
||||
var.typed_id(), coef);
|
||||
}
|
||||
}
|
||||
|
||||
void Model::SetObjective(const QuadraticExpression& objective,
|
||||
const bool is_maximize) {
|
||||
CheckOptionalModel(objective.storage());
|
||||
storage()->clear_objective();
|
||||
storage()->set_is_maximize(is_maximize);
|
||||
storage()->set_objective_offset(objective.offset());
|
||||
for (auto [var, coef] : objective.raw_linear_terms()) {
|
||||
storage()->set_linear_objective_coefficient(var, coef);
|
||||
storage()->clear_objective(kPrimaryObjectiveId);
|
||||
storage()->set_is_maximize(kPrimaryObjectiveId, is_maximize);
|
||||
storage()->set_objective_offset(kPrimaryObjectiveId, objective.offset());
|
||||
for (const auto& [var, coef] : objective.linear_terms()) {
|
||||
storage()->set_linear_objective_coefficient(kPrimaryObjectiveId,
|
||||
var.typed_id(), coef);
|
||||
}
|
||||
for (auto [vars, coef] : objective.raw_quadratic_terms()) {
|
||||
storage()->set_quadratic_objective_coefficient(vars.first, vars.second,
|
||||
coef);
|
||||
for (const auto& [vars, coef] : objective.quadratic_terms()) {
|
||||
storage()->set_quadratic_objective_coefficient(
|
||||
kPrimaryObjectiveId, vars.typed_id().first, vars.typed_id().second,
|
||||
coef);
|
||||
}
|
||||
}
|
||||
|
||||
void Model::AddToObjective(const LinearExpression& objective_terms) {
|
||||
CheckOptionalModel(objective_terms.storage());
|
||||
storage()->set_objective_offset(objective_terms.offset() +
|
||||
storage()->objective_offset());
|
||||
for (auto [var, coef] : objective_terms.raw_terms()) {
|
||||
storage()->set_objective_offset(
|
||||
kPrimaryObjectiveId,
|
||||
objective_terms.offset() +
|
||||
storage()->objective_offset(kPrimaryObjectiveId));
|
||||
for (const auto& [var, coef] : objective_terms.terms()) {
|
||||
storage()->set_linear_objective_coefficient(
|
||||
var, coef + storage()->linear_objective_coefficient(var));
|
||||
kPrimaryObjectiveId, var.typed_id(),
|
||||
coef + storage()->linear_objective_coefficient(kPrimaryObjectiveId,
|
||||
var.typed_id()));
|
||||
}
|
||||
}
|
||||
|
||||
void Model::AddToObjective(const QuadraticExpression& objective_terms) {
|
||||
CheckOptionalModel(objective_terms.storage());
|
||||
storage()->set_objective_offset(objective_terms.offset() +
|
||||
storage()->objective_offset());
|
||||
for (auto [var, coef] : objective_terms.raw_linear_terms()) {
|
||||
storage()->set_objective_offset(
|
||||
kPrimaryObjectiveId,
|
||||
objective_terms.offset() +
|
||||
storage()->objective_offset(kPrimaryObjectiveId));
|
||||
for (const auto& [var, coef] : objective_terms.linear_terms()) {
|
||||
storage()->set_linear_objective_coefficient(
|
||||
var, coef + storage()->linear_objective_coefficient(var));
|
||||
kPrimaryObjectiveId, var.typed_id(),
|
||||
coef + storage()->linear_objective_coefficient(kPrimaryObjectiveId,
|
||||
var.typed_id()));
|
||||
}
|
||||
for (auto [vars, coef] : objective_terms.raw_quadratic_terms()) {
|
||||
for (const auto& [vars, coef] : objective_terms.quadratic_terms()) {
|
||||
storage()->set_quadratic_objective_coefficient(
|
||||
vars.first, vars.second,
|
||||
coef + storage()->quadratic_objective_coefficient(vars.first,
|
||||
vars.second));
|
||||
kPrimaryObjectiveId, vars.typed_id().first, vars.typed_id().second,
|
||||
coef + storage()->quadratic_objective_coefficient(
|
||||
kPrimaryObjectiveId, vars.typed_id().first,
|
||||
vars.typed_id().second));
|
||||
}
|
||||
}
|
||||
|
||||
LinearExpression Model::ObjectiveAsLinearExpression() const {
|
||||
CHECK_EQ(storage()->num_quadratic_objective_terms(), 0)
|
||||
CHECK_EQ(storage()->num_quadratic_objective_terms(kPrimaryObjectiveId), 0)
|
||||
<< "The objective function contains quadratic terms and cannot be "
|
||||
"represented as a LinearExpression";
|
||||
LinearExpression result = storage()->objective_offset();
|
||||
for (const auto& [v, coef] : storage()->linear_objective()) {
|
||||
LinearExpression result = storage()->objective_offset(kPrimaryObjectiveId);
|
||||
for (const auto& [v, coef] :
|
||||
storage()->linear_objective(kPrimaryObjectiveId)) {
|
||||
result += Variable(storage(), v) * coef;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QuadraticExpression Model::ObjectiveAsQuadraticExpression() const {
|
||||
QuadraticExpression result = storage()->objective_offset();
|
||||
for (const auto& [v, coef] : storage()->linear_objective()) {
|
||||
QuadraticExpression result = storage()->objective_offset(kPrimaryObjectiveId);
|
||||
for (const auto& [v, coef] :
|
||||
storage()->linear_objective(kPrimaryObjectiveId)) {
|
||||
result += Variable(storage(), v) * coef;
|
||||
}
|
||||
for (const auto& [v1, v2, coef] : storage()->quadratic_objective_terms()) {
|
||||
for (const auto& [v1, v2, coef] :
|
||||
storage()->quadratic_objective_terms(kPrimaryObjectiveId)) {
|
||||
result +=
|
||||
QuadraticTerm(Variable(storage(), v1), Variable(storage(), v2), coef);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Objective> Model::AuxiliaryObjectives() const {
|
||||
std::vector<Objective> result;
|
||||
result.reserve(num_auxiliary_objectives());
|
||||
for (const AuxiliaryObjectiveId id : storage()->AuxiliaryObjectives()) {
|
||||
result.push_back(auxiliary_objective(id));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Objective> Model::SortedAuxiliaryObjectives() const {
|
||||
std::vector<Objective> result = AuxiliaryObjectives();
|
||||
std::sort(result.begin(), result.end(),
|
||||
[](const Objective& l, const Objective& r) {
|
||||
return l.typed_id() < r.typed_id();
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void Model::SetObjective(const Objective objective,
|
||||
const LinearExpression& expression,
|
||||
const bool is_maximize) {
|
||||
CheckModel(objective.storage());
|
||||
CheckOptionalModel(expression.storage());
|
||||
storage()->clear_objective(objective.typed_id());
|
||||
set_is_maximize(objective, is_maximize);
|
||||
set_objective_offset(objective, expression.offset());
|
||||
for (const auto [var, coef] : expression.terms()) {
|
||||
set_objective_coefficient(objective, var, coef);
|
||||
}
|
||||
}
|
||||
|
||||
void Model::AddToObjective(Objective objective,
|
||||
const LinearExpression& expression) {
|
||||
CheckModel(objective.storage());
|
||||
CheckOptionalModel(expression.storage());
|
||||
set_objective_offset(objective, objective.offset() + expression.offset());
|
||||
for (const auto [var, coef] : expression.terms()) {
|
||||
set_objective_coefficient(objective, var,
|
||||
objective.coefficient(var) + coef);
|
||||
}
|
||||
}
|
||||
|
||||
ModelProto Model::ExportModel() const { return storage()->ExportModel(); }
|
||||
|
||||
std::unique_ptr<UpdateTracker> Model::NewUpdateTracker() {
|
||||
@@ -225,9 +286,22 @@ std::ostream& operator<<(std::ostream& ostr, const Model& model) {
|
||||
if (!model.name().empty()) ostr << " " << model.name();
|
||||
ostr << ":\n";
|
||||
|
||||
ostr << " Objective:\n"
|
||||
<< (model.is_maximize() ? " maximize " : " minimize ")
|
||||
<< model.ObjectiveAsQuadraticExpression() << "\n";
|
||||
if (model.num_auxiliary_objectives() == 0) {
|
||||
ostr << " Objective:\n"
|
||||
<< (model.is_maximize() ? " maximize " : " minimize ")
|
||||
<< model.ObjectiveAsQuadraticExpression() << "\n";
|
||||
} else {
|
||||
ostr << " Objectives:\n";
|
||||
const auto stream_objective = [](std::ostream& ostr, const Objective obj) {
|
||||
ostr << " " << obj << " (priority " << obj.priority()
|
||||
<< "): " << (obj.maximize() ? "maximize " : "minimize ")
|
||||
<< obj.AsQuadraticExpression() << "\n";
|
||||
};
|
||||
stream_objective(ostr, model.primary_objective());
|
||||
for (const Objective obj : model.SortedAuxiliaryObjectives()) {
|
||||
stream_objective(ostr, obj);
|
||||
}
|
||||
}
|
||||
|
||||
ostr << " Linear constraints:\n";
|
||||
for (const LinearConstraint constraint : model.SortedLinearConstraints()) {
|
||||
@@ -244,6 +318,14 @@ std::ostream& operator<<(std::ostream& ostr, const Model& model) {
|
||||
}
|
||||
}
|
||||
|
||||
if (model.num_second_order_cone_constraints() > 0) {
|
||||
ostr << " Second-order cone constraints:\n";
|
||||
for (const SecondOrderConeConstraint constraint :
|
||||
model.SortedSecondOrderConeConstraints()) {
|
||||
ostr << " " << constraint << ": " << constraint.ToString() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (model.num_sos1_constraints() > 0) {
|
||||
ostr << " SOS1 constraints:\n";
|
||||
for (const Sos1Constraint constraint : model.SortedSos1Constraints()) {
|
||||
@@ -305,8 +387,9 @@ QuadraticConstraint Model::AddQuadraticConstraint(
|
||||
}
|
||||
SparseSymmetricMatrix quadratic_terms;
|
||||
for (const auto& [var_ids, coeff] :
|
||||
bounded_expr.expression.raw_quadratic_terms()) {
|
||||
quadratic_terms.set(var_ids.first, var_ids.second, coeff);
|
||||
bounded_expr.expression.quadratic_terms()) {
|
||||
quadratic_terms.set(var_ids.typed_id().first, var_ids.typed_id().second,
|
||||
coeff);
|
||||
}
|
||||
const QuadraticConstraintId id =
|
||||
storage()->AddAtomicConstraint(QuadraticConstraintData{
|
||||
@@ -319,6 +402,27 @@ QuadraticConstraint Model::AddQuadraticConstraint(
|
||||
return QuadraticConstraint(storage(), id);
|
||||
}
|
||||
|
||||
// --------------------- Second-order cone constraints -------------------------
|
||||
|
||||
SecondOrderConeConstraint Model::AddSecondOrderConeConstraint(
|
||||
const std::vector<LinearExpression>& arguments_to_norm,
|
||||
const LinearExpression& upper_bound, const absl::string_view name) {
|
||||
CheckOptionalModel(upper_bound.storage());
|
||||
std::vector<LinearExpressionData> arguments_to_norm_data;
|
||||
arguments_to_norm_data.reserve(arguments_to_norm.size());
|
||||
for (const LinearExpression& expr : arguments_to_norm) {
|
||||
CheckOptionalModel(expr.storage());
|
||||
arguments_to_norm_data.push_back(FromLinearExpression(expr));
|
||||
}
|
||||
const SecondOrderConeConstraintId id =
|
||||
storage()->AddAtomicConstraint(SecondOrderConeConstraintData{
|
||||
.upper_bound = FromLinearExpression(upper_bound),
|
||||
.arguments_to_norm = std::move(arguments_to_norm_data),
|
||||
.name = std::string(name),
|
||||
});
|
||||
return SecondOrderConeConstraint(storage(), id);
|
||||
}
|
||||
|
||||
// --------------------------- SOS1 constraints --------------------------------
|
||||
|
||||
namespace {
|
||||
@@ -326,14 +430,13 @@ namespace {
|
||||
template <typename SosData>
|
||||
SosData MakeSosData(const std::vector<LinearExpression>& expressions,
|
||||
std::vector<double> weights, const absl::string_view name) {
|
||||
std::vector<typename SosData::LinearExpression> storage_expressions;
|
||||
std::vector<LinearExpressionData> storage_expressions;
|
||||
storage_expressions.reserve(expressions.size());
|
||||
for (const LinearExpression& expr : expressions) {
|
||||
typename SosData::LinearExpression& storage_expr =
|
||||
storage_expressions.emplace_back();
|
||||
LinearExpressionData& storage_expr = storage_expressions.emplace_back();
|
||||
storage_expr.offset = expr.offset();
|
||||
for (const auto [var, coeff] : expr.raw_terms()) {
|
||||
storage_expr.terms[var] = coeff;
|
||||
for (const auto& [var, coeff] : expr.terms()) {
|
||||
storage_expr.coeffs.set(var.typed_id(), coeff);
|
||||
}
|
||||
}
|
||||
return SosData(std::move(storage_expressions), std::move(weights),
|
||||
|
||||
@@ -24,19 +24,21 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "ortools/base/status_builder.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/math_opt/constraints/indicator/indicator_constraint.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/constraints/quadratic/quadratic_constraint.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/constraints/second_order_cone/second_order_cone_constraint.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/constraints/sos/sos1_constraint.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/constraints/sos/sos2_constraint.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/constraints/util/model_util.h"
|
||||
#include "ortools/math_opt/cpp/key_types.h"
|
||||
#include "ortools/math_opt/cpp/linear_constraint.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/cpp/objective.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/cpp/update_tracker.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/model.pb.h" // IWYU pragma: export
|
||||
@@ -410,6 +412,64 @@ class Model {
|
||||
// the model sorted by id.
|
||||
inline std::vector<QuadraticConstraint> SortedQuadraticConstraints() const;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// SecondOrderConeConstraint methods
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Adds a second-order cone constraint to the model of the form
|
||||
// ||arguments_to_norm||₂ ≤ upper_bound.
|
||||
//
|
||||
// Usage:
|
||||
// Model model = ...;
|
||||
// const Variable x = ...;
|
||||
// const Variable y = ...;
|
||||
// model.AddSecondOrderConeConstraint({x, y}, 1.0, "soc");
|
||||
// model.AddSecondOrderConeConstraint({1.0, 3 * y - x}, 2 * x);
|
||||
SecondOrderConeConstraint AddSecondOrderConeConstraint(
|
||||
const std::vector<LinearExpression>& arguments_to_norm,
|
||||
const LinearExpression& upper_bound, absl::string_view name = "");
|
||||
|
||||
// Removes a second-order cone constraint from the model.
|
||||
//
|
||||
// It is an error to use any reference to this second-order cone constraint
|
||||
// after this operation. Runs in O(#linear terms appearing in constraint).
|
||||
inline void DeleteSecondOrderConeConstraint(
|
||||
SecondOrderConeConstraint constraint);
|
||||
|
||||
// The number of second-order cone constraints in the model.
|
||||
//
|
||||
// Equal to the number of second-order cone constraints created minus the
|
||||
// number of second-order cone constraints deleted.
|
||||
inline int64_t num_second_order_cone_constraints() const;
|
||||
|
||||
// The returned id of the next call to AddSecondOrderConeConstraint.
|
||||
inline int64_t next_second_order_cone_constraint_id() const;
|
||||
|
||||
// Returns true if this id has been created and not yet deleted.
|
||||
inline bool has_second_order_cone_constraint(int64_t id) const;
|
||||
|
||||
// Returns true if this id has been created and not yet deleted.
|
||||
inline bool has_second_order_cone_constraint(
|
||||
SecondOrderConeConstraintId id) const;
|
||||
|
||||
// Will CHECK if has_second_order_cone_constraint(id) is false.
|
||||
inline SecondOrderConeConstraint second_order_cone_constraint(
|
||||
int64_t id) const;
|
||||
|
||||
// Will CHECK if has_second_order_cone_constraint(id) is false.
|
||||
inline SecondOrderConeConstraint second_order_cone_constraint(
|
||||
SecondOrderConeConstraintId id) const;
|
||||
|
||||
// Returns all the existing (created and not deleted) second-order cone
|
||||
// constraints in the model in an arbitrary order.
|
||||
inline std::vector<SecondOrderConeConstraint> SecondOrderConeConstraints()
|
||||
const;
|
||||
|
||||
// Returns all the existing (created and not deleted) second-order cone
|
||||
// constraints in the model sorted by id.
|
||||
inline std::vector<SecondOrderConeConstraint>
|
||||
SortedSecondOrderConeConstraints() const;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Sos1Constraint methods
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -642,6 +702,11 @@ class Model {
|
||||
LinearExpression ObjectiveAsLinearExpression() const;
|
||||
QuadraticExpression ObjectiveAsQuadraticExpression() const;
|
||||
|
||||
// Returns an object referring to the primary objective in the model. Can be
|
||||
// used with the multi-objective API in the same way that an auxiliary
|
||||
// objective can be.
|
||||
inline Objective primary_objective() const;
|
||||
|
||||
// Returns 0.0 if this variable has no linear objective coefficient.
|
||||
inline double objective_coefficient(Variable variable) const;
|
||||
|
||||
@@ -661,8 +726,9 @@ class Model {
|
||||
inline void set_objective_coefficient(Variable first_variable,
|
||||
Variable second_variable, double value);
|
||||
|
||||
// Equivalent to calling set_linear_coefficient(v, 0.0) for every variable
|
||||
// with nonzero objective coefficient.
|
||||
// Sets the objective offset, linear terms, and quadratic terms of the
|
||||
// objective to zero. The name, direction, and priority are unchanged.
|
||||
// Equivalent to SetObjective(0.0, is_maximize()).
|
||||
//
|
||||
// Runs in O(#linear and quadratic objective terms with nonzero coefficient).
|
||||
inline void clear_objective();
|
||||
@@ -683,6 +749,103 @@ class Model {
|
||||
// Prefer set_maximize() and set_minimize() above for more readable code.
|
||||
inline void set_is_maximize(bool is_maximize);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Auxiliary objective methods
|
||||
//
|
||||
// This is an API for creating and deleting auxiliary objectives. To modify
|
||||
// them, use the multi-objective API below.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Adds an empty (== 0) auxiliary minimization objective to the model.
|
||||
inline Objective AddAuxiliaryObjective(int64_t priority,
|
||||
absl::string_view name = {});
|
||||
// Adds `expression` as an auxiliary objective to the model.
|
||||
inline Objective AddAuxiliaryObjective(const LinearExpression& expression,
|
||||
bool is_maximize, int64_t priority,
|
||||
absl::string_view name = {});
|
||||
// Adds `expression` as an auxiliary maximization objective to the model.
|
||||
inline Objective AddMaximizationObjective(const LinearExpression& expression,
|
||||
int64_t priority,
|
||||
absl::string_view name = {});
|
||||
// Adds `expression` as an auxiliary minimization objective to the model.
|
||||
inline Objective AddMinimizationObjective(const LinearExpression& expression,
|
||||
int64_t priority,
|
||||
absl::string_view name = {});
|
||||
|
||||
// Removes an auxiliary objective from the model.
|
||||
//
|
||||
// It is an error to use any reference to this auxiliary objective after this
|
||||
// operation. Runs in O(1) time.
|
||||
//
|
||||
// Will CHECK-fail if `objective` is from another model, has already been
|
||||
// deleted, or is a primary objective.
|
||||
inline void DeleteAuxiliaryObjective(Objective objective);
|
||||
|
||||
// The number of auxiliary objectives in the model.
|
||||
//
|
||||
// Equal to the number of auxiliary objectives created minus the number of
|
||||
// auxiliary objectives deleted.
|
||||
inline int64_t num_auxiliary_objectives() const;
|
||||
|
||||
// The returned id of the next call to AddAuxiliaryObjectve.
|
||||
inline int64_t next_auxiliary_objective_id() const;
|
||||
|
||||
// Returns true if this id has been created and not yet deleted.
|
||||
inline bool has_auxiliary_objective(int64_t id) const;
|
||||
|
||||
// Returns true if this id has been created and not yet deleted.
|
||||
inline bool has_auxiliary_objective(AuxiliaryObjectiveId id) const;
|
||||
|
||||
// Will CHECK if has_auxiliary_objective(id) is false.
|
||||
inline Objective auxiliary_objective(int64_t id) const;
|
||||
|
||||
// Will CHECK if has_auxiliary_objective(id) is false.
|
||||
inline Objective auxiliary_objective(AuxiliaryObjectiveId id) const;
|
||||
|
||||
// Returns all the existing (created and not deleted) auxiliary objectives in
|
||||
// the model in an arbitrary order.
|
||||
std::vector<Objective> AuxiliaryObjectives() const;
|
||||
|
||||
// Returns all the existing (created and not deleted) auxiliary objectives in
|
||||
// the model sorted by id.
|
||||
std::vector<Objective> SortedAuxiliaryObjectives() const;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Multi-objective methods
|
||||
//
|
||||
// This is an API for setting objective properties (for either primary or
|
||||
// auxiliary objectives). Only linear objectives are supported through this
|
||||
// API. To query objective properties, use the methods on `Objective`.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Sets `objective` to be maximizing `expression`.
|
||||
inline void Maximize(Objective objective, const LinearExpression& expression);
|
||||
// Sets `objective` to be minimizing `expression`.
|
||||
inline void Minimize(Objective objective, const LinearExpression& expression);
|
||||
// Sets the objective to optimize the provided expression.
|
||||
void SetObjective(Objective objective, const LinearExpression& expression,
|
||||
bool is_maximize);
|
||||
|
||||
// Adds the provided expression terms to the objective.
|
||||
void AddToObjective(Objective objective, const LinearExpression& expression);
|
||||
|
||||
// Sets the priority for an objective (lower is more important). `priority`
|
||||
// must be nonnegative.
|
||||
inline void set_objective_priority(Objective objective, int64_t priority);
|
||||
|
||||
// Setting a value to 0.0 will delete the variable from the underlying sparse
|
||||
// representation (and has no effect if the variable is not present).
|
||||
inline void set_objective_coefficient(Objective objective, Variable variable,
|
||||
double value);
|
||||
|
||||
inline void set_objective_offset(Objective objective, double value);
|
||||
|
||||
inline void set_maximize(Objective objective);
|
||||
inline void set_minimize(Objective objective);
|
||||
|
||||
// Prefer set_maximize() and set_minimize() above for more readable code.
|
||||
inline void set_is_maximize(Objective objective, bool is_maximize);
|
||||
|
||||
// Returns a proto representation of the optimization model.
|
||||
//
|
||||
// See FromModelProto() to build a Model from a proto.
|
||||
@@ -1027,6 +1190,53 @@ std::vector<QuadraticConstraint> Model::SortedQuadraticConstraints() const {
|
||||
return SortedAtomicConstraints<QuadraticConstraint>(*storage());
|
||||
}
|
||||
|
||||
// --------------------- Second-order cone constraints -------------------------
|
||||
|
||||
void Model::DeleteSecondOrderConeConstraint(
|
||||
const SecondOrderConeConstraint constraint) {
|
||||
CheckModel(constraint.storage());
|
||||
storage()->DeleteAtomicConstraint(constraint.typed_id());
|
||||
}
|
||||
|
||||
int64_t Model::num_second_order_cone_constraints() const {
|
||||
return storage()->num_constraints<SecondOrderConeConstraintId>();
|
||||
}
|
||||
|
||||
int64_t Model::next_second_order_cone_constraint_id() const {
|
||||
return storage()->next_constraint_id<SecondOrderConeConstraintId>().value();
|
||||
}
|
||||
|
||||
bool Model::has_second_order_cone_constraint(const int64_t id) const {
|
||||
return has_second_order_cone_constraint(SecondOrderConeConstraintId(id));
|
||||
}
|
||||
|
||||
bool Model::has_second_order_cone_constraint(
|
||||
const SecondOrderConeConstraintId id) const {
|
||||
return storage()->has_constraint(id);
|
||||
}
|
||||
|
||||
SecondOrderConeConstraint Model::second_order_cone_constraint(
|
||||
const int64_t id) const {
|
||||
return second_order_cone_constraint(SecondOrderConeConstraintId(id));
|
||||
}
|
||||
|
||||
SecondOrderConeConstraint Model::second_order_cone_constraint(
|
||||
const SecondOrderConeConstraintId id) const {
|
||||
CHECK(has_second_order_cone_constraint(id))
|
||||
<< "No second-order cone constraint with id: " << id.value();
|
||||
return SecondOrderConeConstraint(storage(), id);
|
||||
}
|
||||
|
||||
std::vector<SecondOrderConeConstraint> Model::SecondOrderConeConstraints()
|
||||
const {
|
||||
return AtomicConstraints<SecondOrderConeConstraint>(*storage());
|
||||
}
|
||||
|
||||
std::vector<SecondOrderConeConstraint> Model::SortedSecondOrderConeConstraints()
|
||||
const {
|
||||
return SortedAtomicConstraints<SecondOrderConeConstraint>(*storage());
|
||||
}
|
||||
|
||||
// --------------------------- SOS1 constraints --------------------------------
|
||||
|
||||
void Model::DeleteSos1Constraint(const Sos1Constraint constraint) {
|
||||
@@ -1205,23 +1415,30 @@ void Model::AddToObjective(const LinearTerm objective) {
|
||||
AddToObjective(LinearExpression(objective));
|
||||
}
|
||||
|
||||
Objective Model::primary_objective() const {
|
||||
return Objective::Primary(storage());
|
||||
}
|
||||
|
||||
double Model::objective_coefficient(const Variable variable) const {
|
||||
CheckModel(variable.storage());
|
||||
return storage()->linear_objective_coefficient(variable.typed_id());
|
||||
return storage()->linear_objective_coefficient(kPrimaryObjectiveId,
|
||||
variable.typed_id());
|
||||
}
|
||||
|
||||
double Model::objective_coefficient(const Variable first_variable,
|
||||
const Variable second_variable) const {
|
||||
CheckModel(first_variable.storage());
|
||||
CheckModel(second_variable.storage());
|
||||
return storage()->quadratic_objective_coefficient(first_variable.typed_id(),
|
||||
return storage()->quadratic_objective_coefficient(kPrimaryObjectiveId,
|
||||
first_variable.typed_id(),
|
||||
second_variable.typed_id());
|
||||
}
|
||||
|
||||
void Model::set_objective_coefficient(const Variable variable,
|
||||
const double value) {
|
||||
CheckModel(variable.storage());
|
||||
storage()->set_linear_objective_coefficient(variable.typed_id(), value);
|
||||
storage()->set_linear_objective_coefficient(kPrimaryObjectiveId,
|
||||
variable.typed_id(), value);
|
||||
}
|
||||
|
||||
void Model::set_objective_coefficient(const Variable first_variable,
|
||||
@@ -1230,15 +1447,18 @@ void Model::set_objective_coefficient(const Variable first_variable,
|
||||
CheckModel(first_variable.storage());
|
||||
CheckModel(second_variable.storage());
|
||||
storage()->set_quadratic_objective_coefficient(
|
||||
first_variable.typed_id(), second_variable.typed_id(), value);
|
||||
kPrimaryObjectiveId, first_variable.typed_id(),
|
||||
second_variable.typed_id(), value);
|
||||
}
|
||||
|
||||
void Model::clear_objective() { storage()->clear_objective(); }
|
||||
void Model::clear_objective() {
|
||||
storage()->clear_objective(kPrimaryObjectiveId);
|
||||
}
|
||||
|
||||
bool Model::is_objective_coefficient_nonzero(const Variable variable) const {
|
||||
CheckModel(variable.storage());
|
||||
return storage()->is_linear_objective_coefficient_nonzero(
|
||||
variable.typed_id());
|
||||
kPrimaryObjectiveId, variable.typed_id());
|
||||
}
|
||||
|
||||
bool Model::is_objective_coefficient_nonzero(
|
||||
@@ -1246,23 +1466,140 @@ bool Model::is_objective_coefficient_nonzero(
|
||||
CheckModel(first_variable.storage());
|
||||
CheckModel(second_variable.storage());
|
||||
return storage()->is_quadratic_objective_coefficient_nonzero(
|
||||
first_variable.typed_id(), second_variable.typed_id());
|
||||
kPrimaryObjectiveId, first_variable.typed_id(),
|
||||
second_variable.typed_id());
|
||||
}
|
||||
|
||||
double Model::objective_offset() const { return storage()->objective_offset(); }
|
||||
double Model::objective_offset() const {
|
||||
return storage()->objective_offset(kPrimaryObjectiveId);
|
||||
}
|
||||
|
||||
void Model::set_objective_offset(const double value) {
|
||||
storage()->set_objective_offset(value);
|
||||
storage()->set_objective_offset(kPrimaryObjectiveId, value);
|
||||
}
|
||||
|
||||
bool Model::is_maximize() const { return storage()->is_maximize(); }
|
||||
bool Model::is_maximize() const {
|
||||
return storage()->is_maximize(kPrimaryObjectiveId);
|
||||
}
|
||||
|
||||
void Model::set_maximize() { storage()->set_maximize(); }
|
||||
void Model::set_maximize() { storage()->set_maximize(kPrimaryObjectiveId); }
|
||||
|
||||
void Model::set_minimize() { storage()->set_minimize(); }
|
||||
void Model::set_minimize() { storage()->set_minimize(kPrimaryObjectiveId); }
|
||||
|
||||
void Model::set_is_maximize(const bool is_maximize) {
|
||||
storage()->set_is_maximize(is_maximize);
|
||||
storage()->set_is_maximize(kPrimaryObjectiveId, is_maximize);
|
||||
}
|
||||
|
||||
// -------------------------- Auxiliary objectives -----------------------------
|
||||
|
||||
Objective Model::AddAuxiliaryObjective(const int64_t priority,
|
||||
const absl::string_view name) {
|
||||
return Objective::Auxiliary(storage(),
|
||||
storage()->AddAuxiliaryObjective(priority, name));
|
||||
}
|
||||
|
||||
Objective Model::AddAuxiliaryObjective(const LinearExpression& expression,
|
||||
const bool is_maximize,
|
||||
const int64_t priority,
|
||||
const absl::string_view name) {
|
||||
const Objective obj = AddAuxiliaryObjective(priority, name);
|
||||
SetObjective(obj, expression, is_maximize);
|
||||
return obj;
|
||||
}
|
||||
|
||||
Objective Model::AddMaximizationObjective(const LinearExpression& expression,
|
||||
const int64_t priority,
|
||||
const absl::string_view name) {
|
||||
return AddAuxiliaryObjective(expression, /*is_maximize=*/true, priority,
|
||||
name);
|
||||
}
|
||||
|
||||
Objective Model::AddMinimizationObjective(const LinearExpression& expression,
|
||||
const int64_t priority,
|
||||
const absl::string_view name) {
|
||||
return AddAuxiliaryObjective(expression, /*is_maximize=*/false, priority,
|
||||
name);
|
||||
}
|
||||
|
||||
void Model::DeleteAuxiliaryObjective(const Objective objective) {
|
||||
CheckModel(objective.storage());
|
||||
CHECK(!objective.is_primary()) << "cannot delete primary objective";
|
||||
const AuxiliaryObjectiveId id = *objective.typed_id();
|
||||
CHECK(storage()->has_auxiliary_objective(id))
|
||||
<< "cannot delete unrecognized auxiliary objective id: " << id;
|
||||
storage()->DeleteAuxiliaryObjective(id);
|
||||
}
|
||||
|
||||
int64_t Model::num_auxiliary_objectives() const {
|
||||
return storage()->num_auxiliary_objectives();
|
||||
}
|
||||
|
||||
int64_t Model::next_auxiliary_objective_id() const {
|
||||
return storage()->next_auxiliary_objective_id().value();
|
||||
}
|
||||
|
||||
bool Model::has_auxiliary_objective(const int64_t id) const {
|
||||
return has_auxiliary_objective(AuxiliaryObjectiveId(id));
|
||||
}
|
||||
|
||||
bool Model::has_auxiliary_objective(const AuxiliaryObjectiveId id) const {
|
||||
return storage()->has_auxiliary_objective(id);
|
||||
}
|
||||
|
||||
Objective Model::auxiliary_objective(const int64_t id) const {
|
||||
return auxiliary_objective(AuxiliaryObjectiveId(id));
|
||||
}
|
||||
|
||||
Objective Model::auxiliary_objective(const AuxiliaryObjectiveId id) const {
|
||||
CHECK(has_auxiliary_objective(id))
|
||||
<< "unrecognized auxiliary objective id: " << id;
|
||||
return Objective::Auxiliary(storage(), id);
|
||||
}
|
||||
|
||||
// ---------------------------- Multi-objective --------------------------------
|
||||
|
||||
void Model::Maximize(const Objective objective,
|
||||
const LinearExpression& expression) {
|
||||
SetObjective(objective, expression, /*is_maximize=*/true);
|
||||
}
|
||||
|
||||
void Model::Minimize(const Objective objective,
|
||||
const LinearExpression& expression) {
|
||||
SetObjective(objective, expression, /*is_maximize=*/false);
|
||||
}
|
||||
|
||||
void Model::set_objective_priority(const Objective objective,
|
||||
const int64_t priority) {
|
||||
CheckModel(objective.storage());
|
||||
storage()->set_objective_priority(objective.typed_id(), priority);
|
||||
}
|
||||
|
||||
void Model::set_objective_coefficient(const Objective objective,
|
||||
const Variable variable,
|
||||
const double value) {
|
||||
CheckModel(objective.storage());
|
||||
CheckModel(variable.storage());
|
||||
storage()->set_linear_objective_coefficient(objective.typed_id(),
|
||||
variable.typed_id(), value);
|
||||
}
|
||||
|
||||
void Model::set_objective_offset(const Objective objective,
|
||||
const double value) {
|
||||
CheckModel(objective.storage());
|
||||
storage()->set_objective_offset(objective.typed_id(), value);
|
||||
}
|
||||
|
||||
void Model::set_maximize(const Objective objective) {
|
||||
set_is_maximize(objective, /*is_maximize=*/true);
|
||||
}
|
||||
|
||||
void Model::set_minimize(const Objective objective) {
|
||||
set_is_maximize(objective, /*is_maximize=*/false);
|
||||
}
|
||||
|
||||
void Model::set_is_maximize(const Objective objective, const bool is_maximize) {
|
||||
CheckModel(objective.storage());
|
||||
storage()->set_is_maximize(objective.typed_id(), is_maximize);
|
||||
}
|
||||
|
||||
void Model::CheckOptionalModel(const ModelStorage* const other_storage) const {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user