use pybind11 for ortools/graph

This commit is contained in:
Laurent Perron
2022-03-31 18:21:35 +02:00
parent 8ef66920d9
commit 52e439d5ef
15 changed files with 369 additions and 290 deletions

View File

@@ -0,0 +1,31 @@
# Python wrapper for graph libraries.
load("@ortools_deps//:requirements.bzl", "requirement")
load("@pybind11_bazel//:build_defs.bzl", "pybind_extension")
pybind_extension(
name = "linear_sum_assignment",
srcs = ["linear_sum_assignment.cc"],
visibility = ["//visibility:public"],
deps = [
"//ortools/graph:assignment"
],
)
pybind_extension(
name = "max_flow",
srcs = ["max_flow.cc"],
visibility = ["//visibility:public"],
deps = [
"//ortools/graph:max_flow"
],
)
pybind_extension(
name = "min_cost_flow",
srcs = ["min_cost_flow.cc"],
visibility = ["//visibility:public"],
deps = [
"//ortools/graph:min_cost_flow"
],
)

View File

@@ -1,34 +1,53 @@
set_property(SOURCE graph.i PROPERTY CPLUSPLUS ON)
set_property(SOURCE graph.i PROPERTY SWIG_MODULE_NAME pywrapgraph)
set_property(SOURCE graph.i PROPERTY COMPILE_DEFINITIONS
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT)
swig_add_library(pywrapgraph
TYPE SHARED
LANGUAGE python
OUTPUT_DIR ${PYTHON_PROJECT_DIR}/graph
SOURCES graph.i)
target_include_directories(pywrapgraph PRIVATE ${Python3_INCLUDE_DIRS})
set_property(TARGET pywrapgraph PROPERTY SWIG_USE_TARGET_INCLUDE_DIRECTORIES ON)
target_compile_definitions(pywrapgraph PUBLIC "PY3")
pybind11_add_module(linear_sum_assignment_pybind11 MODULE linear_sum_assignment.cc)
# note: macOS is APPLE and also UNIX !
set_target_properties(linear_sum_assignment_pybind11 PROPERTIES
LIBRARY_OUTPUT_NAME "linear_sum_assignment")
if(APPLE)
set_target_properties(pywrapgraph PROPERTIES
set_target_properties(linear_sum_assignment_pybind11 PROPERTIES
SUFFIX ".so"
INSTALL_RPATH "@loader_path;@loader_path/../../${PROJECT_NAME}/.libs")
set_property(TARGET pywrapgraph APPEND PROPERTY
LINK_FLAGS "-flat_namespace -undefined suppress")
INSTALL_RPATH "@loader_path;@loader_path/../../../${PYTHON_PROJECT}/.libs"
LINK_FLAGS "-flat_namespace -undefined suppress"
)
elseif(UNIX)
set_target_properties(pywrapgraph PROPERTIES
INSTALL_RPATH "$ORIGIN:$ORIGIN/../../${PROJECT_NAME}/.libs")
set_target_properties(linear_sum_assignment_pybind11 PROPERTIES
INSTALL_RPATH "$ORIGIN:$ORIGIN/../../../${PYTHON_PROJECT}/.libs"
)
endif()
target_link_libraries(pywrapgraph PRIVATE ortools::ortools)
target_link_libraries(linear_sum_assignment_pybind11 PRIVATE ${PROJECT_NAMESPACE}::ortools)
add_library(${PROJECT_NAMESPACE}::linear_sum_assignment_pybind11 ALIAS linear_sum_assignment_pybind11)
# Variable PYTHON_LIBRARIES can contains keyword `optimized`
# which won't be interpreted inside a generator expression.
# i.e. we can't use: $<$<PLATFORM_ID:Windows>:${PYTHON_LIBRARIES}>
# see: https://cmake.org/cmake/help/git-stage/command/target_link_libraries.html#command:target_link_libraries
if(MSVC)
target_link_libraries(pywrapgraph PRIVATE ${Python3_LIBRARIES})
pybind11_add_module(max_flow_pybind11 MODULE max_flow.cc)
# note: macOS is APPLE and also UNIX !
set_target_properties(max_flow_pybind11 PROPERTIES
LIBRARY_OUTPUT_NAME "max_flow")
if(APPLE)
set_target_properties(max_flow_pybind11 PROPERTIES
SUFFIX ".so"
INSTALL_RPATH "@loader_path;@loader_path/../../../${PYTHON_PROJECT}/.libs"
LINK_FLAGS "-flat_namespace -undefined suppress"
)
elseif(UNIX)
set_target_properties(max_flow_pybind11 PROPERTIES
INSTALL_RPATH "$ORIGIN:$ORIGIN/../../../${PYTHON_PROJECT}/.libs"
)
endif()
target_link_libraries(max_flow_pybind11 PRIVATE ${PROJECT_NAMESPACE}::ortools)
add_library(${PROJECT_NAMESPACE}::max_flow_pybind11 ALIAS max_flow_pybind11)
pybind11_add_module(min_cost_flow_pybind11 MODULE min_cost_flow.cc)
# note: macOS is APPLE and also UNIX !
set_target_properties(min_cost_flow_pybind11 PROPERTIES
LIBRARY_OUTPUT_NAME "min_cost_flow")
if(APPLE)
set_target_properties(min_cost_flow_pybind11 PROPERTIES
SUFFIX ".so"
INSTALL_RPATH "@loader_path;@loader_path/../../../${PYTHON_PROJECT}/.libs"
LINK_FLAGS "-flat_namespace -undefined suppress"
)
elseif(UNIX)
set_target_properties(min_cost_flow_pybind11 PROPERTIES
INSTALL_RPATH "$ORIGIN:$ORIGIN/../../../${PYTHON_PROJECT}/.libs"
)
endif()
target_link_libraries(min_cost_flow_pybind11 PRIVATE ${PROJECT_NAMESPACE}::ortools)
add_library(${PROJECT_NAMESPACE}::min_cost_flow_pybind11 ALIAS min_cost_flow_pybind11)

