diff --git a/ortools/algorithms/python/knapsack_solver_test.py b/ortools/algorithms/python/knapsack_solver_test.py index 39f153ec56..8809980ca3 100755 --- a/ortools/algorithms/python/knapsack_solver_test.py +++ b/ortools/algorithms/python/knapsack_solver_test.py @@ -22,6 +22,7 @@ from ortools.algorithms.python import knapsack_solver class PyWrapAlgorithmsKnapsackSolverTest(absltest.TestCase): + def RealSolve(self, profits, weights, capacities, solver_type, use_reduction): solver = knapsack_solver.KnapsackSolver(solver_type, "solver") solver.set_use_reduction(use_reduction) diff --git a/ortools/graph/README.md b/ortools/graph/README.md index 59d10b728c..e8940279a7 100644 --- a/ortools/graph/README.md +++ b/ortools/graph/README.md @@ -6,8 +6,8 @@ network flow problems. It contains in particular: * 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). + [Hamiltonian paths](https://en.wikipedia.org/wiki/Hamiltonian_path)). +* hard-to-find algorithms (Hamiltonian paths, push-relabel flow algorithms). * other, more common algorithms, that are useful to use with `EbertGraph`. Graph representations: @@ -69,11 +69,11 @@ 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 the Goldberg-Kennedy push-relabel algorithm. + 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 the Goldberg-Tarjan - push-relabel algorithm. + 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 diff --git a/ortools/graph/max_flow_test.cc b/ortools/graph/max_flow_test.cc index 96de73f8e5..d08ebf7992 100644 --- a/ortools/graph/max_flow_test.cc +++ b/ortools/graph/max_flow_test.cc @@ -191,8 +191,8 @@ TEST(SimpleMaxFlowTest, ProblematicProblemWithMaxCapacity) { FlowModelProto model, ReadFileToProto( file::JoinPathRespectAbsolute(absl::GetFlag(FLAGS_test_srcdir), - "ortools/graph/" - "testdata/max_flow_test1.pb.txt"))); + "ortools/graph/" + "testdata/max_flow_test1.pb.txt"))); SimpleMaxFlow solver; EXPECT_EQ(SimpleMaxFlow::OPTIMAL, LoadAndSolveFlowModel(model, &solver)); EXPECT_EQ(10290243, solver.OptimalFlow()); diff --git a/ortools/graph/samples/assignment_min_flow.py b/ortools/graph/samples/assignment_min_flow.py index 42d775c3e8..22f9cd8e18 100755 --- a/ortools/graph/samples/assignment_min_flow.py +++ b/ortools/graph/samples/assignment_min_flow.py @@ -72,6 +72,7 @@ def main(): for arc in range(smcf.num_arcs()): # Can ignore arcs leading out of source or into 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 smcf.flow(arc) > 0: diff --git a/ortools/graph/samples/balance_min_flow.py b/ortools/graph/samples/balance_min_flow.py index 8a0c97a2e2..3c13128949 100755 --- a/ortools/graph/samples/balance_min_flow.py +++ b/ortools/graph/samples/balance_min_flow.py @@ -103,6 +103,7 @@ def main(): 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 smcf.flow(arc) > 0: diff --git a/ortools/graph/testdata/BUILD.bazel b/ortools/graph/testdata/BUILD.bazel index d7fbe3ea0c..b1ebc2079f 100644 --- a/ortools/graph/testdata/BUILD.bazel +++ b/ortools/graph/testdata/BUILD.bazel @@ -12,5 +12,5 @@ # limitations under the License. exports_files([ - "max_flow_test1.pb.txt", + "max_flow_test1.pb.txt", ]) diff --git a/ortools/init/python/init_test.py b/ortools/init/python/init_test.py index 62060a96a1..7eb2402a91 100755 --- a/ortools/init/python/init_test.py +++ b/ortools/init/python/init_test.py @@ -19,6 +19,7 @@ from ortools.init.python import init class InitTest(absltest.TestCase): + def test_logging(self): print("test_logging") init.CppBridge.init_logging("pywrapinit_test.py") diff --git a/ortools/linear_solver/python/linear_solver_natural_api.py b/ortools/linear_solver/python/linear_solver_natural_api.py index 2d92391bd6..1332b79ac9 100644 --- a/ortools/linear_solver/python/linear_solver_natural_api.py +++ b/ortools/linear_solver/python/linear_solver_natural_api.py @@ -30,7 +30,7 @@ import numbers inf = float("inf") -class _FakeMPVariableRepresentingTheConstantOffset(object): +class _FakeMPVariableRepresentingTheConstantOffset: """A dummy class for a singleton instance used to represent the constant. To represent linear expressions, we store a dictionary @@ -56,7 +56,7 @@ def CastToLinExp(v): return v -class LinearExpr(object): +class LinearExpr: """Holds linear expressions. A linear expression is essentially an offset (floating-point value), and a @@ -178,6 +178,9 @@ class VariableExpr(LinearExpr): def __init__(self, mpvar): self.__var = mpvar + def __str__(self): + return str(self.__var) + def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack): coeffs[self.__var] += multiplier @@ -205,6 +208,7 @@ class ProductCst(LinearExpr): class Constant(LinearExpr): + def __init__(self, val): self.__val = val @@ -222,7 +226,16 @@ class SumArray(LinearExpr): self.__array = [CastToLinExp(elem) for elem in array] def __str__(self): - return "({})".format(" + ".join(map(str, self.__array))) + parts = [] + for term in map(str, self.__array): + if not parts: + parts.append(term) + continue + if term[0] == "-": + parts.append(" - " + term[1:]) + else: + parts.append(" + " + term) + return f'({"".join(parts)})' def AddSelfToCoeffMapOrStack(self, coeffs, multiplier, stack): # Append elements in reversed order so that the first popped from the stack @@ -240,7 +253,7 @@ def Sum(*args): SumCst = Sum # pylint: disable=invalid-name -class LinearConstraint(object): +class LinearConstraint: """Represents a linear constraint: LowerBound <= LinearExpr <= UpperBound.""" def __init__(self, expr, lb, ub): diff --git a/ortools/linear_solver/python/model_builder.py b/ortools/linear_solver/python/model_builder.py index 24e78f94a4..715506616c 100644 --- a/ortools/linear_solver/python/model_builder.py +++ b/ortools/linear_solver/python/model_builder.py @@ -883,12 +883,10 @@ class Model: return clone @typing.overload - def _get_linear_constraints(self, constraints: Optional[pd.Index]) -> pd.Index: - ... + def _get_linear_constraints(self, constraints: Optional[pd.Index]) -> pd.Index: ... @typing.overload - def _get_linear_constraints(self, constraints: pd.Series) -> pd.Series: - ... + def _get_linear_constraints(self, constraints: pd.Series) -> pd.Series: ... def _get_linear_constraints( self, constraints: Optional[_IndexOrSeries] = None @@ -898,12 +896,10 @@ class Model: return constraints @typing.overload - def _get_variables(self, variables: Optional[pd.Index]) -> pd.Index: - ... + def _get_variables(self, variables: Optional[pd.Index]) -> pd.Index: ... @typing.overload - def _get_variables(self, variables: pd.Series) -> pd.Series: - ... + def _get_variables(self, variables: pd.Series) -> pd.Series: ... def _get_variables( self, variables: Optional[_IndexOrSeries] = None diff --git a/ortools/linear_solver/python/model_builder_helper_test.py b/ortools/linear_solver/python/model_builder_helper_test.py index ab6ff00ea4..a2b143a392 100644 --- a/ortools/linear_solver/python/model_builder_helper_test.py +++ b/ortools/linear_solver/python/model_builder_helper_test.py @@ -27,6 +27,7 @@ from ortools.linear_solver.python import model_builder_helper class PywrapModelBuilderHelperTest(absltest.TestCase): + def test_export_model_proto_to_mps_string(self): model = model_builder_helper.ModelBuilderHelper() model.set_name("testmodel") diff --git a/ortools/linear_solver/python/model_builder_test.py b/ortools/linear_solver/python/model_builder_test.py index 32d1c2d640..fa070b6cbe 100644 --- a/ortools/linear_solver/python/model_builder_test.py +++ b/ortools/linear_solver/python/model_builder_test.py @@ -421,6 +421,7 @@ ENDATA class InternalHelperTest(absltest.TestCase): + def test_anonymous_variables(self): helper = mb.Model().helper index = helper.add_var() @@ -435,6 +436,7 @@ class InternalHelperTest(absltest.TestCase): class LinearBaseTest(parameterized.TestCase): + def setUp(self): super().setUp() simple_model = mb.Model() @@ -615,6 +617,7 @@ class LinearBaseTest(parameterized.TestCase): class LinearBaseErrorsTest(absltest.TestCase): + def test_unknown_linear_type(self): with self.assertRaisesRegex(TypeError, r"Unrecognized linear expression"): @@ -637,6 +640,7 @@ class LinearBaseErrorsTest(absltest.TestCase): class BoundedLinearBaseTest(parameterized.TestCase): + def setUp(self): super().setUp() simple_model = mb.Model() @@ -730,6 +734,7 @@ class BoundedLinearBaseTest(parameterized.TestCase): class BoundedLinearBaseErrorsTest(absltest.TestCase): + def test_bounded_linear_expression_as_bool(self): with self.assertRaisesRegex(NotImplementedError, "Boolean value"): model = mb.Model() @@ -738,6 +743,7 @@ class BoundedLinearBaseErrorsTest(absltest.TestCase): class ModelBuilderErrorsTest(absltest.TestCase): + def test_new_var_series_errors(self): with self.assertRaisesRegex(TypeError, r"Non-index object"): model = mb.Model() @@ -1566,6 +1572,7 @@ class ModelBuilderObjectiveTest(parameterized.TestCase): class ModelBuilderProtoTest(absltest.TestCase): + def test_export_to_proto(self): expected = linear_solver_pb2.MPModelProto() text_format.Parse( diff --git a/ortools/linear_solver/python/pywraplp_test.py b/ortools/linear_solver/python/pywraplp_test.py index a683d4be0c..e0633e390f 100644 --- a/ortools/linear_solver/python/pywraplp_test.py +++ b/ortools/linear_solver/python/pywraplp_test.py @@ -42,6 +42,7 @@ constraint { class PyWrapLp(unittest.TestCase): + def test_proto(self): input_proto = linear_solver_pb2.MPModelProto() text_format.Merge(TEXT_MODEL, input_proto) diff --git a/ortools/linear_solver/solve.cc b/ortools/linear_solver/solve.cc index e135ccc19c..7069b5e1f0 100644 --- a/ortools/linear_solver/solve.cc +++ b/ortools/linear_solver/solve.cc @@ -51,6 +51,7 @@ #include #include +#include #include #include @@ -78,9 +79,11 @@ ABSL_FLAG(std::string, input, "", "REQUIRED: Input file name."); ABSL_FLAG(std::string, sol_hint, "", "Input file name with solution in .sol format."); -ABSL_FLAG(std::string, solver, "glop", +ABSL_FLAG(std::optional, solver, std::nullopt, "The solver to use: bop, cbc, clp, glop, glpk_lp, glpk_mip, " - "gurobi_lp, gurobi_mip, pdlp, scip, knapsack, sat."); + "gurobi_lp, gurobi_mip, pdlp, scip, knapsack, sat. If unspecified " + "either use MPModelRequest.solver_type if the --input is an " + "MPModelRequest and the field is set or use glop."); ABSL_FLAG(int, num_threads, 1, "Number of threads to use by the underlying solver."); ABSL_FLAG(std::string, params_file, "", @@ -263,9 +266,15 @@ void Run() { QCHECK_GE(absl::GetFlag(FLAGS_time_limit), absl::ZeroDuration()) << "--time_limit must be given a positive duration"; - MPSolver::OptimizationProblemType type; - CHECK(MPSolver::ParseSolverType(absl::GetFlag(FLAGS_solver), &type)) - << "Unsupported --solver: " << absl::GetFlag(FLAGS_solver); + // Parses --solver if set. + std::optional type; + if (const std::optional type_flag = absl::GetFlag(FLAGS_solver); + type_flag.has_value()) { + MPSolver::OptimizationProblemType decoded_type; + QCHECK(MPSolver::ParseSolverType(type_flag.value(), &decoded_type)) + << "Unsupported --solver: " << type_flag.value(); + type = decoded_type; + } MPModelRequest request_proto = ReadMipModel(absl::GetFlag(FLAGS_input)); @@ -302,7 +311,10 @@ void Run() { } // Set or override request proto options from the command line flags. - request_proto.set_solver_type(static_cast(type)); + if (type.has_value() || !request_proto.has_solver_type()) { + request_proto.set_solver_type(static_cast( + type.value_or(MPSolver::GLOP_LINEAR_PROGRAMMING))); + } if (absl::GetFlag(FLAGS_time_limit) != absl::InfiniteDuration()) { LOG(INFO) << "Setting a time limit of " << absl::GetFlag(FLAGS_time_limit); request_proto.set_solver_time_limit_seconds( diff --git a/ortools/util/python/sorted_interval_list_test.py b/ortools/util/python/sorted_interval_list_test.py index 99f2887f61..b33eb7d878 100755 --- a/ortools/util/python/sorted_interval_list_test.py +++ b/ortools/util/python/sorted_interval_list_test.py @@ -19,6 +19,7 @@ from ortools.util.python import sorted_interval_list class SortedIntervalListTest(absltest.TestCase): + def testCtorAndGetter(self): bool_domain = sorted_interval_list.Domain(0, 1) self.assertEqual(2, bool_domain.size())