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

This commit is contained in:
Laurent Perron
2023-04-05 14:00:42 +02:00
198 changed files with 10312 additions and 4947 deletions

View File

@@ -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/...

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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]

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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]

View File

@@ -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);

View File

@@ -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});

View File

@@ -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]

View File

@@ -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();

View File

@@ -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]

View File

@@ -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();

View File

@@ -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]

View File

@@ -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();

View File

@@ -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]

View File

@@ -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();

View File

@@ -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();

View File

@@ -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]

View File

@@ -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();

View File

@@ -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]

View File

@@ -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();

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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",
],
)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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>
```

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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",

View File

@@ -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)

View File

@@ -43,6 +43,7 @@
//
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstddef>
#include <cstdint>

View File

@@ -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

View File

@@ -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(); }

View File

@@ -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

View File

@@ -31,6 +31,7 @@
#define OR_TOOLS_LP_DATA_SPARSE_VECTOR_H_
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <string>

View File

@@ -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"],
)

View File

@@ -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",

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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",
],
)

View File

@@ -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,

View 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",
],
)

View 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.
#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

View File

@@ -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_

View 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

View 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_

View 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

View 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_

View File

@@ -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",
],
)

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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",

View File

@@ -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

View File

@@ -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(

View File

@@ -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",
],
)

View File

@@ -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 {

View 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

View 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_

View File

@@ -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;

View File

@@ -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_

View File

@@ -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(),

View File

@@ -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;

View File

@@ -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,

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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_

View File

@@ -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;
}

View File

@@ -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",
],
)

View File

@@ -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();
}

View File

@@ -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 {

View File

@@ -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_

View File

@@ -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_

View 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_

View 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

View 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_

View File

@@ -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_

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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_

View File

@@ -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

View File

@@ -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_

View File

@@ -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),

View File

@@ -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