View File

@@ -1,163 +0,0 @@
// Copyright 2010-2021 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.
// This .i files exposes some of the C++ classes in ../, namely :
// - SimpleMaxFlow, from ../max_flow.h
// - SimpleMinCostFlow, from ../min_cost_flow.h
// - LinearSumAssignment, from ../assignment.h
//
// USAGE EXAMPLES:
// - examples/python/pyflow_example.py
// - examples/python/linear_assignment_api.py
//
// The rest of the code of ../ was not deemed worth exporting to python.
// This could change; contact the code owners if you would like some C++
// API to be exposed here.
//
// TODO(user): test all the APIs that are currently marked as 'untested'.
//swiglint: disable full-signature
%include "ortools/base/base.i"
%include "ortools/util/python/functions.i"
%import "ortools/util/python/vector.i"
%import "ortools/graph/ebert_graph.h"
// Convert the "std::vector<int>* result" parameters to python outputs.
// Note: the typemap itself is in "ortools/base/base.i".
%apply std::vector<int>* OUTPUT {std::vector<int>* result}
%apply std::vector<int>* OUTPUT {std::vector<int>* nodes}
%{
#include "ortools/graph/assignment.h"
#include "ortools/graph/max_flow.h"
#include "ortools/graph/min_cost_flow.h"
#include "ortools/graph/shortestpaths.h"
%}
// ############ max_flow.h ############
%ignoreall
%unignore operations_research;
%unignore operations_research::SimpleMaxFlow;
%unignore operations_research::SimpleMaxFlow::SimpleMaxFlow;
%unignore operations_research::SimpleMaxFlow::~SimpleMaxFlow;
%unignore operations_research::SimpleMaxFlow::AddArcWithCapacity;
%unignore operations_research::SimpleMaxFlow::SetArcCapacity; // untested
%unignore operations_research::SimpleMaxFlow::NumNodes; // untested
%unignore operations_research::SimpleMaxFlow::NumArcs;
%unignore operations_research::SimpleMaxFlow::Tail;
%unignore operations_research::SimpleMaxFlow::Head;
%unignore operations_research::SimpleMaxFlow::Capacity;
// Expose the "operations_research::SimpleMaxFlow::Status" enum.
%unignore operations_research::SimpleMaxFlow::Status;
%unignore operations_research::SimpleMaxFlow::OPTIMAL;
%unignore operations_research::SimpleMaxFlow::POSSIBLE_OVERFLOW; // untested
%unignore operations_research::SimpleMaxFlow::BAD_INPUT; // untested
%unignore operations_research::SimpleMaxFlow::BAD_RESULT; // untested
%unignore operations_research::SimpleMaxFlow::Solve;
%unignore operations_research::SimpleMaxFlow::OptimalFlow;
%unignore operations_research::SimpleMaxFlow::Flow;
%unignore operations_research::SimpleMaxFlow::GetSourceSideMinCut; // untested
%unignore operations_research::SimpleMaxFlow::GetSinkSideMinCut; // untested
%include "ortools/graph/max_flow.h"
%unignoreall
// ############ min_cost_flow.h ############
%ignoreall
%unignore operations_research;
// We prefer our users to access the Status enum via the "SimpleMinCostFlow"
// class, i.e. SimpleMinCostFlow.OPTIMAL. But since they're defined on a
// base class in the .h, we must expose it.
%unignore operations_research::MinCostFlowBase;
%unignore operations_research::MinCostFlowBase::Status;
%unignore operations_research::MinCostFlowBase::OPTIMAL;
%unignore operations_research::MinCostFlowBase::NOT_SOLVED; // untested
%unignore operations_research::MinCostFlowBase::FEASIBLE; // untested
%unignore operations_research::MinCostFlowBase::INFEASIBLE; // untested
%unignore operations_research::MinCostFlowBase::UNBALANCED; // untested
%unignore operations_research::MinCostFlowBase::BAD_RESULT; // untested
%unignore operations_research::MinCostFlowBase::BAD_COST_RANGE; // untested
%unignore operations_research::SimpleMinCostFlow;
%unignore operations_research::SimpleMinCostFlow::SimpleMinCostFlow;
%unignore operations_research::SimpleMinCostFlow::~SimpleMinCostFlow;
%unignore operations_research::SimpleMinCostFlow::AddArcWithCapacityAndUnitCost;
%unignore operations_research::SimpleMinCostFlow::SetNodeSupply;
%unignore operations_research::SimpleMinCostFlow::Solve;
// untested
%unignore operations_research::SimpleMinCostFlow::SolveMaxFlowWithMinCost;
%unignore operations_research::SimpleMinCostFlow::OptimalCost;
%unignore operations_research::SimpleMinCostFlow::MaximumFlow; // untested
%unignore operations_research::SimpleMinCostFlow::Flow;
%unignore operations_research::SimpleMinCostFlow::NumNodes; // untested
%unignore operations_research::SimpleMinCostFlow::NumArcs;
%unignore operations_research::SimpleMinCostFlow::Tail;
%unignore operations_research::SimpleMinCostFlow::Head;
%unignore operations_research::SimpleMinCostFlow::Capacity; // untested
%unignore operations_research::SimpleMinCostFlow::Supply; // untested
%unignore operations_research::SimpleMinCostFlow::UnitCost;
%include "ortools/graph/min_cost_flow.h"
%unignoreall
// ############ assignment.h ############
%ignoreall
%unignore operations_research;
// We only expose the C++ "operations_research::SimpleLinearSumAssignment"
// class, and we rename it "LinearSumAssignment".
%rename(LinearSumAssignment) operations_research::SimpleLinearSumAssignment;
%unignore operations_research::SimpleLinearSumAssignment::SimpleLinearSumAssignment;
%unignore operations_research::SimpleLinearSumAssignment::~SimpleLinearSumAssignment;
%unignore operations_research::SimpleLinearSumAssignment::AddArcWithCost;
%unignore operations_research::SimpleLinearSumAssignment::NumNodes;
%unignore operations_research::SimpleLinearSumAssignment::NumArcs; // untested
%unignore operations_research::SimpleLinearSumAssignment::LeftNode; // untested
%unignore operations_research::SimpleLinearSumAssignment::RightNode; // untested
%unignore operations_research::SimpleLinearSumAssignment::Cost; // untested
%unignore operations_research::SimpleLinearSumAssignment::Status;
%unignore operations_research::SimpleLinearSumAssignment::OPTIMAL;
%unignore operations_research::SimpleLinearSumAssignment::INFEASIBLE;
%unignore operations_research::SimpleLinearSumAssignment::POSSIBLE_OVERFLOW;
%unignore operations_research::SimpleLinearSumAssignment::Solve;
%unignore operations_research::SimpleLinearSumAssignment::OptimalCost;
%unignore operations_research::SimpleLinearSumAssignment::RightMate;
%unignore operations_research::SimpleLinearSumAssignment::AssignmentCost;
%include "ortools/graph/assignment.h"
%unignoreall
// ############ shortestpaths.h ############
%ignoreall
%unignore operations_research;
%unignore operations_research::DijkstraShortestPath;
%unignore operations_research::BellmanFordShortestPath;
%unignore operations_research::AStarShortestPath;
%include "ortools/graph/shortestpaths.h"
%unignoreall

