diff --git a/ortools/linear_solver/csharp/model_builder.i b/ortools/linear_solver/csharp/model_builder.i index d82b9f7893..69d752d49c 100644 --- a/ortools/linear_solver/csharp/model_builder.i +++ b/ortools/linear_solver/csharp/model_builder.i @@ -27,7 +27,7 @@ VECTOR_AS_CSHARP_ARRAY(double, double, double, DoubleVector); %module(directors="1") operations_research_model_builder -%extend operations_research::ModelBuilderHelper { +%extend operations_research::mb::ModelBuilderHelper { std::string ExportToMpsString(bool obfuscate) { operations_research::MPModelExportOptions options; options.obfuscate = obfuscate; @@ -45,136 +45,137 @@ VECTOR_AS_CSHARP_ARRAY(double, double, double, DoubleVector); options.obfuscate = obfuscate; return $self->WriteToMpsFile(filename, options); } -} // Extend operations_research::ModelBuilderHelper +} // Extend operations_research::mb::ModelBuilderHelper %ignoreall %unignore operations_research; +%unignore operations_research::mb; // Wrap the ModelBuilderHelper class. -%unignore operations_research::ModelBuilderHelper; -%unignore operations_research::ModelBuilderHelper::ModelBuilderHelper; -%unignore operations_research::ModelBuilderHelper::~ModelBuilderHelper; +%unignore operations_research::mb::ModelBuilderHelper; +%unignore operations_research::mb::ModelBuilderHelper::ModelBuilderHelper; +%unignore operations_research::mb::ModelBuilderHelper::~ModelBuilderHelper; // Var API. -%unignore operations_research::ModelBuilderHelper::AddVar; -%unignore operations_research::ModelBuilderHelper::VarIsIntegral; -%unignore operations_research::ModelBuilderHelper::VarLowerBound; -%unignore operations_research::ModelBuilderHelper::VarName; -%unignore operations_research::ModelBuilderHelper::VarObjectiveCoefficient; -%unignore operations_research::ModelBuilderHelper::VarUpperBound; -%unignore operations_research::ModelBuilderHelper::SetVarIntegrality; -%unignore operations_research::ModelBuilderHelper::SetVarLowerBound; -%unignore operations_research::ModelBuilderHelper::SetVarName; -%unignore operations_research::ModelBuilderHelper::SetVarObjectiveCoefficient; -%unignore operations_research::ModelBuilderHelper::SetVarUpperBound; +%unignore operations_research::mb::ModelBuilderHelper::AddVar; +%unignore operations_research::mb::ModelBuilderHelper::VarIsIntegral; +%unignore operations_research::mb::ModelBuilderHelper::VarLowerBound; +%unignore operations_research::mb::ModelBuilderHelper::VarName; +%unignore operations_research::mb::ModelBuilderHelper::VarObjectiveCoefficient; +%unignore operations_research::mb::ModelBuilderHelper::VarUpperBound; +%unignore operations_research::mb::ModelBuilderHelper::SetVarIntegrality; +%unignore operations_research::mb::ModelBuilderHelper::SetVarLowerBound; +%unignore operations_research::mb::ModelBuilderHelper::SetVarName; +%unignore operations_research::mb::ModelBuilderHelper::SetVarObjectiveCoefficient; +%unignore operations_research::mb::ModelBuilderHelper::SetVarUpperBound; // Linear Constraint API. -%unignore operations_research::ModelBuilderHelper::AddConstraintTerm; -%unignore operations_research::ModelBuilderHelper::AddLinearConstraint; -%unignore operations_research::ModelBuilderHelper::ClearConstraintTerms; -%unignore operations_research::ModelBuilderHelper::ConstraintCoefficients; -%unignore operations_research::ModelBuilderHelper::ConstraintLowerBound; -%unignore operations_research::ModelBuilderHelper::ConstraintName; -%unignore operations_research::ModelBuilderHelper::ConstraintUpperBound; -%unignore operations_research::ModelBuilderHelper::ConstraintVarIndices; -%unignore operations_research::ModelBuilderHelper::SafeAddConstraintTerm; -%unignore operations_research::ModelBuilderHelper::SetConstraintCoefficient; -%unignore operations_research::ModelBuilderHelper::SetConstraintLowerBound; -%unignore operations_research::ModelBuilderHelper::SetConstraintName; -%unignore operations_research::ModelBuilderHelper::SetConstraintUpperBound; +%unignore operations_research::mb::ModelBuilderHelper::AddConstraintTerm; +%unignore operations_research::mb::ModelBuilderHelper::AddLinearConstraint; +%unignore operations_research::mb::ModelBuilderHelper::ClearConstraintTerms; +%unignore operations_research::mb::ModelBuilderHelper::ConstraintCoefficients; +%unignore operations_research::mb::ModelBuilderHelper::ConstraintLowerBound; +%unignore operations_research::mb::ModelBuilderHelper::ConstraintName; +%unignore operations_research::mb::ModelBuilderHelper::ConstraintUpperBound; +%unignore operations_research::mb::ModelBuilderHelper::ConstraintVarIndices; +%unignore operations_research::mb::ModelBuilderHelper::SafeAddConstraintTerm; +%unignore operations_research::mb::ModelBuilderHelper::SetConstraintCoefficient; +%unignore operations_research::mb::ModelBuilderHelper::SetConstraintLowerBound; +%unignore operations_research::mb::ModelBuilderHelper::SetConstraintName; +%unignore operations_research::mb::ModelBuilderHelper::SetConstraintUpperBound; // Enforced Linear Constraints API. -%unignore operations_research::ModelBuilderHelper::AddEnforcedConstraintTerm; -%unignore operations_research::ModelBuilderHelper::AddEnforcedLinearConstraint; -%unignore operations_research::ModelBuilderHelper::ClearEnforcedConstraintTerms; -%unignore operations_research::ModelBuilderHelper::EnforcedConstraintCoefficients; -%unignore operations_research::ModelBuilderHelper::EnforcedConstraintLowerBound; -%unignore operations_research::ModelBuilderHelper::EnforcedConstraintName; -%unignore operations_research::ModelBuilderHelper::EnforcedConstraintUpperBound; -%unignore operations_research::ModelBuilderHelper::EnforcedConstraintVarIndices; -%unignore operations_research::ModelBuilderHelper::EnforcedIndicatorValue; -%unignore operations_research::ModelBuilderHelper::EnforcedIndicatorVariableIndex; -%unignore operations_research::ModelBuilderHelper::IsEnforcedConstraint; -%unignore operations_research::ModelBuilderHelper::SafeAddEnforcedConstraintTerm; -%unignore operations_research::ModelBuilderHelper::SetEnforcedConstraintCoefficient; -%unignore operations_research::ModelBuilderHelper::SetEnforcedConstraintLowerBound; -%unignore operations_research::ModelBuilderHelper::SetEnforcedConstraintName; -%unignore operations_research::ModelBuilderHelper::SetEnforcedConstraintUpperBound; -%unignore operations_research::ModelBuilderHelper::SetEnforcedIndicatorValue; -%unignore operations_research::ModelBuilderHelper::SetEnforcedIndicatorVariableIndex; +%unignore operations_research::mb::ModelBuilderHelper::AddEnforcedConstraintTerm; +%unignore operations_research::mb::ModelBuilderHelper::AddEnforcedLinearConstraint; +%unignore operations_research::mb::ModelBuilderHelper::ClearEnforcedConstraintTerms; +%unignore operations_research::mb::ModelBuilderHelper::EnforcedConstraintCoefficients; +%unignore operations_research::mb::ModelBuilderHelper::EnforcedConstraintLowerBound; +%unignore operations_research::mb::ModelBuilderHelper::EnforcedConstraintName; +%unignore operations_research::mb::ModelBuilderHelper::EnforcedConstraintUpperBound; +%unignore operations_research::mb::ModelBuilderHelper::EnforcedConstraintVarIndices; +%unignore operations_research::mb::ModelBuilderHelper::EnforcedIndicatorValue; +%unignore operations_research::mb::ModelBuilderHelper::EnforcedIndicatorVariableIndex; +%unignore operations_research::mb::ModelBuilderHelper::IsEnforcedConstraint; +%unignore operations_research::mb::ModelBuilderHelper::SafeAddEnforcedConstraintTerm; +%unignore operations_research::mb::ModelBuilderHelper::SetEnforcedConstraintCoefficient; +%unignore operations_research::mb::ModelBuilderHelper::SetEnforcedConstraintLowerBound; +%unignore operations_research::mb::ModelBuilderHelper::SetEnforcedConstraintName; +%unignore operations_research::mb::ModelBuilderHelper::SetEnforcedConstraintUpperBound; +%unignore operations_research::mb::ModelBuilderHelper::SetEnforcedIndicatorValue; +%unignore operations_research::mb::ModelBuilderHelper::SetEnforcedIndicatorVariableIndex; // Objective API. -%unignore operations_research::ModelBuilderHelper::ClearObjective; -%rename (Maximize) operations_research::ModelBuilderHelper::maximize; -%unignore operations_research::ModelBuilderHelper::SetMaximize; -%unignore operations_research::ModelBuilderHelper::ObjectiveOffset; -%unignore operations_research::ModelBuilderHelper::SetObjectiveOffset; +%unignore operations_research::mb::ModelBuilderHelper::ClearObjective; +%rename (Maximize) operations_research::mb::ModelBuilderHelper::maximize; +%unignore operations_research::mb::ModelBuilderHelper::SetMaximize; +%unignore operations_research::mb::ModelBuilderHelper::ObjectiveOffset; +%unignore operations_research::mb::ModelBuilderHelper::SetObjectiveOffset; // Hints -%unignore operations_research::ModelBuilderHelper::ClearHints; -%unignore operations_research::ModelBuilderHelper::AddHint; +%unignore operations_research::mb::ModelBuilderHelper::ClearHints; +%unignore operations_research::mb::ModelBuilderHelper::AddHint; // Model API. -%rename (VariablesCount) operations_research::ModelBuilderHelper::num_variables; -%rename (ConstraintsCount) operations_research::ModelBuilderHelper::num_constraints; -%rename (Name) operations_research::ModelBuilderHelper::name; -%unignore operations_research::ModelBuilderHelper::SetName; -%unignore operations_research::ModelBuilderHelper::ReadModelFromProtoFile; -%unignore operations_research::ModelBuilderHelper::WriteModelToProtoFile; -%unignore operations_research::ModelBuilderHelper::ImportFromMpsString; -%unignore operations_research::ModelBuilderHelper::ImportFromMpsFile; -%unignore operations_research::ModelBuilderHelper::ImportFromLpString; -%unignore operations_research::ModelBuilderHelper::ImportFromLpFile; -%unignore operations_research::ModelBuilderHelper::WriteToMpsFile(std::string, bool); -%unignore operations_research::ModelBuilderHelper::ExportToMpsString(bool); -%unignore operations_research::ModelBuilderHelper::ExportToLpString(bool); -%unignore operations_research::ModelBuilderHelper::OverwriteModel; +%rename (VariablesCount) operations_research::mb::ModelBuilderHelper::num_variables; +%rename (ConstraintsCount) operations_research::mb::ModelBuilderHelper::num_constraints; +%rename (Name) operations_research::mb::ModelBuilderHelper::name; +%unignore operations_research::mb::ModelBuilderHelper::SetName; +%unignore operations_research::mb::ModelBuilderHelper::ReadModelFromProtoFile; +%unignore operations_research::mb::ModelBuilderHelper::WriteModelToProtoFile; +%unignore operations_research::mb::ModelBuilderHelper::ImportFromMpsString; +%unignore operations_research::mb::ModelBuilderHelper::ImportFromMpsFile; +%unignore operations_research::mb::ModelBuilderHelper::ImportFromLpString; +%unignore operations_research::mb::ModelBuilderHelper::ImportFromLpFile; +%unignore operations_research::mb::ModelBuilderHelper::WriteToMpsFile(std::string, bool); +%unignore operations_research::mb::ModelBuilderHelper::ExportToMpsString(bool); +%unignore operations_research::mb::ModelBuilderHelper::ExportToLpString(bool); +%unignore operations_research::mb::ModelBuilderHelper::OverwriteModel; // Callbacks support. -%feature("director") operations_research::MbLogCallback; -%unignore operations_research::MbLogCallback; -%unignore operations_research::MbLogCallback::~MbLogCallback; -%unignore operations_research::MbLogCallback::NewMessage; +%feature("director") operations_research::mb::MbLogCallback; +%unignore operations_research::mb::MbLogCallback; +%unignore operations_research::mb::MbLogCallback::~MbLogCallback; +%unignore operations_research::mb::MbLogCallback::NewMessage; // Solver API. -%unignore operations_research::ModelSolverHelper; -%unignore operations_research::ModelSolverHelper::ModelSolverHelper(const std::string&); -%unignore operations_research::ModelSolverHelper::SolverIsSupported; -%unignore operations_research::ModelSolverHelper::Solve; -%unignore operations_research::ModelSolverHelper::InterruptSolve; -%rename (HasResponse) operations_research::ModelSolverHelper::has_response; -%rename (HasSolution) operations_research::ModelSolverHelper::has_solution; -%rename (Status) operations_research::ModelSolverHelper::status; -%rename (ObjectiveValue) operations_research::ModelSolverHelper::objective_value; -%rename (BestObjectiveBound) operations_research::ModelSolverHelper::best_objective_bound; -%rename (VariableValue) operations_research::ModelSolverHelper::variable_value; -%rename (ReducedCost) operations_research::ModelSolverHelper::reduced_cost; -%rename (DualValue) operations_research::ModelSolverHelper::dual_value; -%rename (Activity) operations_research::ModelSolverHelper::activity; -%rename (StatusString) operations_research::ModelSolverHelper::status_string; -%rename (WallTime) operations_research::ModelSolverHelper::wall_time; -%rename (UserTime) operations_research::ModelSolverHelper::user_time; -%unignore operations_research::ModelSolverHelper::EnableOutput; -%unignore operations_research::ModelSolverHelper::ClearLogCallback; -%unignore operations_research::ModelSolverHelper::SetLogCallbackFromDirectorClass; -%unignore operations_research::ModelSolverHelper::SetTimeLimitInSeconds; -%unignore operations_research::ModelSolverHelper::SetSolverSpecificParameters; +%unignore operations_research::mb::ModelSolverHelper; +%unignore operations_research::mb::ModelSolverHelper::ModelSolverHelper(const std::string&); +%unignore operations_research::mb::ModelSolverHelper::SolverIsSupported; +%unignore operations_research::mb::ModelSolverHelper::Solve; +%unignore operations_research::mb::ModelSolverHelper::InterruptSolve; +%rename (HasResponse) operations_research::mb::ModelSolverHelper::has_response; +%rename (HasSolution) operations_research::mb::ModelSolverHelper::has_solution; +%rename (Status) operations_research::mb::ModelSolverHelper::status; +%rename (ObjectiveValue) operations_research::mb::ModelSolverHelper::objective_value; +%rename (BestObjectiveBound) operations_research::mb::ModelSolverHelper::best_objective_bound; +%rename (VariableValue) operations_research::mb::ModelSolverHelper::variable_value; +%rename (ReducedCost) operations_research::mb::ModelSolverHelper::reduced_cost; +%rename (DualValue) operations_research::mb::ModelSolverHelper::dual_value; +%rename (Activity) operations_research::mb::ModelSolverHelper::activity; +%rename (StatusString) operations_research::mb::ModelSolverHelper::status_string; +%rename (WallTime) operations_research::mb::ModelSolverHelper::wall_time; +%rename (UserTime) operations_research::mb::ModelSolverHelper::user_time; +%unignore operations_research::mb::ModelSolverHelper::EnableOutput; +%unignore operations_research::mb::ModelSolverHelper::ClearLogCallback; +%unignore operations_research::mb::ModelSolverHelper::SetLogCallbackFromDirectorClass; +%unignore operations_research::mb::ModelSolverHelper::SetTimeLimitInSeconds; +%unignore operations_research::mb::ModelSolverHelper::SetSolverSpecificParameters; -%unignore operations_research::SolveStatus; -%unignore operations_research::OPTIMAL; -%unignore operations_research::FEASIBLE; -%unignore operations_research::INFEASIBLE; -%unignore operations_research::UNBOUNDED; -%unignore operations_research::ABNORMAL; -%unignore operations_research::NOT_SOLVED; -%unignore operations_research::MODEL_IS_VALID; -%unignore operations_research::CANCELLED_BY_USER; -%unignore operations_research::UNKNOWN_STATUS; -%unignore operations_research::MODEL_INVALID; -%unignore operations_research::INVALID_SOLVER_PARAMETERS; -%unignore operations_research::SOLVER_TYPE_UNAVAILABLE; -%unignore operations_research::INCOMPATIBLE_OPTIONS; +%unignore operations_research::mb::SolveStatus; +%unignore operations_research::mb::OPTIMAL; +%unignore operations_research::mb::FEASIBLE; +%unignore operations_research::mb::INFEASIBLE; +%unignore operations_research::mb::UNBOUNDED; +%unignore operations_research::mb::ABNORMAL; +%unignore operations_research::mb::NOT_SOLVED; +%unignore operations_research::mb::MODEL_IS_VALID; +%unignore operations_research::mb::CANCELLED_BY_USER; +%unignore operations_research::mb::UNKNOWN_STATUS; +%unignore operations_research::mb::MODEL_INVALID; +%unignore operations_research::mb::INVALID_SOLVER_PARAMETERS; +%unignore operations_research::mb::SOLVER_TYPE_UNAVAILABLE; +%unignore operations_research::mb::INCOMPATIBLE_OPTIONS; // For enums %include "ortools/linear_solver/wrappers/model_builder_helper.h" diff --git a/ortools/linear_solver/java/modelbuilder.i b/ortools/linear_solver/java/modelbuilder.i index e1b17ce7d9..a58a7df757 100644 --- a/ortools/linear_solver/java/modelbuilder.i +++ b/ortools/linear_solver/java/modelbuilder.i @@ -87,7 +87,7 @@ class GlobalRefGuard { %typemap(jstype) std::function "java.util.function.Consumer" // Type used in the Proxy class. %typemap(javain) std::function "$javainput" // passing the Callback to JNI java class. -%extend operations_research::ModelBuilderHelper { +%extend operations_research::mb::ModelBuilderHelper { std::string exportToMpsString(bool obfuscate) { operations_research::MPModelExportOptions options; options.obfuscate = obfuscate; @@ -105,129 +105,130 @@ class GlobalRefGuard { options.obfuscate = obfuscate; return $self->WriteToMpsFile(filename, options); } -} // Extend operations_research::ModelBuilderHelper +} // Extend operations_research::mb::ModelBuilderHelper %ignoreall %unignore operations_research; +%unignore operations_research::mb; // Wrap the ModelBuilderHelper class. -%unignore operations_research::ModelBuilderHelper; -%unignore operations_research::ModelBuilderHelper::ModelBuilderHelper; -%unignore operations_research::ModelBuilderHelper::~ModelBuilderHelper; +%unignore operations_research::mb::ModelBuilderHelper; +%unignore operations_research::mb::ModelBuilderHelper::ModelBuilderHelper; +%unignore operations_research::mb::ModelBuilderHelper::~ModelBuilderHelper; // Var API. -%rename (addVar) operations_research::ModelBuilderHelper::AddVar; -%rename (getVarIntegrality) operations_research::ModelBuilderHelper::VarIsIntegral; -%rename (getVarLowerBound) operations_research::ModelBuilderHelper::VarLowerBound; -%rename (getVarName) operations_research::ModelBuilderHelper::VarName; -%rename (getVarObjectiveCoefficient) operations_research::ModelBuilderHelper::VarObjectiveCoefficient; -%rename (getVarUpperBound) operations_research::ModelBuilderHelper::VarUpperBound; -%rename (setVarIntegrality) operations_research::ModelBuilderHelper::SetVarIntegrality; -%rename (setVarLowerBound) operations_research::ModelBuilderHelper::SetVarLowerBound; -%rename (setVarName) operations_research::ModelBuilderHelper::SetVarName; -%rename (setVarObjectiveCoefficient) operations_research::ModelBuilderHelper::SetVarObjectiveCoefficient; -%rename (setVarUpperBound) operations_research::ModelBuilderHelper::SetVarUpperBound; +%rename (addVar) operations_research::mb::ModelBuilderHelper::AddVar; +%rename (getVarIntegrality) operations_research::mb::ModelBuilderHelper::VarIsIntegral; +%rename (getVarLowerBound) operations_research::mb::ModelBuilderHelper::VarLowerBound; +%rename (getVarName) operations_research::mb::ModelBuilderHelper::VarName; +%rename (getVarObjectiveCoefficient) operations_research::mb::ModelBuilderHelper::VarObjectiveCoefficient; +%rename (getVarUpperBound) operations_research::mb::ModelBuilderHelper::VarUpperBound; +%rename (setVarIntegrality) operations_research::mb::ModelBuilderHelper::SetVarIntegrality; +%rename (setVarLowerBound) operations_research::mb::ModelBuilderHelper::SetVarLowerBound; +%rename (setVarName) operations_research::mb::ModelBuilderHelper::SetVarName; +%rename (setVarObjectiveCoefficient) operations_research::mb::ModelBuilderHelper::SetVarObjectiveCoefficient; +%rename (setVarUpperBound) operations_research::mb::ModelBuilderHelper::SetVarUpperBound; // Linear Constraint API. -%rename (addConstraintTerm) operations_research::ModelBuilderHelper::AddConstraintTerm; -%rename (addLinearConstraint) operations_research::ModelBuilderHelper::AddLinearConstraint; -%rename (clearConstraintTerms) operations_research::ModelBuilderHelper::ClearConstraintTerms; -%rename (getConstraintCoefficients) operations_research::ModelBuilderHelper::ConstraintCoefficients; -%rename (getConstraintLowerBound) operations_research::ModelBuilderHelper::ConstraintLowerBound; -%rename (getConstraintName) operations_research::ModelBuilderHelper::ConstraintName; -%rename (getConstraintUpperBound) operations_research::ModelBuilderHelper::ConstraintUpperBound; -%rename (getConstraintVarIndices) operations_research::ModelBuilderHelper::ConstraintVarIndices; -%rename (safeAddConstraintTerm) operations_research::ModelBuilderHelper::SafeAddConstraintTerm; -%rename (setConstraintCoefficient) operations_research::ModelBuilderHelper::SetConstraintCoefficient; -%rename (setConstraintLowerBound) operations_research::ModelBuilderHelper::SetConstraintLowerBound; -%rename (setConstraintName) operations_research::ModelBuilderHelper::SetConstraintName; -%rename (setConstraintUpperBound) operations_research::ModelBuilderHelper::SetConstraintUpperBound; +%rename (addConstraintTerm) operations_research::mb::ModelBuilderHelper::AddConstraintTerm; +%rename (addLinearConstraint) operations_research::mb::ModelBuilderHelper::AddLinearConstraint; +%rename (clearConstraintTerms) operations_research::mb::ModelBuilderHelper::ClearConstraintTerms; +%rename (getConstraintCoefficients) operations_research::mb::ModelBuilderHelper::ConstraintCoefficients; +%rename (getConstraintLowerBound) operations_research::mb::ModelBuilderHelper::ConstraintLowerBound; +%rename (getConstraintName) operations_research::mb::ModelBuilderHelper::ConstraintName; +%rename (getConstraintUpperBound) operations_research::mb::ModelBuilderHelper::ConstraintUpperBound; +%rename (getConstraintVarIndices) operations_research::mb::ModelBuilderHelper::ConstraintVarIndices; +%rename (safeAddConstraintTerm) operations_research::mb::ModelBuilderHelper::SafeAddConstraintTerm; +%rename (setConstraintCoefficient) operations_research::mb::ModelBuilderHelper::SetConstraintCoefficient; +%rename (setConstraintLowerBound) operations_research::mb::ModelBuilderHelper::SetConstraintLowerBound; +%rename (setConstraintName) operations_research::mb::ModelBuilderHelper::SetConstraintName; +%rename (setConstraintUpperBound) operations_research::mb::ModelBuilderHelper::SetConstraintUpperBound; // Enforced Linear Constraint API. -%rename (addEnforcedConstraintTerm) operations_research::ModelBuilderHelper::AddEnforcedConstraintTerm; -%rename (addEnforcedLinearConstraint) operations_research::ModelBuilderHelper::AddEnforcedLinearConstraint; -%rename (clearEnforcedConstraintTerms) operations_research::ModelBuilderHelper::ClearEnforcedConstraintTerms; -%rename (getEnforcedConstraintCoefficients) operations_research::ModelBuilderHelper::EnforcedConstraintCoefficients; -%rename (getEnforcedConstraintLowerBound) operations_research::ModelBuilderHelper::EnforcedConstraintLowerBound; -%rename (getEnforcedConstraintName) operations_research::ModelBuilderHelper::EnforcedConstraintName; -%rename (getEnforcedConstraintUpperBound) operations_research::ModelBuilderHelper::EnforcedConstraintUpperBound; -%rename (getEnforcedConstraintVarIndices) operations_research::ModelBuilderHelper::EnforcedConstraintVarIndices; -%rename (getEnforcedIndicatorValue) operations_research::ModelBuilderHelper::EnforcedIndicatorValue; -%rename (getEnforcedIndicatorVariableIndex) operations_research::ModelBuilderHelper::EnforcedIndicatorVariableIndex; -%rename (isEnforcedConstraint) operations_research::ModelBuilderHelper::IsEnforcedConstraint; -%rename (safeAddEnforcedConstraintTerm) operations_research::ModelBuilderHelper::SafeAddEnforcedConstraintTerm; -%rename (setEnforcedConstraintCoefficient) operations_research::ModelBuilderHelper::SetEnforcedConstraintCoefficient; -%rename (setEnforcedConstraintLowerBound) operations_research::ModelBuilderHelper::SetEnforcedConstraintLowerBound; -%rename (setEnforcedConstraintName) operations_research::ModelBuilderHelper::SetEnforcedConstraintName; -%rename (setEnforcedConstraintUpperBound) operations_research::ModelBuilderHelper::SetEnforcedConstraintUpperBound; -%rename (setEnforcedIndicatorValue) operations_research::ModelBuilderHelper::SetEnforcedIndicatorValue; -%rename (setEnforcedIndicatorVariableIndex) operations_research::ModelBuilderHelper::SetEnforcedIndicatorVariableIndex; +%rename (addEnforcedConstraintTerm) operations_research::mb::ModelBuilderHelper::AddEnforcedConstraintTerm; +%rename (addEnforcedLinearConstraint) operations_research::mb::ModelBuilderHelper::AddEnforcedLinearConstraint; +%rename (clearEnforcedConstraintTerms) operations_research::mb::ModelBuilderHelper::ClearEnforcedConstraintTerms; +%rename (getEnforcedConstraintCoefficients) operations_research::mb::ModelBuilderHelper::EnforcedConstraintCoefficients; +%rename (getEnforcedConstraintLowerBound) operations_research::mb::ModelBuilderHelper::EnforcedConstraintLowerBound; +%rename (getEnforcedConstraintName) operations_research::mb::ModelBuilderHelper::EnforcedConstraintName; +%rename (getEnforcedConstraintUpperBound) operations_research::mb::ModelBuilderHelper::EnforcedConstraintUpperBound; +%rename (getEnforcedConstraintVarIndices) operations_research::mb::ModelBuilderHelper::EnforcedConstraintVarIndices; +%rename (getEnforcedIndicatorValue) operations_research::mb::ModelBuilderHelper::EnforcedIndicatorValue; +%rename (getEnforcedIndicatorVariableIndex) operations_research::mb::ModelBuilderHelper::EnforcedIndicatorVariableIndex; +%rename (isEnforcedConstraint) operations_research::mb::ModelBuilderHelper::IsEnforcedConstraint; +%rename (safeAddEnforcedConstraintTerm) operations_research::mb::ModelBuilderHelper::SafeAddEnforcedConstraintTerm; +%rename (setEnforcedConstraintCoefficient) operations_research::mb::ModelBuilderHelper::SetEnforcedConstraintCoefficient; +%rename (setEnforcedConstraintLowerBound) operations_research::mb::ModelBuilderHelper::SetEnforcedConstraintLowerBound; +%rename (setEnforcedConstraintName) operations_research::mb::ModelBuilderHelper::SetEnforcedConstraintName; +%rename (setEnforcedConstraintUpperBound) operations_research::mb::ModelBuilderHelper::SetEnforcedConstraintUpperBound; +%rename (setEnforcedIndicatorValue) operations_research::mb::ModelBuilderHelper::SetEnforcedIndicatorValue; +%rename (setEnforcedIndicatorVariableIndex) operations_research::mb::ModelBuilderHelper::SetEnforcedIndicatorVariableIndex; // Objective API. -%rename (clearObjective) operations_research::ModelBuilderHelper::ClearObjective; -%rename (getMaximize) operations_research::ModelBuilderHelper::maximize; -%rename (setMaximize) operations_research::ModelBuilderHelper::SetMaximize; -%rename (getObjectiveOffset) operations_research::ModelBuilderHelper::ObjectiveOffset; -%rename (setObjectiveOffset) operations_research::ModelBuilderHelper::SetObjectiveOffset; +%rename (clearObjective) operations_research::mb::ModelBuilderHelper::ClearObjective; +%rename (getMaximize) operations_research::mb::ModelBuilderHelper::maximize; +%rename (setMaximize) operations_research::mb::ModelBuilderHelper::SetMaximize; +%rename (getObjectiveOffset) operations_research::mb::ModelBuilderHelper::ObjectiveOffset; +%rename (setObjectiveOffset) operations_research::mb::ModelBuilderHelper::SetObjectiveOffset; // Hints. -%rename (clearHints) operations_research::ModelBuilderHelper::ClearHints; -%rename (addHint) operations_research::ModelBuilderHelper::AddHint; +%rename (clearHints) operations_research::mb::ModelBuilderHelper::ClearHints; +%rename (addHint) operations_research::mb::ModelBuilderHelper::AddHint; // Model API. -%rename (numVariables) operations_research::ModelBuilderHelper::num_variables; -%rename (numConstraints) operations_research::ModelBuilderHelper::num_constraints; -%rename (getName) operations_research::ModelBuilderHelper::name; -%rename (setName) operations_research::ModelBuilderHelper::SetName; -%rename (readModelFromProtoFile) operations_research::ModelBuilderHelper::ReadModelFromProtoFile; -%rename (writeModelToProtoFile) operations_research::ModelBuilderHelper::WriteModelToProtoFile; -%rename (importFromMpsString) operations_research::ModelBuilderHelper::ImportFromMpsString; -%rename (importFromMpsFile) operations_research::ModelBuilderHelper::ImportFromMpsFile; -%rename (importFromLpString) operations_research::ModelBuilderHelper::ImportFromLpString; -%rename (importFromLpFile) operations_research::ModelBuilderHelper::ImportFromLpFile; -%unignore operations_research::ModelBuilderHelper::exportToMpsString; -%unignore operations_research::ModelBuilderHelper::exportToLpString; -%unignore operations_research::ModelBuilderHelper::writeToMpsFile; -%rename (overwriteModel) operations_research::ModelBuilderHelper::OverwriteModel; +%rename (numVariables) operations_research::mb::ModelBuilderHelper::num_variables; +%rename (numConstraints) operations_research::mb::ModelBuilderHelper::num_constraints; +%rename (getName) operations_research::mb::ModelBuilderHelper::name; +%rename (setName) operations_research::mb::ModelBuilderHelper::SetName; +%rename (readModelFromProtoFile) operations_research::mb::ModelBuilderHelper::ReadModelFromProtoFile; +%rename (writeModelToProtoFile) operations_research::mb::ModelBuilderHelper::WriteModelToProtoFile; +%rename (importFromMpsString) operations_research::mb::ModelBuilderHelper::ImportFromMpsString; +%rename (importFromMpsFile) operations_research::mb::ModelBuilderHelper::ImportFromMpsFile; +%rename (importFromLpString) operations_research::mb::ModelBuilderHelper::ImportFromLpString; +%rename (importFromLpFile) operations_research::mb::ModelBuilderHelper::ImportFromLpFile; +%unignore operations_research::mb::ModelBuilderHelper::exportToMpsString; +%unignore operations_research::mb::ModelBuilderHelper::exportToLpString; +%unignore operations_research::mb::ModelBuilderHelper::writeToMpsFile; +%rename (overwriteModel) operations_research::mb::ModelBuilderHelper::OverwriteModel; -%unignore operations_research::ModelSolverHelper; -%unignore operations_research::ModelSolverHelper::ModelSolverHelper(const std::string&); -%rename (solverIsSupported) operations_research::ModelSolverHelper::SolverIsSupported; -%rename (solve) operations_research::ModelSolverHelper::Solve; -%rename (interruptSolve) operations_research::ModelSolverHelper::InterruptSolve; -%rename (hasResponse) operations_research::ModelSolverHelper::has_response; -%rename (hasSolution) operations_research::ModelSolverHelper::has_solution; -%rename (getStatus) operations_research::ModelSolverHelper::status; -%rename (getObjectiveValue) operations_research::ModelSolverHelper::objective_value; -%rename (getBestObjectiveBound) operations_research::ModelSolverHelper::best_objective_bound; -%rename (getVariableValue) operations_research::ModelSolverHelper::variable_value; -%rename (getReducedCost) operations_research::ModelSolverHelper::reduced_cost; -%rename (getDualValue) operations_research::ModelSolverHelper::dual_value; -%rename (getActivity) operations_research::ModelSolverHelper::activity; -%rename (getStatusString) operations_research::ModelSolverHelper::status_string; -%rename (getWallTime) operations_research::ModelSolverHelper::wall_time; -%rename (getUserTime) operations_research::ModelSolverHelper::user_time; -%rename (enableOutput) operations_research::ModelSolverHelper::EnableOutput; -%rename (clearLogCallback) operations_research::ModelSolverHelper::ClearLogCallback; -%rename (setLogCallback) operations_research::ModelSolverHelper::SetLogCallback; -%rename (setTimeLimitInSeconds) operations_research::ModelSolverHelper::SetTimeLimitInSeconds; -%rename (setSolverSpecificParameters) operations_research::ModelSolverHelper::SetSolverSpecificParameters; +%unignore operations_research::mb::ModelSolverHelper; +%unignore operations_research::mb::ModelSolverHelper::ModelSolverHelper(const std::string&); +%rename (solverIsSupported) operations_research::mb::ModelSolverHelper::SolverIsSupported; +%rename (solve) operations_research::mb::ModelSolverHelper::Solve; +%rename (interruptSolve) operations_research::mb::ModelSolverHelper::InterruptSolve; +%rename (hasResponse) operations_research::mb::ModelSolverHelper::has_response; +%rename (hasSolution) operations_research::mb::ModelSolverHelper::has_solution; +%rename (getStatus) operations_research::mb::ModelSolverHelper::status; +%rename (getObjectiveValue) operations_research::mb::ModelSolverHelper::objective_value; +%rename (getBestObjectiveBound) operations_research::mb::ModelSolverHelper::best_objective_bound; +%rename (getVariableValue) operations_research::mb::ModelSolverHelper::variable_value; +%rename (getReducedCost) operations_research::mb::ModelSolverHelper::reduced_cost; +%rename (getDualValue) operations_research::mb::ModelSolverHelper::dual_value; +%rename (getActivity) operations_research::mb::ModelSolverHelper::activity; +%rename (getStatusString) operations_research::mb::ModelSolverHelper::status_string; +%rename (getWallTime) operations_research::mb::ModelSolverHelper::wall_time; +%rename (getUserTime) operations_research::mb::ModelSolverHelper::user_time; +%rename (enableOutput) operations_research::mb::ModelSolverHelper::EnableOutput; +%rename (clearLogCallback) operations_research::mb::ModelSolverHelper::ClearLogCallback; +%rename (setLogCallback) operations_research::mb::ModelSolverHelper::SetLogCallback; +%rename (setTimeLimitInSeconds) operations_research::mb::ModelSolverHelper::SetTimeLimitInSeconds; +%rename (setSolverSpecificParameters) operations_research::mb::ModelSolverHelper::SetSolverSpecificParameters; -%unignore operations_research::SolveStatus; -%unignore operations_research::OPTIMAL; -%unignore operations_research::FEASIBLE; -%unignore operations_research::INFEASIBLE; -%unignore operations_research::UNBOUNDED; -%unignore operations_research::ABNORMAL; -%unignore operations_research::NOT_SOLVED; -%unignore operations_research::MODEL_IS_VALID; -%unignore operations_research::CANCELLED_BY_USER; -%unignore operations_research::UNKNOWN_STATUS; -%unignore operations_research::MODEL_INVALID; -%unignore operations_research::INVALID_SOLVER_PARAMETERS; -%unignore operations_research::SOLVER_TYPE_UNAVAILABLE; -%unignore operations_research::INCOMPATIBLE_OPTIONS; +%unignore operations_research::mb::SolveStatus; +%unignore operations_research::mb::OPTIMAL; +%unignore operations_research::mb::FEASIBLE; +%unignore operations_research::mb::INFEASIBLE; +%unignore operations_research::mb::UNBOUNDED; +%unignore operations_research::mb::ABNORMAL; +%unignore operations_research::mb::NOT_SOLVED; +%unignore operations_research::mb::MODEL_IS_VALID; +%unignore operations_research::mb::CANCELLED_BY_USER; +%unignore operations_research::mb::UNKNOWN_STATUS; +%unignore operations_research::mb::MODEL_INVALID; +%unignore operations_research::mb::INVALID_SOLVER_PARAMETERS; +%unignore operations_research::mb::SOLVER_TYPE_UNAVAILABLE; +%unignore operations_research::mb::INCOMPATIBLE_OPTIONS; // For enums %javaconst(1); diff --git a/ortools/linear_solver/python/model_builder.py b/ortools/linear_solver/python/model_builder.py index 9d9dc8f7c6..df276b2e3d 100644 --- a/ortools/linear_solver/python/model_builder.py +++ b/ortools/linear_solver/python/model_builder.py @@ -32,386 +32,35 @@ Other methods and functions listed are primarily used for developing OR-Tools, rather than for solving specific optimization problems. """ -import abc -import dataclasses import math import numbers import typing -from typing import Callable, List, Optional, Sequence, Tuple, Union, cast +from typing import Callable, Optional, Union import numpy as np -from numpy import typing as npt import pandas as pd from ortools.linear_solver import linear_solver_pb2 from ortools.linear_solver.python import model_builder_helper as mbh from ortools.linear_solver.python import model_builder_numbers as mbn - # Custom types. NumberT = Union[int, float, numbers.Real, np.number] IntegerT = Union[int, numbers.Integral, np.integer] -LinearExprT = Union["LinearExpr", NumberT] -ConstraintT = Union["_BoundedLinearExpr", bool] +LinearExprT = Union[mbh.LinearExpr, NumberT] +ConstraintT = Union[mbh.BoundedLinearExpression, bool] _IndexOrSeries = Union[pd.Index, pd.Series] -_VariableOrConstraint = Union["LinearConstraint", "Variable"] +_VariableOrConstraint = Union["LinearConstraint", mbh.Variable] # Forward solve statuses. +BoundedLinearExpression = mbh.BoundedLinearExpression +LinearExpr = mbh.LinearExpr SolveStatus = mbh.SolveStatus - -# pylint: disable=protected-access - - -class LinearExpr(metaclass=abc.ABCMeta): - """Holds an linear expression. - - A linear expression is built from constants and variables. - For example, `x + 2.0 * (y - z + 1.0)`. - - Linear expressions are used in Model models in constraints and in the - objective: - - * You can define linear constraints as in: - - ``` - model.add(x + 2 * y <= 5.0) - model.add(sum(array_of_vars) == 5.0) - ``` - - * In Model, the objective is a linear expression: - - ``` - model.minimize(x + 2.0 * y + z) - ``` - - * For large arrays, using the LinearExpr class is faster that using the python - `sum()` function. You can create constraints and the objective from lists of - linear expressions or coefficients as follows: - - ``` - model.minimize(model_builder.LinearExpr.sum(expressions)) - model.add(model_builder.LinearExpr.weighted_sum(expressions, coeffs) >= 0) - ``` - """ - - @classmethod - def sum( # pytype: disable=annotation-type-mismatch # numpy-scalars - cls, expressions: Sequence[LinearExprT], *, constant: NumberT = 0.0 - ) -> LinearExprT: - """Creates `sum(expressions) + constant`. - - It can perform simple simplifications and returns different objects, - including the input. - - Args: - expressions: a sequence of linear expressions or constants. - constant: a numerical constant. - - Returns: - a LinearExpr instance or a numerical constant. - """ - checked_constant: np.double = mbn.assert_is_a_number(constant) - if not expressions: - return checked_constant - if len(expressions) == 1 and mbn.is_zero(checked_constant): - return expressions[0] - - return LinearExpr.weighted_sum( - expressions, np.ones(len(expressions)), constant=checked_constant - ) - - @classmethod - def weighted_sum( # pytype: disable=annotation-type-mismatch # numpy-scalars - cls, - expressions: Sequence[LinearExprT], - coefficients: Sequence[NumberT], - *, - constant: NumberT = 0.0, - ) -> Union[NumberT, "_LinearExpression"]: - """Creates `sum(expressions[i] * coefficients[i]) + constant`. - - It can perform simple simplifications and returns different object, - including the input. - - Args: - expressions: a sequence of linear expressions or constants. - coefficients: a sequence of numerical constants. - constant: a numerical constant. - - Returns: - a _LinearExpression instance or a numerical constant. - """ - if len(expressions) != len(coefficients): - raise ValueError( - "LinearExpr.weighted_sum: expressions and coefficients have" - " different lengths" - ) - checked_constant: np.double = mbn.assert_is_a_number(constant) - if not expressions: - return checked_constant - return _sum_as_flat_linear_expression( - to_process=list(zip(expressions, coefficients)), offset=checked_constant - ) - - @classmethod - def term( # pytype: disable=annotation-type-mismatch # numpy-scalars - cls, - expression: LinearExprT, - coefficient: NumberT, - *, - constant: NumberT = 0.0, - ) -> LinearExprT: - """Creates `expression * coefficient + constant`. - - It can perform simple simplifications and returns different object, - including the input. - Args: - expression: a linear expression or a constant. - coefficient: a numerical constant. - constant: a numerical constant. - - Returns: - a LinearExpr instance or a numerical constant. - """ - checked_coefficient: np.double = mbn.assert_is_a_number(coefficient) - checked_constant: np.double = mbn.assert_is_a_number(constant) - - if mbn.is_zero(checked_coefficient): - return checked_constant - if mbn.is_one(checked_coefficient) and mbn.is_zero(checked_constant): - return expression - if mbn.is_a_number(expression): - return np.double(expression) * checked_coefficient + checked_constant - if isinstance(expression, LinearExpr): - return _as_flat_linear_expression( - expression * checked_coefficient + checked_constant - ) - raise TypeError(f"Unknown expression {expression!r} of type {type(expression)}") - - def __hash__(self): - return object.__hash__(self) - - def __add__(self, arg: LinearExprT) -> "_Sum": - return _Sum(self, arg) - - def __radd__(self, arg: LinearExprT) -> "_Sum": - return self.__add__(arg) - - def __sub__(self, arg: LinearExprT) -> "_Sum": - return _Sum(self, -arg) - - def __rsub__(self, arg: LinearExprT) -> "_Sum": - return _Sum(-self, arg) - - def __mul__(self, arg: NumberT) -> "_Product": - return _Product(self, arg) - - def __rmul__(self, arg: NumberT) -> "_Product": - return self.__mul__(arg) - - def __truediv__(self, coeff: NumberT) -> "_Product": - return self.__mul__(1.0 / coeff) - - def __neg__(self) -> "_Product": - return _Product(self, -1) - - def __bool__(self): - raise NotImplementedError(f"Cannot use a LinearExpr {self} as a Boolean value") - - def __eq__(self, arg: LinearExprT) -> "BoundedLinearExpression": - return BoundedLinearExpression(self - arg, 0, 0) - - def __ge__(self, arg: LinearExprT) -> "BoundedLinearExpression": - return BoundedLinearExpression( - self - arg, 0, math.inf - ) # pytype: disable=wrong-arg-types # numpy-scalars - - def __le__(self, arg: LinearExprT) -> "BoundedLinearExpression": - return BoundedLinearExpression( - self - arg, -math.inf, 0 - ) # pytype: disable=wrong-arg-types # numpy-scalars - - -class Variable(LinearExpr): - """A variable (continuous or integral). - - A Variable is an object that can take on any integer value within defined - ranges. Variables appear in constraint like: - - x + y >= 5 - - Solving a model is equivalent to finding, for each variable, a single value - from the set of initial values (called the initial domain), such that the - model is feasible, or optimal if you provided an objective function. - """ - - def __init__( - self, - helper: mbh.ModelBuilderHelper, - lb: NumberT, - ub: Optional[NumberT], - is_integral: Optional[bool], - name: Optional[str], - ) -> None: - """See Model.new_var below.""" - LinearExpr.__init__(self) - self.__helper: mbh.ModelBuilderHelper = helper - # Python do not support multiple __init__ methods. - # This method is only called from the Model class. - # We hack the parameter to support the two cases: - # case 1: - # helper is a ModelBuilderHelper, lb is a double value, ub is a double - # value, is_integral is a Boolean value, and name is a string. - # case 2: - # helper is a ModelBuilderHelper, lb is an index (int), ub is None, - # is_integral is None, and name is None. - if mbn.is_integral(lb) and ub is None and is_integral is None: - self.__index: np.int32 = np.int32(lb) - self.__helper: mbh.ModelBuilderHelper = helper - else: - index: np.int32 = helper.add_var() - self.__index: np.int32 = np.int32(index) - self.__helper: mbh.ModelBuilderHelper = helper - helper.set_var_lower_bound(index, lb) - helper.set_var_upper_bound(index, ub) - helper.set_var_integrality(index, is_integral) - if name: - helper.set_var_name(index, name) - - @property - def index(self) -> np.int32: - """Returns the index of the variable in the helper.""" - return self.__index - - @property - def helper(self) -> mbh.ModelBuilderHelper: - """Returns the underlying ModelBuilderHelper.""" - return self.__helper - - def is_equal_to(self, other: LinearExprT) -> bool: - """Returns true if self == other in the python sense.""" - if not isinstance(other, Variable): - return False - return self.index == other.index and self.helper == other.helper - - def __str__(self) -> str: - return self.name - - def __repr__(self) -> str: - return self.__str__() - - @property - def name(self) -> str: - """Returns the name of the variable.""" - var_name = self.__helper.var_name(self.__index) - if var_name: - return var_name - return f"variable#{self.index}" - - @name.setter - def name(self, name: str) -> None: - """Sets the name of the variable.""" - self.__helper.set_var_name(self.__index, name) - - @property - def lower_bound(self) -> np.double: - """Returns the lower bound of the variable.""" - return self.__helper.var_lower_bound(self.__index) - - @lower_bound.setter - def lower_bound(self, bound: NumberT) -> None: - """Sets the lower bound of the variable.""" - self.__helper.set_var_lower_bound(self.__index, bound) - - @property - def upper_bound(self) -> np.double: - """Returns the upper bound of the variable.""" - return self.__helper.var_upper_bound(self.__index) - - @upper_bound.setter - def upper_bound(self, bound: NumberT) -> None: - """Sets the upper bound of the variable.""" - self.__helper.set_var_upper_bound(self.__index, bound) - - @property - def is_integral(self) -> bool: - """Returns whether the variable is integral.""" - return self.__helper.var_is_integral(self.__index) - - @is_integral.setter - def integrality(self, is_integral: bool) -> None: - """Sets the integrality of the variable.""" - self.__helper.set_var_integrality(self.__index, is_integral) - - @property - def objective_coefficient(self) -> NumberT: - return self.__helper.var_objective_coefficient(self.__index) - - @objective_coefficient.setter - def objective_coefficient(self, coeff: NumberT) -> None: - self.__helper.set_var_objective_coefficient(self.__index, coeff) - - def __eq__(self, arg: Optional[LinearExprT]) -> ConstraintT: - if arg is None: - return False - if isinstance(arg, Variable): - return VarEqVar(self, arg) - return BoundedLinearExpression( - self - arg, 0.0, 0.0 - ) # pytype: disable=wrong-arg-types # numpy-scalars - - def __hash__(self): - return hash((self.__helper, self.__index)) - - -class _BoundedLinearExpr(metaclass=abc.ABCMeta): - """Interface for types that can build bounded linear (boolean) expressions. - - Classes derived from _BoundedLinearExpr are used to build linear constraints - to be satisfied. - - * BoundedLinearExpression: a linear expression with upper and lower bounds. - * VarEqVar: an equality comparison between two variables. - """ - - @abc.abstractmethod - def _add_linear_constraint( - self, helper: mbh.ModelBuilderHelper, name: str - ) -> "LinearConstraint": - """Creates a new linear constraint in the helper. - - Args: - helper (mbh.ModelBuilderHelper): The helper to create the constraint. - name (str): The name of the linear constraint. - - Returns: - LinearConstraint: A reference to the linear constraint in the helper. - """ - - @abc.abstractmethod - def _add_enforced_linear_constraint( - self, - helper: mbh.ModelBuilderHelper, - var: Variable, - value: bool, - name: str, - ) -> "EnforcedLinearConstraint": - """Creates a new enforced linear constraint in the helper. - - Args: - helper (mbh.ModelBuilderHelper): The helper to create the constraint. - var (Variable): The indicator variable of the constraint. - value (bool): The indicator value of the constraint. - name (str): The name of the linear constraint. - - Returns: - Enforced LinearConstraint: A reference to the linear constraint in the - helper. - """ +Variable = mbh.Variable def _add_linear_constraint_to_helper( - bounded_expr: Union[bool, _BoundedLinearExpr], + bounded_expr: Union[bool, mbh.BoundedLinearExpression], helper: mbh.ModelBuilderHelper, name: Optional[str], ): @@ -448,14 +97,21 @@ def _add_linear_constraint_to_helper( helper.set_constraint_lower_bound(c.index, 1) helper.set_constraint_upper_bound(c.index, -1) return c - if isinstance(bounded_expr, _BoundedLinearExpr): + if isinstance(bounded_expr, mbh.BoundedLinearExpression): + c = LinearConstraint(helper) # pylint: disable=protected-access - return bounded_expr._add_linear_constraint(helper, name) + helper.add_terms_to_constraint(c.index, bounded_expr.vars, bounded_expr.coeffs) + helper.set_constraint_lower_bound(c.index, bounded_expr.lower_bound) + helper.set_constraint_upper_bound(c.index, bounded_expr.upper_bound) + # pylint: enable=protected-access + if name is not None: + helper.set_constraint_name(c.index, name) + return c raise TypeError("invalid type={}".format(type(bounded_expr))) def _add_enforced_linear_constraint_to_helper( - bounded_expr: Union[bool, _BoundedLinearExpr], + bounded_expr: Union[bool, mbh.BoundedLinearExpression], helper: mbh.ModelBuilderHelper, var: Variable, value: bool, @@ -502,153 +158,20 @@ def _add_enforced_linear_constraint_to_helper( helper.set_enforced_constraint_lower_bound(c.index, 1) helper.set_enforced_constraint_upper_bound(c.index, -1) return c - if isinstance(bounded_expr, _BoundedLinearExpr): - # pylint: disable=protected-access - return bounded_expr._add_enforced_linear_constraint(helper, var, value, name) - raise TypeError("invalid type={}".format(type(bounded_expr))) - - -@dataclasses.dataclass(repr=False, eq=False, frozen=True) -class VarEqVar(_BoundedLinearExpr): - """Represents var == var.""" - - __slots__ = ("left", "right") - - left: Variable - right: Variable - - def __str__(self): - return f"{self.left} == {self.right}" - - def __repr__(self): - return self.__str__() - - def __bool__(self) -> bool: - return hash(self.left) == hash(self.right) - - def _add_linear_constraint( - self, helper: mbh.ModelBuilderHelper, name: str - ) -> "LinearConstraint": - c = LinearConstraint(helper) - helper.set_constraint_lower_bound(c.index, 0.0) - helper.set_constraint_upper_bound(c.index, 0.0) - # pylint: disable=protected-access - helper.add_term_to_constraint(c.index, self.left.index, 1.0) - helper.add_term_to_constraint(c.index, self.right.index, -1.0) - # pylint: enable=protected-access - helper.set_constraint_name(c.index, name) - return c - - def _add_enforced_linear_constraint( - self, - helper: mbh.ModelBuilderHelper, - var: Variable, - value: bool, - name: str, - ) -> "EnforcedLinearConstraint": - """Adds an enforced linear constraint to the model.""" + if isinstance(bounded_expr, mbh.BoundedLinearExpression): c = EnforcedLinearConstraint(helper) c.indicator_variable = var c.indicator_value = value - helper.set_enforced_constraint_lower_bound(c.index, 0.0) - helper.set_enforced_constraint_upper_bound(c.index, 0.0) - # pylint: disable=protected-access - helper.add_term_to_enforced_constraint(c.index, self.left.index, 1.0) - helper.add_term_to_enforced_constraint(c.index, self.right.index, -1.0) - # pylint: enable=protected-access - helper.set_enforced_constraint_name(c.index, name) - return c - - -class BoundedLinearExpression(_BoundedLinearExpr): - """Represents a linear constraint: `lb <= linear expression <= ub`. - - The only use of this class is to be added to the Model through - `Model.add(bounded expression)`, as in: - - model.Add(x + 2 * y -1 >= z) - """ - - def __init__(self, expr: LinearExprT, lb: NumberT, ub: NumberT) -> None: - self.__expr: LinearExprT = expr - self.__lb: np.double = mbn.assert_is_a_number(lb) - self.__ub: np.double = mbn.assert_is_a_number(ub) - - def __str__(self) -> str: - if self.__lb > -math.inf and self.__ub < math.inf: - if self.__lb == self.__ub: - return f"{self.__expr} == {self.__lb}" - else: - return f"{self.__lb} <= {self.__expr} <= {self.__ub}" - elif self.__lb > -math.inf: - return f"{self.__expr} >= {self.__lb}" - elif self.__ub < math.inf: - return f"{self.__expr} <= {self.__ub}" - else: - return f"{self.__expr} free" - - def __repr__(self): - return self.__str__() - - @property - def expression(self) -> LinearExprT: - return self.__expr - - @property - def lower_bound(self) -> np.double: - return self.__lb - - @property - def upper_bound(self) -> np.double: - return self.__ub - - def __bool__(self) -> bool: - raise NotImplementedError( - f"Cannot use a BoundedLinearExpression {self} as a Boolean value" + helper.add_terms_to_enforced_constraint( + c.index, bounded_expr.vars, bounded_expr.coeffs ) - - def _add_linear_constraint( - self, helper: mbh.ModelBuilderHelper, name: Optional[str] - ) -> "LinearConstraint": - c = LinearConstraint(helper) - flat_expr = _as_flat_linear_expression(self.__expr) - # pylint: disable=protected-access - helper.add_terms_to_constraint( - c.index, flat_expr._variable_indices, flat_expr._coefficients - ) - helper.set_constraint_lower_bound(c.index, self.__lb - flat_expr._offset) - helper.set_constraint_upper_bound(c.index, self.__ub - flat_expr._offset) - # pylint: enable=protected-access + helper.set_enforced_constraint_lower_bound(c.index, bounded_expr.lower_bound) + helper.set_enforced_constraint_upper_bound(c.index, bounded_expr.upper_bound) if name is not None: helper.set_constraint_name(c.index, name) return c - def _add_enforced_linear_constraint( - self, - helper: mbh.ModelBuilderHelper, - var: Variable, - value: bool, - name: Optional[str], - ) -> "EnforcedLinearConstraint": - """Adds an enforced linear constraint to the model.""" - c = EnforcedLinearConstraint(helper) - c.indicator_variable = var - c.indicator_value = value - flat_expr = _as_flat_linear_expression(self.__expr) - # pylint: disable=protected-access - helper.add_terms_to_enforced_constraint( - c.index, flat_expr._variable_indices, flat_expr._coefficients - ) - helper.set_enforced_constraint_lower_bound( - c.index, self.__lb - flat_expr._offset - ) - helper.set_enforced_constraint_upper_bound( - c.index, self.__ub - flat_expr._offset - ) - # pylint: enable=protected-access - if name is not None: - helper.set_enforced_constraint_name(c.index, name) - return c + raise TypeError("invalid type={}".format(type(bounded_expr))) class LinearConstraint: @@ -683,6 +206,9 @@ class LinearConstraint: self.__helper: mbh.ModelBuilderHelper = helper self.__is_under_specified = is_under_specified + def __hash__(self): + return hash((self.__helper, self.__index)) + @property def index(self) -> IntegerT: """Returns the index of the constraint in the helper.""" @@ -837,7 +363,7 @@ class EnforcedLinearConstraint: enforcement_var_index = ( self.__helper.enforced_constraint_indicator_variable_index(self.__index) ) - return Variable(self.__helper, enforcement_var_index, None, None, None) + return Variable(self.__helper, enforcement_var_index) @indicator_variable.setter def indicator_variable(self, var: "Variable") -> None: @@ -984,15 +510,14 @@ class Model: """ return _attribute_series( # pylint: disable=g-long-lambda - func=lambda c: _as_flat_linear_expression( + func=lambda c: mbh.FlatExpression( # pylint: disable=g-complex-comprehension - sum( - coeff * Variable(self.__helper, var_id, None, None, None) - for var_id, coeff in zip( - c.helper.constraint_var_indices(c.index), - c.helper.constraint_coefficients(c.index), - ) - ) + [ + Variable(self.__helper, var_id) + for var_id in c.helper.constraint_var_indices(c.index) + ], + c.helper.constraint_coefficients(c.index), + 0.0, ), values=self._get_linear_constraints(constraints), ) @@ -1221,11 +746,11 @@ class Model: data=[ # pylint: disable=g-complex-comprehension Variable( - helper=self.__helper, - name=f"{name}[{i}]", - lb=lower_bounds[i], - ub=upper_bounds[i], - is_integral=is_integrals[i], + self.__helper, + lower_bounds[i], + upper_bounds[i], + is_integrals[i], + f"{name}[{i}]", ) for i in index ], @@ -1318,7 +843,7 @@ class Model: def var_from_index(self, index: IntegerT) -> Variable: """Rebuilds a variable object from the model and its index.""" - return Variable(self.__helper, index, None, None, None) + return Variable(self.__helper, index) # Linear constraints. @@ -1336,17 +861,13 @@ class Model: if mbn.is_a_number(linear_expr): self.__helper.set_constraint_lower_bound(ct.index, lb - linear_expr) self.__helper.set_constraint_upper_bound(ct.index, ub - linear_expr) - elif isinstance(linear_expr, Variable): - self.__helper.set_constraint_lower_bound(ct.index, lb) - self.__helper.set_constraint_upper_bound(ct.index, ub) - self.__helper.add_term_to_constraint(ct.index, linear_expr.index, 1.0) elif isinstance(linear_expr, LinearExpr): - flat_expr = _as_flat_linear_expression(linear_expr) + flat_expr = mbh.FlatExpression(linear_expr) # pylint: disable=protected-access - self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr._offset) - self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr._offset) + self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr.offset) + self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr.offset) self.__helper.add_terms_to_constraint( - ct.index, flat_expr._variable_indices, flat_expr._coefficients + ct.index, flat_expr.vars, flat_expr.coeffs ) else: raise TypeError( @@ -1381,8 +902,8 @@ class Model: you can check the if a constraint is under specified by reading the `LinearConstraint.is_under_specified` property. """ - if isinstance(ct, _BoundedLinearExpr): - return ct._add_linear_constraint(self.__helper, name) + if isinstance(ct, mbh.BoundedLinearExpression): + return _add_linear_constraint_to_helper(ct, self.__helper, name) elif isinstance(ct, bool): return _add_linear_constraint_to_helper(ct, self.__helper, name) elif isinstance(ct, pd.Series): @@ -1402,7 +923,7 @@ class Model: """Rebuilds a linear constraint object from the model and its index.""" return LinearConstraint(self.__helper, index=index) - # EnforcedLinear constraints. + # Enforced Linear constraints. def add_enforced_linear_constraint( # pytype: disable=annotation-type-mismatch # numpy-scalars self, @@ -1422,18 +943,12 @@ class Model: if mbn.is_a_number(linear_expr): self.__helper.set_constraint_lower_bound(ct.index, lb - linear_expr) self.__helper.set_constraint_upper_bound(ct.index, ub - linear_expr) - elif isinstance(linear_expr, Variable): - self.__helper.set_constraint_lower_bound(ct.index, lb) - self.__helper.set_constraint_upper_bound(ct.index, ub) - self.__helper.add_term_to_constraint(ct.index, linear_expr.index, 1.0) elif isinstance(linear_expr, LinearExpr): - flat_expr = _as_flat_linear_expression(linear_expr) + flat_expr = mbh.FlatExpression(linear_expr) # pylint: disable=protected-access - self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr._offset) - self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr._offset) - self.__helper.add_terms_to_constraint( - ct.index, flat_expr._variable_indices, flat_expr._coefficients - ) + self.__helper.set_constraint_lower_bound(ct.index, lb - flat_expr.offset) + self.__helper.set_constraint_upper_bound(ct.index, ub - flat_expr.offset) + self.__helper.add_terms_to_constraint(ct.index, flat_expr, flat_expr.coeffs) else: raise TypeError( "Not supported:" @@ -1472,8 +987,10 @@ class Model: you can check the if a constraint is always false (lb=inf, ub=-inf) by calling EnforcedLinearConstraint.is_always_false() """ - if isinstance(ct, _BoundedLinearExpr): - return ct._add_enforced_linear_constraint(self.__helper, var, value, name) + if isinstance(ct, mbh.BoundedLinearExpression): # IMPLEMENTME + return _add_enforced_linear_constraint_to_helper( + ct, self.__helper, var, value, name + ) elif ( isinstance(ct, bool) and isinstance(var, Variable) @@ -1525,12 +1042,11 @@ class Model: elif isinstance(linear_expr, Variable): self.helper.set_var_objective_coefficient(linear_expr.index, 1.0) elif isinstance(linear_expr, LinearExpr): - flat_expr = _as_flat_linear_expression(linear_expr) + flat_expr = mbh.FlatExpression(linear_expr) # pylint: disable=protected-access - self.helper.set_objective_offset(flat_expr._offset) - self.helper.set_objective_coefficients( - flat_expr._variable_indices, flat_expr._coefficients - ) + self.helper.set_objective_offset(flat_expr.offset) + var_indices = [var.index for var in flat_expr.vars] + self.helper.set_objective_coefficients(var_indices, flat_expr.coeffs) else: raise TypeError(f"Not supported: Model.minimize/maximize({linear_expr})") @@ -1543,15 +1059,17 @@ class Model: def objective_offset(self, value: NumberT) -> None: self.__helper.set_objective_offset(value) - def objective_expression(self) -> "_LinearExpression": + def objective_expression(self) -> "LinearExpr": """Returns the expression to optimize.""" - return _as_flat_linear_expression( - sum( - variable * self.__helper.var_objective_coefficient(variable.index) - for variable in self.get_variables() - if self.__helper.var_objective_coefficient(variable.index) != 0.0 - ) - + self.__helper.objective_offset() + variables: list[Variable] = [] + coefficients: list[numbers.Real] = [] + for variable in self.get_variables(): + coeff = self.__helper.var_objective_coefficient(variable.index) + if coeff != 0.0: + variables.append(variable) + coefficients.append(coeff) + return mbh.FlatExpression( + variables, coefficients, self.__helper.objective_offset() ) # Hints. @@ -1712,15 +1230,8 @@ class Solver: return pd.NA if mbn.is_a_number(expr): return expr - elif isinstance(expr, Variable): - return self.__solve_helper.var_value(expr.index) elif isinstance(expr, LinearExpr): - flat_expr = _as_flat_linear_expression(expr) - return self.__solve_helper.expression_value( - flat_expr._variable_indices, - flat_expr._coefficients, - flat_expr._offset, - ) + return self.__solve_helper.expression_value(expr) else: raise TypeError(f"Unknown expression {expr!r} of type {type(expr)}") @@ -1742,7 +1253,7 @@ class Solver: if not self.__solve_helper.has_solution(): return _attribute_series(func=lambda v: pd.NA, values=variables) return _attribute_series( - func=lambda v: self.__solve_helper.var_value(v.index), + func=lambda v: self.__solve_helper.variable_value(v.index), values=variables, ) @@ -1839,164 +1350,6 @@ class Solver: return self.__solve_helper.user_time() -# The maximum number of terms to display in a linear expression's repr. -_MAX_LINEAR_EXPRESSION_REPR_TERMS = 5 - - -@dataclasses.dataclass(repr=False, eq=False, frozen=True) -class _LinearExpression(LinearExpr): - """For variables x, an expression: offset + sum_{i in I} coeff_i * x_i.""" - - __slots__ = ("_variable_indices", "_coefficients", "_offset", "_helper") - - _variable_indices: npt.NDArray[np.int32] - _coefficients: npt.NDArray[np.double] - _offset: float - _helper: Optional[mbh.ModelBuilderHelper] - - @property - def variable_indices(self) -> npt.NDArray[np.int32]: - return self._variable_indices - - @property - def coefficients(self) -> npt.NDArray[np.double]: - return self._coefficients - - @property - def constant(self) -> float: - return self._offset - - @property - def helper(self) -> Optional[mbh.ModelBuilderHelper]: - return self._helper - - def __repr__(self): - return self.__str__() - - def __str__(self): - if self._helper is None: - return str(self._offset) - - result = [] - for index, coeff in zip(self.variable_indices, self.coefficients): - if len(result) >= _MAX_LINEAR_EXPRESSION_REPR_TERMS: - result.append(" + ...") - break - var_name = Variable(self._helper, index, None, None, None).name - if not result and mbn.is_one(coeff): - result.append(var_name) - elif not result and mbn.is_minus_one(coeff): - result.append(f"-{var_name}") - elif not result: - result.append(f"{coeff} * {var_name}") - elif mbn.is_one(coeff): - result.append(f" + {var_name}") - elif mbn.is_minus_one(coeff): - result.append(f" - {var_name}") - elif coeff > 0.0: - result.append(f" + {coeff} * {var_name}") - elif coeff < 0.0: - result.append(f" - {-coeff} * {var_name}") - - if not result: - return f"{self.constant}" - if self.constant > 0: - result.append(f" + {self.constant}") - elif self.constant < 0: - result.append(f" - {-self.constant}") - return "".join(result) - - -def _sum_as_flat_linear_expression( - to_process: List[Tuple[LinearExprT, float]], offset: float = 0.0 -) -> _LinearExpression: - """Creates a _LinearExpression as the sum of terms.""" - indices = [] - coeffs = [] - helper = None - while to_process: # Flatten AST of LinearTypes. - expr, coeff = to_process.pop() - if isinstance(expr, _Sum): - to_process.append((expr._left, coeff)) - to_process.append((expr._right, coeff)) - elif isinstance(expr, Variable): - indices.append([expr.index]) - coeffs.append([coeff]) - if helper is None: - helper = expr.helper - elif mbn.is_a_number(expr): - offset += coeff * cast(NumberT, expr) - elif isinstance(expr, _Product): - to_process.append((expr._expression, coeff * expr._coefficient)) - elif isinstance(expr, _LinearExpression): - offset += coeff * expr._offset - if expr._helper is not None: - indices.append(expr.variable_indices) - coeffs.append(np.multiply(expr.coefficients, coeff)) - if helper is None: - helper = expr._helper - else: - raise TypeError( - "Unrecognized linear expression: " + str(expr) + f" {type(expr)}" - ) - - if helper is not None: - all_indices: npt.NDArray[np.int32] = np.concatenate(indices, axis=0) - all_coeffs: npt.NDArray[np.double] = np.concatenate(coeffs, axis=0) - sorted_indices, sorted_coefficients = helper.sort_and_regroup_terms( - all_indices, all_coeffs - ) - return _LinearExpression(sorted_indices, sorted_coefficients, offset, helper) - else: - assert not indices - assert not coeffs - return _LinearExpression( - _variable_indices=np.zeros(dtype=np.int32, shape=[0]), - _coefficients=np.zeros(dtype=np.double, shape=[0]), - _offset=offset, - _helper=None, - ) - - -def _as_flat_linear_expression(base_expr: LinearExprT) -> _LinearExpression: - """Converts floats, ints and Linear objects to a LinearExpression.""" - if isinstance(base_expr, _LinearExpression): - return base_expr - return _sum_as_flat_linear_expression(to_process=[(base_expr, 1.0)], offset=0.0) - - -@dataclasses.dataclass(repr=False, eq=False, frozen=True) -class _Sum(LinearExpr): - """Represents the (deferred) sum of two expressions.""" - - __slots__ = ("_left", "_right") - - _left: LinearExprT - _right: LinearExprT - - def __repr__(self): - return self.__str__() - - def __str__(self): - return str(_as_flat_linear_expression(self)) - - -@dataclasses.dataclass(repr=False, eq=False, frozen=True) -class _Product(LinearExpr): - """Represents the (deferred) product of an expression by a constant.""" - - __slots__ = ("_expression", "_coefficient") - - _expression: LinearExpr - _coefficient: NumberT - - def __repr__(self): - return self.__str__() - - def __str__(self): - return str(_as_flat_linear_expression(self)) - - def _get_index(obj: _IndexOrSeries) -> pd.Index: """Returns the indices of `obj` as a `pd.Index`.""" if isinstance(obj, pd.Series): diff --git a/ortools/linear_solver/python/model_builder_helper.cc b/ortools/linear_solver/python/model_builder_helper.cc index c83ef804a3..4583f9c8b2 100644 --- a/ortools/linear_solver/python/model_builder_helper.cc +++ b/ortools/linear_solver/python/model_builder_helper.cc @@ -16,9 +16,8 @@ #include "ortools/linear_solver/wrappers/model_builder_helper.h" #include -#include +#include #include -#include #include #include #include @@ -28,12 +27,15 @@ #include "Eigen/Core" #include "Eigen/SparseCore" +#include "absl/hash/hash.h" #include "absl/log/check.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "absl/types/span.h" #include "ortools/base/logging.h" #include "ortools/linear_solver/linear_solver.pb.h" #include "ortools/linear_solver/model_exporter.h" +#include "pybind11/cast.h" #include "pybind11/eigen.h" #include "pybind11/pybind11.h" #include "pybind11/pytypes.h" @@ -42,19 +44,30 @@ using ::Eigen::SparseMatrix; using ::Eigen::VectorXd; -using ::operations_research::ModelBuilderHelper; -using ::operations_research::ModelSolverHelper; using ::operations_research::MPConstraintProto; using ::operations_research::MPModelExportOptions; using ::operations_research::MPModelProto; using ::operations_research::MPModelRequest; using ::operations_research::MPSolutionResponse; using ::operations_research::MPVariableProto; -using ::operations_research::SolveStatus; +using ::operations_research::mb::AffineExpr; +using ::operations_research::mb::BoundedLinearExpression; +using ::operations_research::mb::ExprOrValue; +using ::operations_research::mb::FlatExpression; +using ::operations_research::mb::LinearExpr; +using ::operations_research::mb::ModelBuilderHelper; +using ::operations_research::mb::ModelSolverHelper; +using ::operations_research::mb::SolveStatus; +using ::operations_research::mb::Variable; namespace py = pybind11; using ::py::arg; +void ThrowError(PyObject* py_exception, const std::string& message) { + PyErr_SetString(py_exception, message.c_str()); + throw py::error_already_set(); +} + const MPModelProto& ToMPModelProto(ModelBuilderHelper* helper) { return helper->model(); } @@ -153,9 +166,306 @@ std::vector> SortedGroupedTerms( return terms; } +LinearExpr* SafeWeightedSum(const std::vector& exprs, + const std::vector& coeffs, + double constant = 0.0) { + if (exprs.size() != coeffs.size()) { + ThrowError(PyExc_ValueError, + "The number of expressions and coefficients must match."); + } + return LinearExpr::WeightedSum(exprs, coeffs, constant); +} + +LinearExpr* SafeMixedWeightedSum(const std::vector& exprs, + const std::vector& coeffs, + double constant = 0.0) { + if (exprs.size() != coeffs.size()) { + ThrowError(PyExc_ValueError, + "The number of expressions and coefficients must match."); + } + return LinearExpr::MixedWeightedSum(exprs, coeffs, constant); +} + +const char* kLinearExprClassDoc = R"doc( +Holds an linear expression. + +A linear expression is built from constants and variables. +For example, `x + 2.0 * (y - z + 1.0)`. + +Linear expressions are used in Model models in constraints and in the objective: + + * You can define linear constraints as in: + +``` + model.add(x + 2 * y <= 5.0) + model.add(sum(array_of_vars) == 5.0) +``` + + * In Model, the objective is a linear expression: + +``` + model.minimize(x + 2.0 * y + z) +``` + + * For large arrays, using the LinearExpr class is faster that using the python + `sum()` function. You can create constraints and the objective from lists of + linear expressions or coefficients as follows: + +``` + model.minimize(model_builder.LinearExpr.sum(expressions)) + model.add(model_builder.LinearExpr.weighted_sum(expressions, coeffs) >= 0) +``` +)doc"; + +const char* kVarClassDoc = R"doc(A variable (continuous or integral). + + A Variable is an object that can take on any integer value within defined + ranges. Variables appear in constraint like: + + x + y >= 5 + + Solving a model is equivalent to finding, for each variable, a single value + from the set of initial values (called the initial domain), such that the + model is feasible, or optimal if you provided an objective function. +)doc"; + PYBIND11_MODULE(model_builder_helper, m) { pybind11_protobuf::ImportNativeProtoCasters(); + py::class_(m, "ExprOrValue") + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def_readonly("double_value", &ExprOrValue::value) + .def_readonly("expr", &ExprOrValue::expr); + + py::implicitly_convertible(); + py::implicitly_convertible(); + py::implicitly_convertible(); + + py::class_(m, "LinearExpr", kLinearExprClassDoc) + // We make sure to keep the order of the overloads: LinearExpr* before + // ExprOrValue as this is faster to parse and type check. + .def_static("sum", (&LinearExpr::Sum), arg("exprs"), py::kw_only(), + arg("constant") = 0.0, "Creates `sum(exprs) + constant`.", + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def_static("sum", &LinearExpr::MixedSum, arg("exprs"), py::kw_only(), + arg("constant") = 0.0, "Creates `sum(exprs) + constant`.", + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def_static("weighted_sum", &SafeWeightedSum, arg("exprs"), arg("coeffs"), + py::kw_only(), arg("constant") = 0.0, + "Creates `sum(expressions[i] * coefficients[i]) + constant`.", + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def_static("weighted_sum", &SafeMixedWeightedSum, arg("exprs"), + arg("coeffs"), py::kw_only(), arg("constant") = 0.0, + "Creates `sum(expressions[i] * coefficients[i]) + constant`.", + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def_static("term", &LinearExpr::Term, arg("expr").none(false), + arg("coeff"), "Returns expr * coeff.", + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + // Compatibility layer. + .def_static("term", &LinearExpr::Affine, arg("expr").none(false), + arg("coeff"), py::kw_only(), py::arg("constant"), + "Returns expr * coeff [+ constant].", + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def_static("term", &LinearExpr::AffineCst, arg("value"), arg("coeff"), + py::kw_only(), py::arg("constant"), + "Returns value * coeff [+ constant].", + py::return_value_policy::automatic) + .def_static("affine", &LinearExpr::Affine, arg("expr").none(false), + arg("coeff"), arg("constant") = 0.0, + "Returns expr * coeff + constant.", + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def_static("affine", &LinearExpr::AffineCst, arg("value"), arg("coeff"), + arg("constant") = 0.0, "Returns value * coeff + constant.", + py::return_value_policy::automatic) + .def_static("constant", &LinearExpr::Constant, arg("value"), + "Returns a constant linear expression.", + py::return_value_policy::automatic) + // Methods. + .def("__str__", &LinearExpr::ToString) + .def("__repr__", &LinearExpr::DebugString) + // Operators. + // Note that we keep the 3 APIS (expr, int, double) instead of using an + // ExprOrValue argument as this is more efficient. + .def("__add__", &LinearExpr::Add, arg("other").none(false), + py::return_value_policy::automatic, py::keep_alive<0, 1>(), + py::keep_alive<0, 2>()) + .def("__add__", &LinearExpr::AddFloat, arg("cst"), + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def("__radd__", &LinearExpr::AddFloat, arg("cst"), + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def("__sub__", &LinearExpr::Sub, arg("other").none(false), + py::return_value_policy::automatic, py::keep_alive<0, 1>(), + py::keep_alive<0, 2>()) + .def("__sub__", &LinearExpr::SubFloat, arg("cst"), + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def("__rsub__", &LinearExpr::RSubFloat, arg("cst"), + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def("__mul__", &LinearExpr::MulFloat, arg("cst"), + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def("__rmul__", &LinearExpr::MulFloat, arg("cst"), + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def( + "__truediv__", + [](LinearExpr* self, double cst) { + if (cst == 0.0) { + ThrowError(PyExc_ZeroDivisionError, + "Division by zero is not supported."); + } + return self->MulFloat(1.0 / cst); + }, + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def("__neg__", &LinearExpr::Neg, py::return_value_policy::automatic, + py::keep_alive<0, 1>()) + // Comparison operators. + .def("__eq__", &LinearExpr::Eq, arg("other").none(false), + "Creates the constraint `self == other`.", + py::return_value_policy::automatic, py::keep_alive<0, 1>(), + py::keep_alive<0, 2>()) + .def("__eq__", &LinearExpr::EqCst, arg("cst"), + "Creates the constraint `self == cst`.", + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def("__le__", &LinearExpr::Le, arg("other").none(false), + "Creates the constraint `self <= other`.", + py::return_value_policy::automatic, py::keep_alive<0, 1>(), + py::keep_alive<0, 2>()) + .def("__le__", &LinearExpr::LeCst, arg("cst"), + "Creates the constraint `self <= cst`.", + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + .def("__ge__", &LinearExpr::Ge, arg("other").none(false), + "Creates the constraint `self >= other`.", + py::return_value_policy::automatic, py::keep_alive<0, 1>(), + py::keep_alive<0, 2>()) + .def("__ge__", &LinearExpr::GeCst, arg("cst"), + "Creates the constraint `self >= cst`.", + py::return_value_policy::automatic, py::keep_alive<0, 1>()) + // Disable other operators as they are not supported. + .def("__floordiv__", + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { + ThrowError(PyExc_NotImplementedError, + "calling // on a linear expression is not supported."); + }) + .def("__mod__", + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { + ThrowError(PyExc_NotImplementedError, + "calling %% on a linear expression is not supported."); + }) + .def("__pow__", + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { + ThrowError(PyExc_NotImplementedError, + "calling ** on a linear expression is not supported."); + }) + .def("__lshift__", + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { + ThrowError( + PyExc_NotImplementedError, + "calling left shift on a linear expression is not supported"); + }) + .def("__rshift__", + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { + ThrowError( + PyExc_NotImplementedError, + "calling right shift on a linear expression is not supported"); + }) + .def("__and__", + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { + ThrowError(PyExc_NotImplementedError, + "calling and on a linear expression is not supported"); + }) + .def("__or__", + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { + ThrowError(PyExc_NotImplementedError, + "calling or on a linear expression is not supported"); + }) + .def("__xor__", + [](LinearExpr* /*self*/, ExprOrValue /*other*/) { + ThrowError(PyExc_NotImplementedError, + "calling xor on a linear expression is not supported"); + }) + .def("__abs__", + [](LinearExpr* /*self*/) { + ThrowError( + PyExc_NotImplementedError, + "calling abs() on a linear expression is not supported."); + }) + .def("__bool__", [](LinearExpr* /*self*/) { + ThrowError(PyExc_NotImplementedError, + "Evaluating a LinearExpr instance as a Boolean is " + "not supported."); + }); + + // Expose Internal classes, mostly for testing. + py::class_(m, "FlatExpression") + .def(py::init()) + .def(py::init()) + .def(py::init&, + const std::vector&, double>(), + py::keep_alive<1, 2>()) + .def(py::init()) + .def_property_readonly("vars", &FlatExpression::vars) + .def("variable_indices", &FlatExpression::VarIndices) + .def_property_readonly("coeffs", &FlatExpression::coeffs) + .def_property_readonly("offset", &FlatExpression::offset); + + py::class_(m, "AffineExpr") + .def(py::init()) + .def_property_readonly("expression", &AffineExpr ::expression) + .def_property_readonly("coefficient", &AffineExpr::coefficient) + .def_property_readonly("offset", &AffineExpr::offset); + + py::class_(m, "Variable", kVarClassDoc) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def_property_readonly("index", &Variable::index, + "The index of the variable in the model.") + .def_property_readonly("helper", &Variable::helper, + "The ModelBuilderHelper instance.") + .def_property("name", &Variable::name, &Variable::SetName, + "The name of the variable in the model.") + .def_property("lower_bound", &Variable::lower_bounds, + &Variable::SetLowerBound) + .def_property("upper_bound", &Variable::upper_bound, + &Variable::SetUpperBound) + .def_property("is_integral", &Variable::is_integral, + &Variable::SetIsIntegral) + .def_property("objective_coefficient", &Variable::objective_coefficient, + &Variable::SetObjectiveCoefficient) + .def("__str__", &Variable::ToString) + .def("__repr__", &Variable::DebugString) + .def("__hash__", [](const Variable& self) { + return absl::HashOf(std::make_tuple(self.helper(), self.index())); + }); + + py::class_(m, "BoundedLinearExpression") + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def_property_readonly("vars", &BoundedLinearExpression::vars) + .def_property_readonly("coeffs", &BoundedLinearExpression::coeffs) + .def_property_readonly("lower_bound", + &BoundedLinearExpression::lower_bound) + .def_property_readonly("upper_bound", + &BoundedLinearExpression::upper_bound) + .def("__bool__", + [](const BoundedLinearExpression& self) { + bool result; + if (self.CastToBool(&result)) return result; + ThrowError(PyExc_NotImplementedError, + absl::StrCat("Evaluating a BoundedLinearExpression '", + self.ToString(), + "'instance as a Boolean is " + "not supported.") + .c_str()); + return false; + }) + .def("__str__", &BoundedLinearExpression::ToString) + .def("__repr__", &BoundedLinearExpression::DebugString); + m.def("to_mpmodel_proto", &ToMPModelProto, arg("helper")); py::class_(m, "MPModelExportOptions") @@ -314,11 +624,11 @@ PYBIND11_MODULE(model_builder_helper, m) { arg("ct_index"), arg("var_index"), arg("coeff")) .def("add_terms_to_constraint", [](ModelBuilderHelper* helper, int ct_index, - const std::vector& indices, + const std::vector& vars, const std::vector& coefficients) { - for (const auto& [i, c] : - SortedGroupedTerms(indices, coefficients)) { - helper->AddConstraintTerm(ct_index, i, c); + for (int i = 0; i < vars.size(); ++i) { + helper->AddConstraintTerm(ct_index, vars[i]->index(), + coefficients[i]); } }) .def("safe_add_term_to_constraint", @@ -354,11 +664,11 @@ PYBIND11_MODULE(model_builder_helper, m) { arg("var_index"), arg("coeff")) .def("add_terms_to_enforced_constraint", [](ModelBuilderHelper* helper, int ct_index, - const std::vector& indices, + const std::vector& vars, const std::vector& coefficients) { - for (const auto& [i, c] : - SortedGroupedTerms(indices, coefficients)) { - helper->AddEnforcedConstraintTerm(ct_index, i, c); + for (int i = 0; i < vars.size(); ++i) { + helper->AddEnforcedConstraintTerm(ct_index, vars[i]->index(), + coefficients[i]); } }) .def("safe_add_term_to_enforced_constraint", @@ -402,22 +712,7 @@ PYBIND11_MODULE(model_builder_helper, m) { .def("objective_offset", &ModelBuilderHelper::ObjectiveOffset) .def("clear_hints", &ModelBuilderHelper::ClearHints) .def("add_hint", &ModelBuilderHelper::AddHint, arg("var_index"), - arg("var_value")) - .def("sort_and_regroup_terms", - [](ModelBuilderHelper* helper, py::array_t indices, - py::array_t coefficients) { - const std::vector> terms = - SortedGroupedTerms(indices, coefficients); - std::vector sorted_indices; - std::vector sorted_coefficients; - sorted_indices.reserve(terms.size()); - sorted_coefficients.reserve(terms.size()); - for (const auto& [i, c] : terms) { - sorted_indices.push_back(i); - sorted_coefficients.push_back(c); - } - return std::make_pair(sorted_indices, sorted_coefficients); - }); + arg("var_value")); py::enum_(m, "SolveStatus") .value("OPTIMAL", SolveStatus::OPTIMAL) @@ -483,7 +778,16 @@ PYBIND11_MODULE(model_builder_helper, m) { .def("user_time", &ModelSolverHelper::user_time) .def("objective_value", &ModelSolverHelper::objective_value) .def("best_objective_bound", &ModelSolverHelper::best_objective_bound) - .def("var_value", &ModelSolverHelper::variable_value, arg("var_index")) + .def("variable_value", &ModelSolverHelper::variable_value, + arg("var_index")) + .def("expression_value", + [](const ModelSolverHelper& helper, LinearExpr* expr) { + if (!helper.has_response()) { + throw std::logic_error( + "Accessing a solution value when none has been found."); + } + return helper.expression_value(expr); + }) .def("reduced_cost", &ModelSolverHelper::reduced_cost, arg("var_index")) .def("dual_value", &ModelSolverHelper::dual_value, arg("ct_index")) .def("activity", &ModelSolverHelper::activity, arg("ct_index")) @@ -500,20 +804,6 @@ PYBIND11_MODULE(model_builder_helper, m) { } return vec; }) - .def("expression_value", - [](const ModelSolverHelper& helper, const std::vector& indices, - const std::vector& coefficients, double constant) { - if (!helper.has_response()) { - throw std::logic_error( - "Accessing a solution value when none has been found."); - } - const MPSolutionResponse& response = helper.response(); - for (int i = 0; i < indices.size(); ++i) { - constant += - response.variable_value(indices[i]) * coefficients[i]; - } - return constant; - }) .def("reduced_costs", [](const ModelSolverHelper& helper) { if (!helper.has_response()) { @@ -539,4 +829,4 @@ PYBIND11_MODULE(model_builder_helper, m) { } return vec; }); -} +} // NOLINT(readability/fn_size) diff --git a/ortools/linear_solver/python/model_builder_helper_test.py b/ortools/linear_solver/python/model_builder_helper_test.py index a2b143a392..4f0dd7e784 100644 --- a/ortools/linear_solver/python/model_builder_helper_test.py +++ b/ortools/linear_solver/python/model_builder_helper_test.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Tests for model_builder_helper.""" - import gzip import os import threading @@ -97,7 +95,7 @@ class PywrapModelBuilderHelperTest(absltest.TestCase): linear_solver_pb2.MPSolverResponseStatus.MPSOLVER_OPTIMAL, ) self.assertAlmostEqual(solver.objective_value(), 1.0) - self.assertAlmostEqual(solver.var_value(0), 1.0) + self.assertAlmostEqual(solver.variable_value(0), 1.0) values = solver.variable_values() self.assertEqual(1, len(values)) self.assertAlmostEqual(1.0, values[0]) diff --git a/ortools/linear_solver/python/model_builder_test.py b/ortools/linear_solver/python/model_builder_test.py index cf721af4dd..ce73622655 100644 --- a/ortools/linear_solver/python/model_builder_test.py +++ b/ortools/linear_solver/python/model_builder_test.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Tests for ModelBuilder.""" - import math from typing import Any, Callable, Dict, Mapping, Union @@ -28,15 +26,18 @@ import os from google.protobuf import text_format from ortools.linear_solver import linear_solver_pb2 from ortools.linear_solver.python import model_builder as mb +from ortools.linear_solver.python import model_builder_helper as mbh -def build_dict(expr: mb.LinearExprT) -> Dict[mb.Variable, float]: +def build_dict(expr: mb.LinearExprT) -> Dict[mbh.Variable, float]: res = {} - for i, c in zip(expr.variable_indices, expr.coefficients): - if not c: + flat_expr = mbh.FlatExpression(expr) + print(f"expr = {expr} flat_expr = {flat_expr}", flush=True) + for var, coeff in zip(flat_expr.vars, flat_expr.coeffs): + print(f"process {var} * {coeff}", flush=True) + if not coeff: continue - var = mb.Variable(expr.helper, lb=i, ub=None, is_integral=None, name=None) - res[var] = c + res[var] = coeff return res @@ -208,66 +209,75 @@ ENDATA t = model.new_int_var(3, 10, "t") e1 = mb.LinearExpr.sum([x, y, z]) + flat_e1 = mbh.FlatExpression(e1) expected_vars = np.array([0, 1, 2], dtype=np.int32) - np_testing.assert_array_equal(expected_vars, e1.variable_indices) + np_testing.assert_array_equal(expected_vars, flat_e1.variable_indices()) np_testing.assert_array_equal( - np.array([1, 1, 1], dtype=np.double), e1.coefficients + np.array([1, 1, 1], dtype=np.double), flat_e1.coeffs ) - self.assertEqual(e1.constant, 0.0) - self.assertEqual(e1.__str__(), "x + y + z") + self.assertEqual(flat_e1.offset, 0.0) + self.assertEqual(e1.__str__(), "(x + y + z)") e2 = mb.LinearExpr.sum([e1, 4.0]) - np_testing.assert_array_equal(expected_vars, e2.variable_indices) + flat_e2 = mbh.FlatExpression(e2) + np_testing.assert_array_equal(expected_vars, flat_e2.variable_indices()) np_testing.assert_array_equal( - np.array([1, 1, 1], dtype=np.double), e2.coefficients + np.array([1, 1, 1], dtype=np.double), flat_e2.coeffs ) - self.assertEqual(e2.constant, 4.0) - self.assertEqual(e2.__str__(), "x + y + z + 4.0") + self.assertEqual(flat_e2.offset, 4.0) + self.assertEqual(e2.__str__(), "((x + y + z) + 4)") + self.assertEqual(flat_e2.__str__(), "x + y + z + 4") e3 = mb.LinearExpr.term(e2, 2) - np_testing.assert_array_equal(expected_vars, e3.variable_indices) + flat_e3 = mbh.FlatExpression(e3) + np_testing.assert_array_equal(expected_vars, flat_e3.variable_indices()) np_testing.assert_array_equal( - np.array([2, 2, 2], dtype=np.double), e3.coefficients + np.array([2, 2, 2], dtype=np.double), flat_e3.coeffs ) - self.assertEqual(e3.constant, 8.0) - self.assertEqual(e3.__str__(), "2.0 * x + 2.0 * y + 2.0 * z + 8.0") + self.assertEqual(flat_e3.offset, 8.0) + self.assertEqual(e3.__str__(), "(2 * ((x + y + z) + 4))") + self.assertEqual(flat_e3.__str__(), "2 * x + 2 * y + 2 * z + 8") e4 = mb.LinearExpr.weighted_sum([x, t], [-1, 1], constant=2) + flat_e4 = mbh.FlatExpression(e4) np_testing.assert_array_equal( - np.array([0, 3], dtype=np.int32), e4.variable_indices + np.array([0, 3], dtype=np.int32), flat_e4.variable_indices() ) np_testing.assert_array_equal( - np.array([-1, 1], dtype=np.double), e4.coefficients + np.array([-1, 1], dtype=np.double), flat_e4.coeffs ) - self.assertEqual(e4.constant, 2.0) - self.assertEqual(e4.__str__(), "-x + t + 2.0") + self.assertEqual(flat_e4.offset, 2.0) + self.assertEqual(e4.__str__(), "(-x + t + 2)") e4b = mb.LinearExpr.weighted_sum([e4 * 3], [1]) + flat_e4b = mbh.FlatExpression(e4b) np_testing.assert_array_equal( - np.array([0, 3], dtype=np.int32), e4b.variable_indices + np.array([0, 3], dtype=np.int32), flat_e4b.variable_indices() ) np_testing.assert_array_equal( - np.array([-3, 3], dtype=np.double), e4b.coefficients + np.array([-3, 3], dtype=np.double), flat_e4b.coeffs ) - self.assertEqual(e4b.constant, 6.0) - self.assertEqual(e4b.__str__(), "-3.0 * x + 3.0 * t + 6.0") + self.assertEqual(flat_e4b.offset, 6.0) + self.assertEqual(e4b.__str__(), "(3 * (-x + t + 2))") e5 = mb.LinearExpr.sum([e1, -3, e4]) + flat_e5 = mbh.FlatExpression(e5) np_testing.assert_array_equal( - np.array([1, 2, 3], dtype=np.int32), e5.variable_indices + np.array([1, 2, 3], dtype=np.int32), flat_e5.variable_indices() ) np_testing.assert_array_equal( - np.array([1, 1, 1], dtype=np.double), e5.coefficients + np.array([1, 1, 1], dtype=np.double), flat_e5.coeffs ) - self.assertEqual(e5.constant, -1.0) - self.assertEqual(e5.__str__(), "y + z + t - 1.0") + self.assertEqual(flat_e5.offset, -1.0) + self.assertEqual(flat_e5.__str__(), "y + z + t - 1") e6 = mb.LinearExpr.term(x, 2.0, constant=1.0) + flat_e6 = mbh.FlatExpression(e6) np_testing.assert_array_equal( - np.array([0], dtype=np.int32), e6.variable_indices + np.array([0], dtype=np.int32), flat_e6.variable_indices() ) - np_testing.assert_array_equal(np.array([2], dtype=np.double), e6.coefficients) - self.assertEqual(e6.constant, 1.0) + np_testing.assert_array_equal(np.array([2], dtype=np.double), flat_e6.coeffs) + self.assertEqual(flat_e6.offset, 1.0) e7 = mb.LinearExpr.term(x, 1.0, constant=0.0) self.assertEqual(x, e7) @@ -278,8 +288,8 @@ ENDATA e9 = mb.LinearExpr.term(x * 2 + 3, 1, constant=0) e10 = mb.LinearExpr.term(x, 2, constant=3) self.assertEqual( - str(mb._as_flat_linear_expression(e9)), - str(mb._as_flat_linear_expression(e10)), + str(mbh.FlatExpression(e9)), + str(mbh.FlatExpression(e10)), ) def test_variables(self): @@ -377,14 +387,6 @@ ENDATA status = solver.solve(model) self.assertEqual(mb.SolveStatus.OPTIMAL, status) - def test_vareqvar(self): - model = mb.Model() - x = model.new_int_var(0.0, 4.0, "x") - y = model.new_int_var(0.0, 4.0, "y") - ct = x == y - self.assertEqual(ct.left.index, x.index) - self.assertEqual(ct.right.index, y.index) - def test_create_false_ct(self): # Create the model. model = mb.Model() @@ -424,7 +426,7 @@ class InternalHelperTest(absltest.TestCase): def test_anonymous_variables(self): helper = mb.Model().helper index = helper.add_var() - variable = mb.Variable(helper, index, None, None, None) + variable = mb.Variable(helper, index) self.assertEqual(variable.name, f"variable#{index}") def test_anonymous_constraints(self): @@ -452,178 +454,175 @@ class LinearBaseTest(parameterized.TestCase): dict( testcase_name="x[0]", expr=lambda x, y: x[0], - expected_repr="x[0]", + expected_str="x[0]", ), dict( testcase_name="x[1]", expr=lambda x, y: x[1], - expected_repr="x[1]", + expected_str="x[1]", ), dict( testcase_name="x[2]", expr=lambda x, y: x[2], - expected_repr="x[2]", + expected_str="x[2]", ), dict( testcase_name="y[0]", expr=lambda x, y: y[0], - expected_repr="y[0]", + expected_str="y[0]", ), dict( testcase_name="y[4]", expr=lambda x, y: y[4], - expected_repr="y[4]", + expected_str="y[4]", ), # Sum dict( testcase_name="x[0] + 5", expr=lambda x, y: x[0] + 5, - expected_repr="x[0] + 5.0", + expected_str="x[0] + 5", ), dict( testcase_name="x[0] - 5", expr=lambda x, y: x[0] - 5, - expected_repr="x[0] - 5.0", + expected_str="x[0] - 5", ), dict( testcase_name="5 - x[0]", expr=lambda x, y: 5 - x[0], - expected_repr="-x[0] + 5.0", + expected_str="-x[0] + 5", ), dict( testcase_name="5 + x[0]", expr=lambda x, y: 5 + x[0], - expected_repr="x[0] + 5.0", + expected_str="x[0] + 5", ), dict( testcase_name="x[0] + y[0]", expr=lambda x, y: x[0] + y[0], - expected_repr="x[0] + y[0]", + expected_str="x[0] + y[0]", ), dict( testcase_name="x[0] + y[0] + 5", expr=lambda x, y: x[0] + y[0] + 5, - expected_repr="x[0] + y[0] + 5.0", + expected_str="x[0] + y[0] + 5", ), dict( testcase_name="5 + x[0] + y[0]", expr=lambda x, y: 5 + x[0] + y[0], - expected_repr="x[0] + y[0] + 5.0", + expected_str="x[0] + y[0] + 5", ), dict( testcase_name="5 + x[0] - x[0]", expr=lambda x, y: 5 + x[0] - x[0], - expected_repr="5.0", + expected_str="5", ), dict( testcase_name="5 + x[0] - y[0]", expr=lambda x, y: 5 + x[0] - y[0], - expected_repr="x[0] - y[0] + 5.0", + expected_str="x[0] - y[0] + 5", ), dict( testcase_name="x.sum()", expr=lambda x, y: x.sum(), - expected_repr="x[0] + x[1] + x[2]", + expected_str="x[0] + x[1] + x[2]", ), dict( testcase_name="x.add(y, fill_value=0).sum() + 5", expr=lambda x, y: x.add(y, fill_value=0).sum() + 5, - expected_repr="x[0] + x[1] + x[2] + y[0] + y[1] + ... + 5.0", + expected_str="x[0] + x[1] + x[2] + y[0] + y[1] + ... + 5", ), dict( testcase_name="sum(x, y + 5)", expr=lambda x, y: mb.LinearExpr.sum([x.sum(), y.sum() + 5]), - expected_repr="x[0] + x[1] + x[2] + y[0] + y[1] + ... + 5.0", + expected_str="x[0] + x[1] + x[2] + y[0] + y[1] + ... + 5", ), # Product dict( testcase_name="- x.sum()", expr=lambda x, y: -x.sum(), - expected_repr="-x[0] - x[1] - x[2]", + expected_str="-x[0] - x[1] - x[2]", ), dict( testcase_name="5 - x.sum()", expr=lambda x, y: 5 - x.sum(), - expected_repr="-x[0] - x[1] - x[2] + 5.0", + expected_str="-x[0] - x[1] - x[2] + 5", ), dict( - testcase_name="x.sum() / 2.0", - expr=lambda x, y: x.sum() / 2.0, - expected_repr="0.5 * x[0] + 0.5 * x[1] + 0.5 * x[2]", + testcase_name="x.sum() / 2", + expr=lambda x, y: x.sum() / 2, + expected_str="0.5 * x[0] + 0.5 * x[1] + 0.5 * x[2]", ), dict( testcase_name="(3 * x).sum()", expr=lambda x, y: (3 * x).sum(), - expected_repr="3.0 * x[0] + 3.0 * x[1] + 3.0 * x[2]", + expected_str="3 * x[0] + 3 * x[1] + 3 * x[2]", ), dict( testcase_name="(x * 3).sum()", expr=lambda x, y: (x * 3).sum(), - expected_repr="3.0 * x[0] + 3.0 * x[1] + 3.0 * x[2]", + expected_str="3 * x[0] + 3 * x[1] + 3 * x[2]", ), dict( testcase_name="x.sum() * 3", expr=lambda x, y: x.sum() * 3, - expected_repr="3.0 * x[0] + 3.0 * x[1] + 3.0 * x[2]", + expected_str="3 * x[0] + 3 * x[1] + 3 * x[2]", ), dict( testcase_name="3 * x.sum()", expr=lambda x, y: 3 * x.sum(), - expected_repr="3.0 * x[0] + 3.0 * x[1] + 3.0 * x[2]", + expected_str="3 * x[0] + 3 * x[1] + 3 * x[2]", ), dict( testcase_name="0 * x.sum() + y.sum()", expr=lambda x, y: 0 * x.sum() + y.sum(), - expected_repr="y[0] + y[1] + y[2] + y[3] + y[4]", + expected_str="y[0] + y[1] + y[2] + y[3] + y[4]", ), # LinearExpression dict( - testcase_name="_as_flat_linear_expression(x.sum())", - expr=lambda x, y: mb._as_flat_linear_expression(x.sum()), - expected_repr="x[0] + x[1] + x[2]", + testcase_name="FlatExpression(x.sum())", + expr=lambda x, y: mbh.FlatExpression(x.sum()), + expected_str="x[0] + x[1] + x[2]", ), dict( - testcase_name=( - "_as_flat_linear_expression(_as_flat_linear_expression(x.sum()))" - ), + testcase_name="FlatExpression(FlatExpression(x.sum()))", # pylint: disable=g-long-lambda - expr=lambda x, y: mb._as_flat_linear_expression( - mb._as_flat_linear_expression(x.sum()) - ), - expected_repr="x[0] + x[1] + x[2]", + expr=lambda x, y: mbh.FlatExpression(mbh.FlatExpression(x.sum())), + expected_str="x[0] + x[1] + x[2]", ), dict( - testcase_name="""_as_flat_linear_expression(sum([ - _as_flat_linear_expression(x.sum()), - _as_flat_linear_expression(x.sum()), + testcase_name="""FlatExpression(sum([ + FlatExpression(x.sum()), + FlatExpression(x.sum()), ]))""", # pylint: disable=g-long-lambda - expr=lambda x, y: mb._as_flat_linear_expression( + expr=lambda x, y: mbh.FlatExpression( sum( [ - mb._as_flat_linear_expression(x.sum()), - mb._as_flat_linear_expression(x.sum()), + mbh.FlatExpression(x.sum()), + mbh.FlatExpression(x.sum()), ] ) ), - expected_repr="2.0 * x[0] + 2.0 * x[1] + 2.0 * x[2]", + expected_str="2 * x[0] + 2 * x[1] + 2 * x[2]", ), ) - def test_repr(self, expr, expected_repr): + def test_str(self, expr, expected_str): x = self.x y = self.y - self.assertEqual(repr(expr(x, y)), expected_repr) + self.assertEqual(str(mbh.FlatExpression(expr(x, y))), expected_str) class LinearBaseErrorsTest(absltest.TestCase): def test_unknown_linear_type(self): - with self.assertRaisesRegex(TypeError, r"Unrecognized linear expression"): + with self.assertRaises(TypeError): class UnknownLinearType(mb.LinearExpr): - pass + def __init__(self): + mb.LinearExpr.__init__(self) - mb._as_flat_linear_expression(UnknownLinearType()) + mbh.FlatExpression(UnknownLinearType()) def test_division_by_zero(self): with self.assertRaises(ZeroDivisionError): @@ -632,7 +631,7 @@ class LinearBaseErrorsTest(absltest.TestCase): print(x / 0) def test_boolean_expression(self): - with self.assertRaisesRegex(NotImplementedError, r"Cannot use a LinearExpr"): + with self.assertRaisesRegex(NotImplementedError, r"instance as a Boolean"): model = mb.Model() x = model.new_var_series(name="x", index=pd.Index(range(1))) bool(x.sum()) @@ -688,28 +687,26 @@ class BoundedLinearBaseTest(parameterized.TestCase): lambda lhs, rhs: lhs >= rhs, ), ) - def test_repr(self, lhs, rhs, op): + def test_str(self, lhs, rhs, op): x = self.x y = self.y l: mb.LinearExprT = lhs(x, y) r: mb.LinearExprT = rhs(x, y) result = op(l, r) if isinstance(l, mb.LinearExpr) or isinstance(r, mb.LinearExpr): - self.assertIsInstance(result, mb._BoundedLinearExpr) - self.assertIn("=", repr(result), msg="is one of ==, <=, or >=") + self.assertIsInstance(result, mbh.BoundedLinearExpression) + self.assertIn("=", str(result), msg="is one of ==, <=, or >=") else: self.assertIsInstance(result, bool) def test_doublesided_bounded_expressions(self): x = self.x - self.assertEqual( - "0.0 <= x[0] <= 1.0", repr(mb.BoundedLinearExpression(x[0], 0, 1)) - ) + self.assertEqual("0 <= x[0] <= 1", str(mb.BoundedLinearExpression(x[0], 0, 1))) def test_free_bounded_expressions(self): self.assertEqual( - "x[0] free", - repr(mb.BoundedLinearExpression(self.x[0], -math.inf, math.inf)), + "-inf <= x[0] <= inf", + str(mb.BoundedLinearExpression(self.x[0], -math.inf, math.inf)), ) def test_var_eq_var_as_bool(self): @@ -734,8 +731,16 @@ class BoundedLinearBaseTest(parameterized.TestCase): class BoundedLinearBaseErrorsTest(absltest.TestCase): + def test_single_var_bounded_linear_expression_as_bool(self): + with self.assertRaisesRegex( + NotImplementedError, "Evaluating a BoundedLinearExpression" + ): + model = mb.Model() + x = model.new_bool_var(name="x") + bool(mb.BoundedLinearExpression(x, 0, 1)) + def test_bounded_linear_expression_as_bool(self): - with self.assertRaisesRegex(NotImplementedError, "Boolean value"): + with self.assertRaisesRegex(TypeError, "incompatible constructor arguments"): model = mb.Model() x = model.new_var_series(name="x", index=pd.Index(range(1))) bool(mb.BoundedLinearExpression(x, 0, 1)) @@ -896,7 +901,7 @@ class ModelBuilderVariablesTest(parameterized.TestCase): self.assertLen(variables, len(index)) self.assertLen(set(variables), len(index)) for i in index: - self.assertEqual(repr(variables[i]), f"test_variable[{i}]") + self.assertEqual(variables[i].name, f"test_variable[{i}]") @parameterized.product( index=_variable_indices, bounds=_bounds, is_integer=_is_integer @@ -1440,7 +1445,7 @@ class ModelBuilderLinearConstraintsTest(parameterized.TestCase): for expr, expr_term in zip(linear_constraint_expressions, expr_terms): self.assertDictEqual(build_dict(expr), expr_term) self.assertSequenceAlmostEqual( - [expr._offset for expr in linear_constraint_expressions], + [expr.offset for expr in linear_constraint_expressions], expression_offsets, ) @@ -1473,19 +1478,22 @@ class ModelBuilderObjectiveTest(parameterized.TestCase): def assertLinearExpressionAlmostEqual( self, - expr1: mb._LinearExpression, - expr2: mb._LinearExpression, + expr1: mbh.LinearExpr, + expr2: mbh.LinearExpr, ) -> None: """Test that the two linear expressions are almost equal.""" - self.assertEqual(len(expr1.variable_indices), len(expr2.variable_indices)) - if len(expr1.variable_indices) > 0: # pylint: disable=g-explicit-length-test - self.assertSequenceEqual(expr1.variable_indices, expr2.variable_indices) + flat_expr1 = mbh.FlatExpression(expr1) + flat_expr2 = mbh.FlatExpression(expr2) + self.assertEqual(len(flat_expr1.vars), len(flat_expr2.vars)) + if len(flat_expr1.vars) > 0: # pylint: disable=g-explicit-length-test + self.assertSequenceEqual(flat_expr1.vars, flat_expr2.vars) self.assertSequenceAlmostEqual( - expr1.coefficients, expr2.coefficients, places=5 + flat_expr1.coeffs, flat_expr2.coeffs, places=5 ) else: - self.assertEmpty(expr2.coefficients) - self.assertAlmostEqual(expr1._offset, expr2._offset) + self.assertEmpty(flat_expr1.coeffs) + self.assertEmpty(flat_expr2.coeffs) + self.assertAlmostEqual(flat_expr1.offset, flat_expr2.offset) @parameterized.product( expression=_expressions, @@ -1501,7 +1509,8 @@ class ModelBuilderObjectiveTest(parameterized.TestCase): model = mb.Model() x = model.new_var_series(name="x", index=variable_indices) y = model.new_var_series(name="y", index=variable_indices) - objective_expression = mb._as_flat_linear_expression(expression(x, y)) + objective_expression = expression(x, y) + print(f"objective_expression: {objective_expression}") if is_maximize: model.maximize(objective_expression) else: @@ -1515,14 +1524,14 @@ class ModelBuilderObjectiveTest(parameterized.TestCase): model = mb.Model() x = model.new_var_series(name="x", index=pd.Index(range(3))) old_objective_expression = 1 - new_objective_expression = mb._as_flat_linear_expression(x.sum() - 2.3) + new_objective_expression = x.sum() - 2.3 # Set and check for old objective. model.maximize(old_objective_expression) - got_objective_expression = model.objective_expression() - for var_coeff in got_objective_expression.coefficients: - self.assertAlmostEqual(var_coeff, 0) - self.assertAlmostEqual(got_objective_expression._offset, 1) + flat_got_objective_expression = mbh.FlatExpression(model.objective_expression()) + self.assertEmpty(flat_got_objective_expression.vars) + self.assertEmpty(flat_got_objective_expression.coeffs) + self.assertAlmostEqual(flat_got_objective_expression.offset, 1) # Set to a new objective and check that it is different. model.minimize(new_objective_expression) @@ -1543,7 +1552,7 @@ class ModelBuilderObjectiveTest(parameterized.TestCase): model = mb.Model() x = model.new_var_series(name="x", index=variable_indices) y = model.new_var_series(name="y", index=variable_indices) - objective_expression = mb._as_flat_linear_expression(expression(x, y)) + objective_expression = mbh.FlatExpression(expression(x, y)) model.minimize(objective_expression) got_objective_expression = model.objective_expression() self.assertLinearExpressionAlmostEqual( @@ -1562,7 +1571,7 @@ class ModelBuilderObjectiveTest(parameterized.TestCase): model = mb.Model() x = model.new_var_series(name="x", index=variable_indices) y = model.new_var_series(name="y", index=variable_indices) - objective_expression = mb._as_flat_linear_expression(expression(x, y)) + objective_expression = mbh.FlatExpression(expression(x, y)) model.maximize(objective_expression) got_objective_expression = model.objective_expression() self.assertLinearExpressionAlmostEqual( diff --git a/ortools/linear_solver/wrappers/model_builder_helper.cc b/ortools/linear_solver/wrappers/model_builder_helper.cc index 3f2c77114e..cada9f9836 100644 --- a/ortools/linear_solver/wrappers/model_builder_helper.cc +++ b/ortools/linear_solver/wrappers/model_builder_helper.cc @@ -14,6 +14,7 @@ #include "ortools/linear_solver/wrappers/model_builder_helper.h" #include +#include #include #include #include @@ -23,6 +24,8 @@ #include "absl/log/check.h" #include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" #include "ortools/base/helpers.h" #include "ortools/base/logging.h" #include "ortools/base/options.h" @@ -51,6 +54,9 @@ #include "ortools/xpress/environment.h" namespace operations_research { +namespace mb { + +// ModelBuilderHelper. void ModelBuilderHelper::OverwriteModel( const ModelBuilderHelper& other_helper) { @@ -514,7 +520,8 @@ SolveStatus MPSolverResponseStatusToSolveStatus(MPSolverResponseStatus s) { } } // namespace -ModelSolverHelper::ModelSolverHelper(const std::string& solver_name) { +ModelSolverHelper::ModelSolverHelper(const std::string& solver_name) + : evaluator_(this) { if (solver_name.empty()) return; MPSolver::OptimizationProblemType parsed_type; if (!MPSolver::ParseSolverType(solver_name, &parsed_type)) { @@ -709,6 +716,13 @@ double ModelSolverHelper::variable_value(int var_index) const { return response_.value().variable_value(var_index); } +double ModelSolverHelper::expression_value(LinearExpr* expr) const { + if (!has_response()) return 0.0; + evaluator_.Clear(); + evaluator_.AddToProcess(expr, 1.0); + return evaluator_.Evaluate(); +} + double ModelSolverHelper::reduced_cost(int var_index) const { if (!has_response()) return 0.0; if (var_index >= response_.value().reduced_cost_size()) return 0.0; @@ -768,4 +782,634 @@ void ModelSolverHelper::SetSolverSpecificParameters( void ModelSolverHelper::EnableOutput(bool enabled) { solver_output_ = enabled; } +// Expressions. + +LinearExpr* LinearExpr::Sum(const std::vector& exprs, + double constant) { + if (exprs.empty()) { + return new FixedValue(0.0); + } else if (exprs.size() == 1) { + return exprs[0]; + } else { + return new SumArray(exprs, constant); + } +} + +LinearExpr* LinearExpr::MixedSum(const std::vector& exprs, + double constant) { + std::vector lin_exprs; + + for (const ExprOrValue& choice : exprs) { + if (choice.expr != nullptr) { + lin_exprs.push_back(choice.expr); + } else { + constant += choice.value; + } + } + + // Special case: if there is only one term, return it. + if (constant == 0.0 && lin_exprs.size() == 1) { + return lin_exprs[0]; + } + + if (lin_exprs.empty()) { + return new FixedValue(constant); + } else if (lin_exprs.size() == 1) { + return new AffineExpr(lin_exprs[0], 1.0, constant); + } else { + return new SumArray(lin_exprs, constant); + } +} + +LinearExpr* LinearExpr::WeightedSum(const std::vector& exprs, + const std::vector& coeffs, + double constant) { + if (exprs.empty()) return new FixedValue(0.0); + if (exprs.size() == 1) return Affine(exprs[0], coeffs[0], constant); + return new WeightedSumArray(exprs, coeffs, constant); +} + +LinearExpr* LinearExpr::MixedWeightedSum(const std::vector& exprs, + const std::vector& coeffs, + double constant) { + std::vector lin_exprs; + std::vector lin_coeffs; + for (int i = 0; i < exprs.size(); ++i) { + if (exprs[i].expr != nullptr) { + lin_exprs.push_back(exprs[i].expr); + lin_coeffs.push_back(coeffs[i]); + } else { + constant += coeffs[i] * exprs[i].value; + } + } + + if (lin_exprs.empty()) return new FixedValue(constant); + if (lin_exprs.size() == 1) { + return Affine(lin_exprs[0], lin_coeffs[0], constant); + } + return new WeightedSumArray(lin_exprs, lin_coeffs, constant); +} + +LinearExpr* LinearExpr::Term(LinearExpr* expr, double coeff) { + return new AffineExpr(expr, coeff, 0.0); +} + +LinearExpr* LinearExpr::Affine(LinearExpr* expr, double coeff, + double constant) { + if (coeff == 1.0 && constant == 0.0) return expr; + return new AffineExpr(expr, coeff, constant); +} + +double LinearExpr::AffineCst(double value, double coeff, double constant) { + return value * coeff + constant; +} + +LinearExpr* LinearExpr::Constant(double value) { return new FixedValue(value); } + +LinearExpr* LinearExpr::Add(LinearExpr* expr) { + return new SumArray({this, expr}, 0.0); +} + +LinearExpr* LinearExpr::AddFloat(double cst) { + if (cst == 0.0) return this; + return new AffineExpr(this, 1.0, cst); +} + +LinearExpr* LinearExpr::Sub(LinearExpr* expr) { + return new WeightedSumArray({this, expr}, {1, -1}, 0.0); +} + +LinearExpr* LinearExpr::SubFloat(double cst) { + if (cst == 0.0) return this; + return new AffineExpr(this, 1.0, -cst); +} + +LinearExpr* LinearExpr::RSubFloat(double cst) { + return new AffineExpr(this, -1.0, cst); +} + +LinearExpr* LinearExpr::MulFloat(double cst) { + if (cst == 0.0) return new FixedValue(0.0); + if (cst == 1.0) return this; + return new AffineExpr(this, cst, 0.0); +} + +LinearExpr* LinearExpr::Neg() { return new AffineExpr(this, -1, 0); } + +// Expression visitors. + +void ExprVisitor::AddToProcess(const LinearExpr* expr, double coeff) { + to_process_.push_back(std::make_pair(expr, coeff)); +} + +void ExprVisitor::AddConstant(double constant) { offset_ += constant; } + +void ExprVisitor::Clear() { + to_process_.clear(); + offset_ = 0.0; +} + +void ExprFlattener::AddVarCoeff(const Variable* var, double coeff) { + canonical_terms_[var] += coeff; +} + +double ExprFlattener::Flatten(std::vector* vars, + std::vector* coeffs) { + while (!to_process_.empty()) { + const auto [expr, coeff] = to_process_.back(); + to_process_.pop_back(); + expr->Visit(*this, coeff); + } + + vars->clear(); + coeffs->clear(); + for (const auto& [var, coeff] : canonical_terms_) { + if (coeff == 0.0) continue; + vars->push_back(var); + coeffs->push_back(coeff); + } + + return offset_; +} + +void ExprEvaluator::AddVarCoeff(const Variable* var, double coeff) { + if (coeff == 0.0) return; + offset_ += coeff * helper_->variable_value(var->index()); +} + +double ExprEvaluator::Evaluate() { + offset_ = 0.0; + while (!to_process_.empty()) { + const auto [expr, coeff] = to_process_.back(); + to_process_.pop_back(); + expr->Visit(*this, coeff); + } + return offset_; +} + +FlatExpression::FlatExpression(const LinearExpr* expr) { + ExprFlattener lin; + lin.AddToProcess(expr, 1.0); + offset_ = lin.Flatten(&vars_, &coeffs_); +} + +FlatExpression::FlatExpression(const LinearExpr* pos, const LinearExpr* neg) { + ExprFlattener lin; + lin.AddToProcess(pos, 1.0); + lin.AddToProcess(neg, -1.0); + offset_ = lin.Flatten(&vars_, &coeffs_); +} + +FlatExpression::FlatExpression(const std::vector& vars, + const std::vector& coeffs, double offset) + : vars_(vars), coeffs_(coeffs), offset_(offset) {} + +FlatExpression::FlatExpression(double offset) : offset_(offset) {} + +std::vector FlatExpression::VarIndices() const { + std::vector var_indices; + var_indices.reserve(vars_.size()); + for (const Variable* var : vars_) { + var_indices.push_back(var->index()); + } + return var_indices; +} + +void FlatExpression::Visit(ExprVisitor& lin, double c) const { + for (int i = 0; i < vars_.size(); ++i) { + lin.AddVarCoeff(vars_[i], coeffs_[i] * c); + } + if (offset_ != 0.0) { + lin.AddConstant(offset_ * c); + } +} + +std::string FlatExpression::ToString() const { + if (vars_.empty()) { + return absl::StrCat(offset_); + } + std::string s; + int num_printed = 0; + for (int i = 0; i < vars_.size(); ++i) { + if (coeffs_[i] == 0.0) continue; + ++num_printed; + if (num_printed > 5) { + absl::StrAppend(&s, " + ..."); + break; + } + if (num_printed == 1) { + if (coeffs_[i] == 1.0) { + absl::StrAppend(&s, vars_[i]->ToString()); + } else if (coeffs_[i] == -1.0) { + absl::StrAppend(&s, "-", vars_[i]->ToString()); + } else { + absl::StrAppend(&s, coeffs_[i], " * ", vars_[i]->ToString()); + } + } else { + if (coeffs_[i] == 1.0) { + absl::StrAppend(&s, " + ", vars_[i]->ToString()); + } else if (coeffs_[i] == -1.0) { + absl::StrAppend(&s, " - ", vars_[i]->ToString()); + } else if (coeffs_[i] > 0.0) { + absl::StrAppend(&s, " + ", coeffs_[i], " * ", vars_[i]->ToString()); + } else { + absl::StrAppend(&s, " - ", -coeffs_[i], " * ", vars_[i]->ToString()); + } + } + } + // If there are no terms, just print the offset. + if (num_printed == 0) { + return absl::StrCat(offset_); + } + + // If there is an offset, print it. + if (offset_ != 0.0) { + if (offset_ > 0.0) { + absl::StrAppend(&s, " + ", offset_); + } else { + absl::StrAppend(&s, " - ", -offset_); + } + } + return s; +} + +std::string FlatExpression::DebugString() const { + std::string s = absl::StrCat( + "FlatExpression(", + absl::StrJoin(vars_, ", ", [](std::string* out, const Variable* expr) { + absl::StrAppend(out, expr->DebugString()); + })); + if (offset_ != 0.0) { + absl::StrAppend(&s, ", offset=", offset_); + } + absl::StrAppend(&s, ")"); + return s; +} + +void FixedValue::Visit(ExprVisitor& lin, double c) const { + lin.AddConstant(value_ * c); +} + +std::string FixedValue::ToString() const { return absl::StrCat(value_); } + +std::string FixedValue::DebugString() const { + return absl::StrCat("FixedValue(", value_, ")"); +} + +WeightedSumArray::WeightedSumArray(const std::vector& exprs, + const std::vector& coeffs, + double offset) + : exprs_(exprs.begin(), exprs.end()), + coeffs_(coeffs.begin(), coeffs.end()), + offset_(offset) {} + +void WeightedSumArray::Visit(ExprVisitor& lin, double c) const { + for (int i = 0; i < exprs_.size(); ++i) { + lin.AddToProcess(exprs_[i], coeffs_[i] * c); + } + lin.AddConstant(offset_ * c); +} + +std::string WeightedSumArray::ToString() const { + if (exprs_.empty()) { + return absl::StrCat(offset_); + } + std::string s = "("; + bool first_printed = true; + for (int i = 0; i < exprs_.size(); ++i) { + if (coeffs_[i] == 0.0) continue; + if (first_printed) { + first_printed = false; + if (coeffs_[i] == 1.0) { + absl::StrAppend(&s, exprs_[i]->ToString()); + } else if (coeffs_[i] == -1.0) { + absl::StrAppend(&s, "-", exprs_[i]->ToString()); + } else { + absl::StrAppend(&s, coeffs_[i], " * ", exprs_[i]->ToString()); + } + } else { + if (coeffs_[i] == 1.0) { + absl::StrAppend(&s, " + ", exprs_[i]->ToString()); + } else if (coeffs_[i] == -1.0) { + absl::StrAppend(&s, " - ", exprs_[i]->ToString()); + } else if (coeffs_[i] > 0.0) { + absl::StrAppend(&s, " + ", coeffs_[i], " * ", exprs_[i]->ToString()); + } else { + absl::StrAppend(&s, " - ", -coeffs_[i], " * ", exprs_[i]->ToString()); + } + } + } + // If there are no terms, just print the offset. + if (first_printed) { + return absl::StrCat(offset_); + } + + // If there is an offset, print it. + if (offset_ != 0.0) { + if (offset_ > 0.0) { + absl::StrAppend(&s, " + ", offset_); + } else { + absl::StrAppend(&s, " - ", -offset_); + } + } + absl::StrAppend(&s, ")"); + return s; +} + +std::string WeightedSumArray::DebugString() const { + return absl::StrCat("WeightedSumArray([", + absl::StrJoin(exprs_, ", ", + [](std::string* out, const LinearExpr* e) { + absl::StrAppend(out, e->DebugString()); + }), + "], [", absl::StrJoin(coeffs_, "], "), offset_, ")"); +} + +AffineExpr::AffineExpr(LinearExpr* expr, double coeff, double offset) + : expr_(expr), coeff_(coeff), offset_(offset) {} + +void AffineExpr::Visit(ExprVisitor& lin, double c) const { + lin.AddToProcess(expr_, c * coeff_); + lin.AddConstant(offset_ * c); +} + +std::string AffineExpr::ToString() const { + std::string s = "("; + if (coeff_ == 1.0) { + absl::StrAppend(&s, expr_->ToString()); + } else if (coeff_ == -1.0) { + absl::StrAppend(&s, "-", expr_->ToString()); + } else { + absl::StrAppend(&s, coeff_, " * ", expr_->ToString()); + } + if (offset_ > 0.0) { + absl::StrAppend(&s, " + ", offset_); + } else if (offset_ < 0.0) { + absl::StrAppend(&s, " - ", -offset_); + } + absl::StrAppend(&s, ")"); + return s; +} + +std::string AffineExpr::DebugString() const { + return absl::StrCat("AffineExpr(expr=", expr_->DebugString(), + ", coeff=", coeff_, ", offset=", offset_, ")"); +} +BoundedLinearExpression* LinearExpr::Eq(LinearExpr* rhs) { + return new BoundedLinearExpression(this, rhs, 0.0, 0.0); +} + +BoundedLinearExpression* LinearExpr::EqCst(double rhs) { + return new BoundedLinearExpression(this, rhs, rhs); +} + +BoundedLinearExpression* LinearExpr::Le(LinearExpr* rhs) { + return new BoundedLinearExpression( + this, rhs, -std::numeric_limits::infinity(), 0.0); +} + +BoundedLinearExpression* LinearExpr::LeCst(double rhs) { + return new BoundedLinearExpression( + this, -std::numeric_limits::infinity(), rhs); +} + +BoundedLinearExpression* LinearExpr::Ge(LinearExpr* rhs) { + return new BoundedLinearExpression(this, rhs, 0.0, + std::numeric_limits::infinity()); +} + +BoundedLinearExpression* LinearExpr::GeCst(double rhs) { + return new BoundedLinearExpression(this, rhs, + std::numeric_limits::infinity()); +} + +bool VariableComparator::operator()(const Variable* lhs, + const Variable* rhs) const { + return lhs->index() < rhs->index(); +} + +Variable::Variable(ModelBuilderHelper* helper, int index) + : helper_(helper), index_(index) {} + +Variable::Variable(ModelBuilderHelper* helper, double lb, double ub, + bool is_integral) + : helper_(helper) { + index_ = helper_->AddVar(); + helper_->SetVarLowerBound(index_, lb); + helper_->SetVarUpperBound(index_, ub); + helper_->SetVarIntegrality(index_, is_integral); +} + +Variable::Variable(ModelBuilderHelper* helper, double lb, double ub, + bool is_integral, const std::string& name) + : helper_(helper) { + index_ = helper_->AddVar(); + helper_->SetVarLowerBound(index_, lb); + helper_->SetVarUpperBound(index_, ub); + helper_->SetVarIntegrality(index_, is_integral); + helper_->SetVarName(index_, name); +} + +Variable::Variable(ModelBuilderHelper* helper, int64_t lb, int64_t ub, + bool is_integral) + : helper_(helper) { + index_ = helper_->AddVar(); + helper_->SetVarLowerBound(index_, lb); + helper_->SetVarUpperBound(index_, ub); + helper_->SetVarIntegrality(index_, is_integral); +} + +Variable::Variable(ModelBuilderHelper* helper, int64_t lb, int64_t ub, + bool is_integral, const std::string& name) + : helper_(helper) { + index_ = helper_->AddVar(); + helper_->SetVarLowerBound(index_, lb); + helper_->SetVarUpperBound(index_, ub); + helper_->SetVarIntegrality(index_, is_integral); + helper_->SetVarName(index_, name); +} + +std::string Variable::ToString() const { + if (!helper_->VarName(index_).empty()) { + return helper_->VarName(index_); + } else { + return absl::StrCat("Variable(", index_, ")"); + } +} + +std::string Variable::DebugString() const { + return absl::StrCat("Variable(index=", index_, + ", lb=", helper_->VarLowerBound(index_), + ", ub=", helper_->VarUpperBound(index_), + ", is_integral=", helper_->VarIsIntegral(index_), + ", name=\'", helper_->VarName(index_), "')"); +} + +std::string Variable::name() const { + const std::string& var_name = helper_->VarName(index_); + if (!var_name.empty()) return var_name; + return absl::StrCat("variable#", index_); +} + +void Variable::SetName(const std::string& name) { + helper_->SetVarName(index_, name); +} + +double Variable::lower_bounds() const { return helper_->VarLowerBound(index_); } + +void Variable::SetLowerBound(double lb) { + helper_->SetVarLowerBound(index_, lb); +} + +double Variable::upper_bound() const { return helper_->VarUpperBound(index_); } + +void Variable::SetUpperBound(double ub) { + helper_->SetVarUpperBound(index_, ub); +} + +bool Variable::is_integral() const { return helper_->VarIsIntegral(index_); } + +void Variable::SetIsIntegral(bool is_integral) { + helper_->SetVarIntegrality(index_, is_integral); +} + +double Variable::objective_coefficient() const { + return helper_->VarObjectiveCoefficient(index_); +} + +void Variable::SetObjectiveCoefficient(double coeff) { + helper_->SetVarObjectiveCoefficient(index_, coeff); +} + +BoundedLinearExpression::BoundedLinearExpression(const LinearExpr* expr, + double lower_bound, + double upper_bound) { + FlatExpression flat_expr(expr); + vars_ = flat_expr.vars(); + coeffs_ = flat_expr.coeffs(); + lower_bound_ = lower_bound - flat_expr.offset(); + upper_bound_ = upper_bound - flat_expr.offset(); +} + +BoundedLinearExpression::BoundedLinearExpression(const LinearExpr* pos, + const LinearExpr* neg, + double lower_bound, + double upper_bound) { + FlatExpression flat_expr(pos, neg); + vars_ = flat_expr.vars(); + coeffs_ = flat_expr.coeffs(); + lower_bound_ = lower_bound - flat_expr.offset(); + upper_bound_ = upper_bound - flat_expr.offset(); +} + +BoundedLinearExpression::BoundedLinearExpression(const LinearExpr* expr, + int64_t lower_bound, + int64_t upper_bound) { + FlatExpression flat_expr(expr); + vars_ = flat_expr.vars(); + coeffs_ = flat_expr.coeffs(); + lower_bound_ = lower_bound - flat_expr.offset(); + upper_bound_ = upper_bound - flat_expr.offset(); +} + +BoundedLinearExpression::BoundedLinearExpression(const LinearExpr* pos, + const LinearExpr* neg, + int64_t lower_bound, + int64_t upper_bound) { + FlatExpression flat_expr(pos, neg); + vars_ = flat_expr.vars(); + coeffs_ = flat_expr.coeffs(); + lower_bound_ = lower_bound - flat_expr.offset(); + upper_bound_ = upper_bound - flat_expr.offset(); +} + +double BoundedLinearExpression::lower_bound() const { return lower_bound_; } +double BoundedLinearExpression::upper_bound() const { return upper_bound_; } +const std::vector& BoundedLinearExpression::vars() const { + return vars_; +} +const std::vector& BoundedLinearExpression::coeffs() const { + return coeffs_; +} +std::string BoundedLinearExpression::ToString() const { + std::string s; + if (vars_.empty()) { + s = absl::StrCat(0.0); + } else if (vars_.size() == 1) { + const std::string var_name = vars_[0]->ToString(); + if (coeffs_[0] == 1) { + s = var_name; + } else if (coeffs_[0] == -1) { + s = absl::StrCat("-", var_name); + } else { + s = absl::StrCat(coeffs_[0], " * ", var_name); + } + } else { + s = "("; + for (int i = 0; i < vars_.size(); ++i) { + const std::string var_name = vars_[i]->ToString(); + if (i == 0) { + if (coeffs_[i] == 1) { + absl::StrAppend(&s, var_name); + } else if (coeffs_[i] == -1) { + absl::StrAppend(&s, "-", var_name); + } else { + absl::StrAppend(&s, coeffs_[i], " * ", var_name); + } + } else { + if (coeffs_[i] == 1) { + absl::StrAppend(&s, " + ", var_name); + } else if (coeffs_[i] == -1) { + absl::StrAppend(&s, " - ", var_name); + } else if (coeffs_[i] > 1) { + absl::StrAppend(&s, " + ", coeffs_[i], " * ", var_name); + } else { + absl::StrAppend(&s, " - ", -coeffs_[i], " * ", var_name); + } + } + } + absl::StrAppend(&s, ")"); + } + if (lower_bound_ == upper_bound_) { + return absl::StrCat(s, " == ", lower_bound_); + } else if (lower_bound_ == std::numeric_limits::min()) { + if (upper_bound_ == std::numeric_limits::max()) { + return absl::StrCat("True (unbounded expr ", s, ")"); + } else { + return absl::StrCat(s, " <= ", upper_bound_); + } + } else if (upper_bound_ == std::numeric_limits::max()) { + return absl::StrCat(s, " >= ", lower_bound_); + } else { + return absl::StrCat(lower_bound_, " <= ", s, " <= ", upper_bound_); + } +} + +std::string BoundedLinearExpression::DebugString() const { + return absl::StrCat("BoundedLinearExpression(vars=[", + absl::StrJoin(vars_, ", ", + [](std::string* out, const Variable* var) { + absl::StrAppend(out, var->DebugString()); + }), + "], coeffs=[", absl::StrJoin(coeffs_, ", "), + "], lower_bound=", lower_bound_, + ", upper_bound=", upper_bound_, ")"); +} + +bool BoundedLinearExpression::CastToBool(bool* result) const { + const bool is_zero = lower_bound_ == 0.0 && upper_bound_ == 0.0; + if (is_zero) { + if (vars_.empty()) { + *result = true; + return true; + } else if (vars_.size() == 2 && coeffs_[0] + coeffs_[1] == 0 && + std::abs(coeffs_[0]) == 1) { + *result = false; + return true; + } + } + return false; +} + +} // namespace mb } // namespace operations_research diff --git a/ortools/linear_solver/wrappers/model_builder_helper.h b/ortools/linear_solver/wrappers/model_builder_helper.h index 6fa20539c1..2ea1106682 100644 --- a/ortools/linear_solver/wrappers/model_builder_helper.h +++ b/ortools/linear_solver/wrappers/model_builder_helper.h @@ -15,18 +15,334 @@ #define OR_TOOLS_LINEAR_SOLVER_WRAPPERS_MODEL_BUILDER_HELPER_H_ #include +#include #include -#include #include #include +#include #include +#include "absl/container/btree_map.h" +#include "absl/container/fixed_array.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" #include "ortools/linear_solver/linear_solver.pb.h" #include "ortools/linear_solver/model_exporter.h" -#include "ortools/util/logging.h" #include "ortools/util/solve_interrupter.h" namespace operations_research { +namespace mb { + +// Base implementation of linear expressions. + +class BoundedLinearExpression; +class FlatExpression; +class ExprVisitor; +class LinearExpr; +class ModelBuilderHelper; +class ModelSolverHelper; +class Variable; + +// A class to hold an linear expression or a constant. +struct ExprOrValue { + explicit ExprOrValue(LinearExpr* e) : expr(e) {} + explicit ExprOrValue(double v) : value(v) {} + explicit ExprOrValue(int64_t v) : value(static_cast(v)) {} + + LinearExpr* expr = nullptr; + double value = 0.0; +}; + +// A linear expression that can be either integer or floating point. +class LinearExpr { + public: + virtual ~LinearExpr() = default; + virtual void Visit(ExprVisitor& /*lin*/, double /*c*/) const = 0; + virtual std::string ToString() const = 0; + virtual std::string DebugString() const = 0; + + static LinearExpr* Sum(const std::vector& exprs, + double constant = 0.0); + static LinearExpr* MixedSum(const std::vector& exprs, + double offset = 0.0); + static LinearExpr* WeightedSum(const std::vector& exprs, + const std::vector& coeffs, + double constant = 0.0); + static LinearExpr* MixedWeightedSum(const std::vector& exprs, + const std::vector& coeffs, + double constant = 0.0); + static LinearExpr* Term(LinearExpr* expr, double coeff); + static LinearExpr* Affine(LinearExpr* expr, double coeff, double constant); + static double AffineCst(double value, double coeff, double constant); + static LinearExpr* Constant(double value); + + LinearExpr* Add(LinearExpr* expr); + LinearExpr* AddFloat(double cst); + LinearExpr* Sub(LinearExpr* expr); + LinearExpr* SubFloat(double cst); + LinearExpr* RSubFloat(double cst); + LinearExpr* MulFloat(double cst); + LinearExpr* Neg(); + + BoundedLinearExpression* Eq(LinearExpr* rhs); + BoundedLinearExpression* EqCst(double rhs); + BoundedLinearExpression* Ge(LinearExpr* rhs); + BoundedLinearExpression* GeCst(double rhs); + BoundedLinearExpression* Le(LinearExpr* rhs); + BoundedLinearExpression* LeCst(double rhs); +}; + +// Compare the indices of variables. +struct VariableComparator { + bool operator()(const Variable* lhs, const Variable* rhs) const; +}; + +// A visitor class to parse a floating point linear expression. +class ExprVisitor { + public: + virtual ~ExprVisitor() = default; + void AddToProcess(const LinearExpr* expr, double coeff); + void AddConstant(double constant); + virtual void AddVarCoeff(const Variable* var, double coeff) = 0; + void Clear(); + + protected: + std::vector> to_process_; + double offset_ = 0; +}; + +class ExprFlattener : public ExprVisitor { + public: + ~ExprFlattener() override = default; + void AddVarCoeff(const Variable* var, double coeff) override; + double Flatten(std::vector* vars, + std::vector* coeffs); + + private: + absl::btree_map canonical_terms_; +}; + +class ExprEvaluator : public ExprVisitor { + public: + explicit ExprEvaluator(ModelSolverHelper* helper) : helper_(helper) {} + ~ExprEvaluator() override = default; + void AddVarCoeff(const Variable* var, double coeff) override; + double Evaluate(); + + private: + ModelSolverHelper* helper_; +}; + +// A flat linear expression sum(vars[i] * coeffs[i]) + offset +class FlatExpression : public LinearExpr { + public: + explicit FlatExpression(const LinearExpr* expr); + // Flatten pos - neg. + FlatExpression(const LinearExpr* pos, const LinearExpr* neg); + FlatExpression(const std::vector&, + const std::vector&, double); + explicit FlatExpression(double offset); + const std::vector& vars() const { return vars_; } + std::vector VarIndices() const; + const std::vector& coeffs() const { return coeffs_; } + double offset() const { return offset_; } + + void Visit(ExprVisitor& lin, double c) const override; + std::string ToString() const override; + std::string DebugString() const override; + + private: + std::vector vars_; + std::vector coeffs_; + double offset_; +}; + +// A class to hold a sum of linear expressions, and optional integer and +// double offsets. +class SumArray : public LinearExpr { + public: + explicit SumArray(const std::vector& exprs, double offset) + : exprs_(exprs.begin(), exprs.end()), offset_(offset) {} + ~SumArray() override = default; + + void Visit(ExprVisitor& lin, double c) const override { + for (int i = 0; i < exprs_.size(); ++i) { + lin.AddToProcess(exprs_[i], c); + } + if (offset_ != 0.0) { + lin.AddConstant(offset_ * c); + } + } + + std::string ToString() const override { + if (exprs_.empty()) { + if (offset_ != 0.0) { + return absl::StrCat(offset_); + } + } + std::string s = "("; + for (int i = 0; i < exprs_.size(); ++i) { + if (i > 0) { + absl::StrAppend(&s, " + "); + } + absl::StrAppend(&s, exprs_[i]->ToString()); + } + if (offset_ != 0.0) { + if (offset_ > 0.0) { + absl::StrAppend(&s, " + ", offset_); + } else { + absl::StrAppend(&s, " - ", -offset_); + } + } + absl::StrAppend(&s, ")"); + return s; + } + + std::string DebugString() const override { + std::string s = absl::StrCat( + "SumArray(", + absl::StrJoin(exprs_, ", ", [](std::string* out, LinearExpr* expr) { + absl::StrAppend(out, expr->DebugString()); + })); + if (offset_ != 0.0) { + absl::StrAppend(&s, ", offset=", offset_); + } + absl::StrAppend(&s, ")"); + return s; + } + + private: + const absl::FixedArray exprs_; + const double offset_; +}; + +// A class to hold a weighted sum of floating point linear expressions. +class WeightedSumArray : public LinearExpr { + public: + WeightedSumArray(const std::vector& exprs, + const std::vector& coeffs, double offset); + ~WeightedSumArray() override = default; + + void Visit(ExprVisitor& lin, double c) const override; + std::string ToString() const override; + std::string DebugString() const override; + + private: + const absl::FixedArray exprs_; + const absl::FixedArray coeffs_; + double offset_; +}; + +// A class to hold linear_expr * a = b. +class AffineExpr : public LinearExpr { + public: + AffineExpr(LinearExpr* expr, double coeff, double offset); + ~AffineExpr() override = default; + + void Visit(ExprVisitor& lin, double c) const override; + + std::string ToString() const override; + std::string DebugString() const override; + + LinearExpr* expression() const { return expr_; } + double coefficient() const { return coeff_; } + double offset() const { return offset_; } + + private: + LinearExpr* expr_; + double coeff_; + double offset_; +}; + +// A class to hold a fixed value. +class FixedValue : public LinearExpr { + public: + explicit FixedValue(double value) : value_(value) {} + ~FixedValue() override = default; + + void Visit(ExprVisitor& lin, double c) const override; + + std::string ToString() const override; + std::string DebugString() const override; + + private: + double value_; +}; + +// A class to hold a variable index. +class Variable : public LinearExpr { + public: + Variable(ModelBuilderHelper* helper, int index); + Variable(ModelBuilderHelper* helper, double lb, double ub, bool is_integral); + Variable(ModelBuilderHelper* helper, double lb, double ub, bool is_integral, + const std::string& name); + Variable(ModelBuilderHelper* helper, int64_t lb, int64_t ub, + bool is_integral); + Variable(ModelBuilderHelper* helper, int64_t lb, int64_t ub, bool is_integral, + const std::string& name); + ~Variable() override {} + + ModelBuilderHelper* helper() const { return helper_; } + int index() const { return index_; } + std::string name() const; + void SetName(const std::string& name); + double lower_bounds() const; + void SetLowerBound(double lb); + double upper_bound() const; + void SetUpperBound(double ub); + bool is_integral() const; + void SetIsIntegral(bool is_integral); + double objective_coefficient() const; + void SetObjectiveCoefficient(double coeff); + + void Visit(ExprVisitor& lin, double c) const override { + lin.AddVarCoeff(this, c); + } + + std::string ToString() const override; + + std::string DebugString() const override; + + bool operator<(const Variable& other) const { return index_ < other.index_; } + + protected: + ModelBuilderHelper* helper_; + int index_; +}; + +template +H AbslHashValue(H h, const Variable* i) { + return H::combine(std::move(h), i->index()); +} + +// A class to hold a linear expression with bounds. +class BoundedLinearExpression { + public: + BoundedLinearExpression(const LinearExpr* expr, double lower_bound, + double upper_bound); + BoundedLinearExpression(const LinearExpr* pos, const LinearExpr* neg, + double lower_bound, double upper_bound); + BoundedLinearExpression(const LinearExpr* expr, int64_t lower_bound, + int64_t upper_bound); + BoundedLinearExpression(const LinearExpr* pos, const LinearExpr* neg, + int64_t lower_bound, int64_t upper_bound); + + ~BoundedLinearExpression() = default; + + double lower_bound() const; + double upper_bound() const; + const std::vector& vars() const; + const std::vector& coeffs() const; + std::string ToString() const; + std::string DebugString() const; + bool CastToBool(bool* result) const; + + private: + std::vector vars_; + std::vector coeffs_; + double lower_bound_; + double upper_bound_; +}; // The arguments of the functions defined below must follow these rules // to be wrapped by SWIG correctly: @@ -189,6 +505,7 @@ class ModelSolverHelper { double objective_value() const; double best_objective_bound() const; double variable_value(int var_index) const; + double expression_value(LinearExpr* expr) const; double reduced_cost(int var_index) const; double dual_value(int ct_index) const; double activity(int ct_index); @@ -216,8 +533,10 @@ class ModelSolverHelper { std::optional model_of_last_solve_; std::vector activities_; bool solver_output_ = false; + mutable ExprEvaluator evaluator_; }; +} // namespace mb } // namespace operations_research #endif // OR_TOOLS_LINEAR_SOLVER_WRAPPERS_MODEL_BUILDER_HELPER_H_