View File

@@ -0,0 +1,46 @@
// Copyright 2010-2021 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/graph/assignment.h"
#include "pybind11/pybind11.h"
#include "pybind11/stl.h"
using ::operations_research::NodeIndex;
using ::operations_research::SimpleLinearSumAssignment;
using ::pybind11::arg;
PYBIND11_MODULE(linear_sum_assignment, m) {
pybind11::class_<SimpleLinearSumAssignment> slsa(m,
"SimpleLinearSumAssignment");
slsa.def(pybind11::init<>());
slsa.def("add_arc_with_cost", &SimpleLinearSumAssignment::AddArcWithCost,
arg("left_node"), arg("right_node"), arg("cost"));
slsa.def("num_nodes", &SimpleLinearSumAssignment::NumNodes);
slsa.def("num_arcs", &SimpleLinearSumAssignment::NumArcs);
slsa.def("left_node", &SimpleLinearSumAssignment::LeftNode, arg("arc"));
slsa.def("right_node", &SimpleLinearSumAssignment::RightNode, arg("arc"));
slsa.def("cost", &SimpleLinearSumAssignment::Cost, arg("arc"));
slsa.def("solve", &SimpleLinearSumAssignment::Solve);
slsa.def("optimal_cost", &SimpleLinearSumAssignment::OptimalCost);
slsa.def("right_mate", &SimpleLinearSumAssignment::RightMate,
arg("left_node"));
slsa.def("assignment_cost", &SimpleLinearSumAssignment::AssignmentCost,
arg("left_node"));
pybind11::enum_<SimpleLinearSumAssignment::Status>(slsa, "Status")
.value("OPTIMAL", SimpleLinearSumAssignment::Status::OPTIMAL)
.value("INFEASIBLE", SimpleLinearSumAssignment::Status::INFEASIBLE)
.value("POSSIBLE_OVERFLOW",
SimpleLinearSumAssignment::Status::POSSIBLE_OVERFLOW)
.export_values();
}

View File

@@ -0,0 +1,55 @@
// Copyright 2010-2021 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/graph/max_flow.h"
#include "pybind11/pybind11.h"
#include "pybind11/stl.h"
using ::operations_research::NodeIndex;
using ::operations_research::SimpleMaxFlow;
using ::pybind11::arg;
PYBIND11_MODULE(max_flow, m) {
pybind11::class_<SimpleMaxFlow> smf(m, "SimpleMaxFlow");
smf.def(pybind11::init<>());
smf.def("add_arc_with_capacity", &SimpleMaxFlow::AddArcWithCapacity,
arg("tail"), arg("head"), arg("capacity"));
smf.def("set_arc_capacity", &SimpleMaxFlow::SetArcCapacity, arg("arc"),
arg("capacity"));
smf.def("num_nodes", &SimpleMaxFlow::NumNodes);
smf.def("num_arcs", &SimpleMaxFlow::NumArcs);
smf.def("tail", &SimpleMaxFlow::Tail, arg("arc"));
smf.def("head", &SimpleMaxFlow::Head, arg("arc"));
smf.def("capacity", &SimpleMaxFlow::Capacity, arg("arc"));
smf.def("solve", &SimpleMaxFlow::Solve, arg("source"), arg("sink"));
smf.def("optimal_flow", &SimpleMaxFlow::OptimalFlow);
smf.def("flow", &SimpleMaxFlow::Flow, arg("arc"));
smf.def("get_source_side_min_cut", [](SimpleMaxFlow* smf) {
std::vector<NodeIndex> result;
smf->GetSourceSideMinCut(&result);
return result;
});
smf.def("get_sink_side_min_cut", [](SimpleMaxFlow* smf) {
std::vector<NodeIndex> result;
smf->GetSinkSideMinCut(&result);
return result;
});
pybind11::enum_<SimpleMaxFlow::Status>(smf, "Status")
.value("OPTIMAL", SimpleMaxFlow::Status::OPTIMAL)
.value("POSSIBLE_OVERFLOW", SimpleMaxFlow::Status::POSSIBLE_OVERFLOW)
.value("BAD_INPUT", SimpleMaxFlow::Status::BAD_INPUT)
.value("BAD_RESULT", SimpleMaxFlow::Status::BAD_RESULT)
.export_values();
}

View File

@@ -0,0 +1,54 @@
// Copyright 2010-2021 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/graph/min_cost_flow.h"
#include "pybind11/pybind11.h"
#include "pybind11/stl.h"
using ::operations_research::MinCostFlowBase;
using ::operations_research::SimpleMinCostFlow;
using ::pybind11::arg;
PYBIND11_MODULE(min_cost_flow, m) {
pybind11::class_<SimpleMinCostFlow> smcf(m, "SimpleMinCostFlow");
smcf.def(pybind11::init<>());
smcf.def("add_arc_with_capacity_and_unit_cost",
&SimpleMinCostFlow::AddArcWithCapacityAndUnitCost, arg("tail"),
arg("head"), arg("capacity"), arg("unit_cost"));
smcf.def("set_node_supply", &SimpleMinCostFlow::SetNodeSupply, arg("node"),
arg("supply"));
smcf.def("num_nodes", &SimpleMinCostFlow::NumNodes);
smcf.def("num_arcs", &SimpleMinCostFlow::NumArcs);
smcf.def("tail", &SimpleMinCostFlow::Tail, arg("arc"));
smcf.def("head", &SimpleMinCostFlow::Head, arg("arc"));
smcf.def("capacity", &SimpleMinCostFlow::Capacity, arg("arc"));
smcf.def("supply", &SimpleMinCostFlow::Supply, arg("node"));
smcf.def("unit_cost", &SimpleMinCostFlow::UnitCost, arg("arc"));
smcf.def("solve", &SimpleMinCostFlow::Solve);
smcf.def("solve_max_flow_with_min_cost",
&SimpleMinCostFlow::SolveMaxFlowWithMinCost);
smcf.def("optimal_cost", &SimpleMinCostFlow::OptimalCost);
smcf.def("maximum_flow", &SimpleMinCostFlow::MaximumFlow);
smcf.def("flow", &SimpleMinCostFlow::Flow, arg("arc"));
pybind11::enum_<SimpleMinCostFlow::Status>(smcf, "Status")
.value("BAD_COST_RANGE", MinCostFlowBase::Status::BAD_COST_RANGE)
.value("BAD_RESULT", MinCostFlowBase::Status::BAD_RESULT)
.value("FEASIBLE", MinCostFlowBase::Status::FEASIBLE)
.value("INFEASIBLE", MinCostFlowBase::Status::INFEASIBLE)
.value("NOT_SOLVED", MinCostFlowBase::Status::NOT_SOLVED)
.value("OPTIMAL", MinCostFlowBase::Status::OPTIMAL)
.value("UNBALANCED", MinCostFlowBase::Status::UNBALANCED)
.export_values();
}

View File

@@ -1,11 +1,11 @@
load(":code_samples.bzl", "code_sample_cc")
load(":code_samples.bzl", "code_sample_cc_py")
code_sample_cc(name = "assignment_linear_sum_assignment")
code_sample_cc_py(name = "assignment_linear_sum_assignment")
code_sample_cc(name = "assignment_min_flow")
code_sample_cc_py(name = "assignment_min_flow")
code_sample_cc(name = "balance_min_flow")
code_sample_cc_py(name = "balance_min_flow")
code_sample_cc(name = "simple_max_flow_program")
code_sample_cc_py(name = "simple_max_flow_program")
code_sample_cc(name = "simple_min_cost_flow_program")
code_sample_cc_py(name = "simple_min_cost_flow_program")

View File

@@ -14,14 +14,14 @@
# [START program]
"""Solve assignment problem using linear assignment solver."""
# [START import]
from ortools.graph import pywrapgraph
from ortools.graph.python import linear_sum_assignment
# [END import]
def main():
"""Linear Sum Assignment example."""
# [START solver]
assignment = pywrapgraph.LinearSumAssignment()
assignment = linear_sum_assignment.SimpleLinearSumAssignment()
# [END solver]
# [START data]
@@ -39,19 +39,19 @@ def main():
for worker in range(num_workers):
for task in range(num_tasks):
if costs[worker][task]:
assignment.AddArcWithCost(worker, task, costs[worker][task])
assignment.add_arc_with_cost(worker, task, costs[worker][task])
# [END constraints]
# [START solve]
status = assignment.Solve()
status = assignment.solve()
# [END solve]
# [START print_solution]
if status == assignment.OPTIMAL:
print(f'Total cost = {assignment.OptimalCost()}\n')
for i in range(0, assignment.NumNodes()):
print(f'Worker {i} assigned to task {assignment.RightMate(i)}.' +
f' Cost = {assignment.AssignmentCost(i)}')
print(f'Total cost = {assignment.optimal_cost()}\n')
for i in range(0, assignment.num_nodes()):
print(f'Worker {i} assigned to task {assignment.right_mate(i)}.' +
f' Cost = {assignment.assignment_cost(i)}')
elif status == assignment.INFEASIBLE:
print('No assignment is possible.')
elif status == assignment.POSSIBLE_OVERFLOW:

View File

@@ -14,7 +14,7 @@
# [START program]
"""Linear assignment example."""
# [START import]
from ortools.graph import pywrapgraph
from ortools.graph.python import min_cost_flow
# [END import]
@@ -22,7 +22,7 @@ def main():
"""Solving an Assignment Problem with MinCostFlow."""
# [START solver]
# Instantiate a SimpleMinCostFlow solver.
min_cost_flow = pywrapgraph.SimpleMinCostFlow()
smcf = min_cost_flow.SimpleMinCostFlow()
# [END solver]
# [START data]
@@ -49,34 +49,31 @@ def main():
# [START constraints]
# Add each arc.
for i in range(len(start_nodes)):
min_cost_flow.AddArcWithCapacityAndUnitCost(start_nodes[i],
end_nodes[i], capacities[i],
costs[i])
smcf.add_arc_with_capacity_and_unit_cost(start_nodes[i], end_nodes[i],
capacities[i], costs[i])
# Add node supplies.
for i in range(len(supplies)):
min_cost_flow.SetNodeSupply(i, supplies[i])
smcf.set_node_supply(i, supplies[i])
# [END constraints]
# [START solve]
# Find the minimum cost flow between node 0 and node 10.
status = min_cost_flow.Solve()
status = smcf.solve()
# [END solve]
# [START print_solution]
if status == min_cost_flow.OPTIMAL:
print('Total cost = ', min_cost_flow.OptimalCost())
if status == smcf.OPTIMAL:
print('Total cost = ', smcf.optimal_cost())
print()
for arc in range(min_cost_flow.NumArcs()):
for arc in range(smcf.num_arcs()):
# Can ignore arcs leading out of source or into sink.
if min_cost_flow.Tail(arc) != source and min_cost_flow.Head(
arc) != sink:
if smcf.tail(arc) != source and smcf.head(arc) != sink:
# Arcs in the solution have a flow value of 1. Their start and end nodes
# give an assignment of worker to task.
if min_cost_flow.Flow(arc) > 0:
if smcf.flow(arc) > 0:
print('Worker %d assigned to task %d. Cost = %d' %
(min_cost_flow.Tail(arc), min_cost_flow.Head(arc),
min_cost_flow.UnitCost(arc)))
(smcf.tail(arc), smcf.head(arc), smcf.unit_cost(arc)))
else:
print('There was an issue with the min cost flow input.')
print(f'Status: {status}')

View File

@@ -14,14 +14,14 @@
# [START program]
"""Assignment with teams of workers."""
# [START import]
from ortools.graph import pywrapgraph
from ortools.graph.python import min_cost_flow
# [END import]
def main():
"""Solving an Assignment with teams of worker."""
# [START solver]
min_cost_flow = pywrapgraph.SimpleMinCostFlow()
smcf = min_cost_flow.SimpleMinCostFlow()
# [END solver]
# [START data]
@@ -54,38 +54,34 @@ def main():
# [START constraints]
# Add each arc.
for i in range(0, len(start_nodes)):
min_cost_flow.AddArcWithCapacityAndUnitCost(start_nodes[i],
end_nodes[i], capacities[i],
costs[i])
smcf.add_arc_with_capacity_and_unit_cost(start_nodes[i], end_nodes[i],
capacities[i], costs[i])
# Add node supplies.
for i in range(0, len(supplies)):
min_cost_flow.SetNodeSupply(i, supplies[i])
smcf.set_node_supply(i, supplies[i])
# [END constraints]
# [START solve]
# Find the minimum cost flow between node 0 and node 10.
status = min_cost_flow.Solve()
status = smcf.solve()
# [END solve]
# [START print_solution]
if status == min_cost_flow.OPTIMAL:
min_cost_flow.Solve()
print('Total cost = ', min_cost_flow.OptimalCost())
if status == smcf.OPTIMAL:
smcf.solve()
print('Total cost = ', smcf.optimal_cost())
print()
for arc in range(min_cost_flow.NumArcs()):
for arc in range(smcf.num_arcs()):
# Can ignore arcs leading out of source or intermediate, or into sink.
if (min_cost_flow.Tail(arc) != source and
min_cost_flow.Tail(arc) != 11 and
min_cost_flow.Tail(arc) != 12 and
min_cost_flow.Head(arc) != sink):
if (smcf.tail(arc) != source and smcf.tail(arc) != 11 and
smcf.tail(arc) != 12 and smcf.head(arc) != sink):
# Arcs in the solution will have a flow value of 1.
# There start and end nodes give an assignment of worker to task.
if min_cost_flow.Flow(arc) > 0:
if smcf.flow(arc) > 0:
print('Worker %d assigned to task %d. Cost = %d' %
(min_cost_flow.Tail(arc), min_cost_flow.Head(arc),
min_cost_flow.UnitCost(arc)))
(smcf.tail(arc), smcf.head(arc), smcf.unit_cost(arc)))
else:
print('There was an issue with the min cost flow input.')
print(f'Status: {status}')

View File

@@ -1,32 +1,60 @@
"""Helper macro to compile and test code samples."""
def code_sample_cc(name):
native.cc_binary(
name = name,
srcs = [name + ".cc"],
deps = [
"//ortools/base",
"//ortools/graph:assignment",
"//ortools/graph:ebert_graph",
"//ortools/graph:linear_assignment",
"//ortools/graph:max_flow",
"//ortools/graph:min_cost_flow",
"//ortools/graph:shortestpaths",
],
)
load("@rules_python//python:defs.bzl", "py_binary")
load("@ortools_deps//:requirements.bzl", "requirement")
native.cc_test(
name = name+"_test",
size = "small",
srcs = [name + ".cc"],
deps = [
":"+name,
"//ortools/base",
"//ortools/graph:assignment",
"//ortools/graph:ebert_graph",
"//ortools/graph:linear_assignment",
"//ortools/graph:max_flow",
"//ortools/graph:min_cost_flow",
"//ortools/graph:shortestpaths",
],
)
def code_sample_cc(name):
native.cc_binary(
name = name + "_cc",
srcs = [name + ".cc"],
deps = [
"//ortools/base",
"//ortools/graph:assignment",
"//ortools/graph:ebert_graph",
"//ortools/graph:linear_assignment",
"//ortools/graph:max_flow",
"//ortools/graph:min_cost_flow",
"//ortools/graph:shortestpaths",
],
)
native.sh_test(
name = name + "_cc_test",
size = "small",
srcs = ["code_samples_cc_test.sh"],
args = [name],
data = [
":" + name + "_cc",
],
)
def code_sample_py(name):
py_binary(
name = name + "_py3",
srcs = [name + ".py"],
main = name + ".py",
data = [
"//ortools/graph/python:linear_sum_assignment.so",
"//ortools/graph/python:min_cost_flow.so",
"//ortools/graph/python:max_flow.so",
],
deps = [
requirement("absl-py"),
],
python_version = "PY3",
srcs_version = "PY3",
)
native.sh_test(
name = name + "_py_test",
size = "small",
srcs = ["code_samples_py_test.sh"],
args = [name],
data = [
":" + name + "_py3",
],
)
def code_sample_cc_py(name):
code_sample_cc(name = name)
code_sample_py(name = name)

View File

@@ -0,0 +1,9 @@
#!/bin/bash
declare -r DIR="${TEST_SRCDIR}/com_google_ortools/ortools/graph/samples"
function test::ortools::code_samples_graph_cc() {
"${DIR}/$1_cc"
}
test::ortools::code_samples_graph_cc $1

View File

@@ -0,0 +1,9 @@
#!/bin/bash
declare -r DIR="${TEST_SRCDIR}/com_google_ortools/ortools/graph/samples"
function test::ortools::code_samples_graph_py() {
"${DIR}/$1_py3"
}
test::ortools::code_samples_graph_py $1

View File

@@ -14,7 +14,7 @@
# [START program]
"""From Taha 'Introduction to Operations Research', example 6.4-2."""
# [START import]
from ortools.graph import pywrapgraph
from ortools.graph.python import max_flow
# [END import]
@@ -22,7 +22,7 @@ def main():
"""MaxFlow simple interface example."""
# [START solver]
# Instantiate a SimpleMaxFlow solver.
max_flow = pywrapgraph.SimpleMaxFlow()
smf = max_flow.SimpleMaxFlow()
# [END solver]
# [START data]
@@ -37,28 +37,27 @@ def main():
# [START constraints]
# Add each arc.
for arc in zip(start_nodes, end_nodes, capacities):
max_flow.AddArcWithCapacity(arc[0], arc[1], arc[2])
smf.add_arc_with_capacity(arc[0], arc[1], arc[2])
# [END constraints]
# [START solve]
# Find the maximum flow between node 0 and node 4.
status = max_flow.Solve(0, 4)
status = smf.solve(0, 4)
# [END solve]
# [START print_solution]
if status != max_flow.OPTIMAL:
if status != smf.OPTIMAL:
print('There was an issue with the max flow input.')
print(f'Status: {status}')
exit(1)
print('Max flow:', max_flow.OptimalFlow())
print('Max flow:', smf.optimal_flow())
print('')
print(' Arc Flow / Capacity')
for i in range(max_flow.NumArcs()):
for i in range(smf.num_arcs()):
print('%1s -> %1s %3s / %3s' %
(max_flow.Tail(i), max_flow.Head(i), max_flow.Flow(i),
max_flow.Capacity(i)))
print('Source side min-cut:', max_flow.GetSourceSideMinCut())
print('Sink side min-cut:', max_flow.GetSinkSideMinCut())
(smf.tail(i), smf.head(i), smf.flow(i), smf.capacity(i)))
print('Source side min-cut:', smf.get_source_side_min_cut())
print('Sink side min-cut:', smf.get_sink_side_min_cut())
# [END print_solution]

View File

@@ -14,7 +14,7 @@
# [START program]
"""From Bradley, Hax and Maganti, 'Applied Mathematical Programming', figure 8.1."""
# [START import]
from ortools.graph import pywrapgraph
from ortools.graph.python import min_cost_flow
# [END import]
@@ -22,7 +22,7 @@ def main():
"""MinCostFlow simple interface example."""
# [START solver]
# Instantiate a SimpleMinCostFlow solver.
min_cost_flow = pywrapgraph.SimpleMinCostFlow()
smcf = min_cost_flow.SimpleMinCostFlow()
# [END solver]
# [START data]
@@ -41,32 +41,31 @@ def main():
# [START constraints]
# Add each arc.
for arc in zip(start_nodes, end_nodes, capacities, unit_costs):
min_cost_flow.AddArcWithCapacityAndUnitCost(arc[0], arc[1], arc[2],
arc[3])
smcf.add_arc_with_capacity_and_unit_cost(arc[0], arc[1], arc[2], arc[3])
# Add node supply.
for count, supply in enumerate(supplies):
min_cost_flow.SetNodeSupply(count, supply)
smcf.set_node_supply(count, supply)
# [END constraints]
# [START solve]
# Find the min cost flow.
status = min_cost_flow.Solve()
status = smcf.solve()
# [END solve]
# [START print_solution]
if status != min_cost_flow.OPTIMAL:
if status != smcf.OPTIMAL:
print('There was an issue with the min cost flow input.')
print(f'Status: {status}')
exit(1)
print('Minimum cost: ', min_cost_flow.OptimalCost())
print('Minimum cost: ', smcf.optimal_cost())
print('')
print(' Arc Flow / Capacity Cost')
for i in range(min_cost_flow.NumArcs()):
cost = min_cost_flow.Flow(i) * min_cost_flow.UnitCost(i)
print('%1s -> %1s %3s / %3s %3s' %
(min_cost_flow.Tail(i), min_cost_flow.Head(i),
min_cost_flow.Flow(i), min_cost_flow.Capacity(i), cost))
for i in range(smcf.num_arcs()):
cost = smcf.flow(i) * smcf.unit_cost(i)
print(
'%1s -> %1s %3s / %3s %3s' %
(smcf.tail(i), smcf.head(i), smcf.flow(i), smcf.capacity(i), cost))
# [END print_solution]