model builder: add clear_terms API; add enforced linear constraint in java + minimal test

This commit is contained in:
Laurent Perron
2023-11-05 12:41:22 +01:00
parent 1a610bd7ef
commit ce9f1ed6d2
8 changed files with 252 additions and 19 deletions

View File

@@ -19,6 +19,7 @@ java_library(
srcs = [
"AffineExpression.java",
"ConstantExpression.java",
"EnforcedLinearConstraint.java",
"LinearArgument.java",
"LinearConstraint.java",
"LinearExpr.java",

View File

@@ -0,0 +1,101 @@
// Copyright 2010-2022 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.ortools.modelbuilder;
/** Wrapper around a linear constraint stored in the ModelBuilderHelper instance. */
public class EnforcedLinearConstraint {
public EnforcedLinearConstraint(ModelBuilderHelper helper) {
this.helper = helper;
this.index = helper.addEnforcedLinearConstraint();
}
EnforcedLinearConstraint(ModelBuilderHelper helper, int index) {
this.helper = helper;
this.index = index;
}
/** Returns the index of the constraint in the model. */
public int getIndex() {
return index;
}
/** Returns the constraint builder. */
public ModelBuilderHelper getHelper() {
return helper;
}
/** Returns the lower bound of the constraint. */
public double getLowerBound() {
return helper.getEnforcedConstraintLowerBound(index);
}
/** Sets the lower bound of the constraint. */
public void setLowerBound(double lb) {
helper.setEnforcedConstraintLowerBound(index, lb);
}
/** Returns the upper bound of the constraint. */
public double getUpperBound() {
return helper.getEnforcedConstraintUpperBound(index);
}
/** Sets the upper bound of the constraint. */
public void setUpperBound(double ub) {
helper.setEnforcedConstraintUpperBound(index, ub);
}
/** Returns the name of the constraint given upon creation. */
public String getName() {
return helper.getEnforcedConstraintName(index);
}
// Sets the name of the constraint. */
public void setName(String name) {
helper.setEnforcedConstraintName(index, name);
}
// Adds var * coeff to the constraint.
public void addEnforcedTerm(Variable v, double coeff) {
helper.safeAddEnforcedConstraintTerm(index, v.getIndex(), coeff);
}
// Returns the indicator variable of the constraint.
public Variable getIndicatorVariable() {
return new Variable(helper, helper.getEnforcedIndicatorVariableIndex(index));
}
// Sets the indicator variable of the constraint.
public void setIndicatorVariable(Variable v) {
helper.setEnforcedIndicatorVariable(index, v.index);
}
// Returns the indicator value of the constraint.
public boolean getIndicatorValue() {
return helper.getEnforcedIndicatorValue(index);
}
// Sets the indicator value of the constraint.
public void setIndicatorValue(boolean b) {
helper.setEnforcedIndicatorValue(index, b);
}
/** Inline setter */
public EnforcedLinearConstraint withName(String name) {
setName(name);
return this;
}
private final ModelBuilderHelper helper;
private final int index;
}

View File

@@ -117,11 +117,6 @@ public final class ModelBuilder {
return lin;
}
/** Returns the number of variables in the model. */
public int numVariables() {
return helper.numVariables();
}
/** Adds {@code expr == value}. */
public LinearConstraint addEquality(LinearArgument expr, double value) {
return addLinearConstraint(expr, value, value);
@@ -161,16 +156,80 @@ public final class ModelBuilder {
return addLinearConstraint(difference, 0.0, Double.POSITIVE_INFINITY);
}
/** Returns the number of constraints in the model. */
public int numConstraints() {
return helper.numConstraints();
}
/** Rebuilds a linear constraint from its index. */
public LinearConstraint constraintFromIndex(int index) {
return new LinearConstraint(helper, index);
}
// Enforced Linear constraints.
/** Adds {@code lb <= expr <= ub}. */
public EnforcedLinearConstraint addEnforcedLinearConstraint(LinearArgument expr, double lb, double ub, Variable iVar, boolean iValue) {
EnforcedLinearConstraint lin = new EnforcedLinearConstraint(helper);
lin.setIndicatorVariable(iVar);
lin.setIndicatorValue(iValue);
final LinearExpr e = expr.build();
for (int i = 0; i < e.numElements(); ++i) {
helper.addEnforcedConstraintTerm(lin.getIndex(), e.getVariableIndex(i), e.getCoefficient(i));
}
double offset = e.getOffset();
if (lb == Double.NEGATIVE_INFINITY || lb == Double.POSITIVE_INFINITY) {
lin.setLowerBound(lb);
} else {
lin.setLowerBound(lb - offset);
}
if (ub == Double.NEGATIVE_INFINITY || ub == Double.POSITIVE_INFINITY) {
lin.setUpperBound(ub);
} else {
lin.setUpperBound(ub - offset);
}
return lin;
}
/** Adds {@code expr == value}. */
public EnforcedLinearConstraint addEnforcedEquality(LinearArgument expr, double value, Variable iVar, boolean iValue) {
return addEnforcedLinearConstraint(expr, value, value, iVar, iValue);
}
/** Adds {@code left == right}. */
public EnforcedLinearConstraint addEnforcedEquality(LinearArgument left, LinearArgument right, Variable iVar, boolean iValue) {
LinearExprBuilder difference = LinearExpr.newBuilder();
difference.addTerm(left, 1);
difference.addTerm(right, -1);
return addEnforcedLinearConstraint(difference, 0.0, 0.0, iVar, iValue);
}
/** Adds {@code expr <= value}. */
public EnforcedLinearConstraint addEnforcedLessOrEqual(LinearArgument expr, double value, Variable iVar, boolean iValue) {
return addEnforcedLinearConstraint(expr, Double.NEGATIVE_INFINITY, value, iVar, iValue);
}
/** Adds {@code left <= right}. */
public EnforcedLinearConstraint addEnforcedLessOrEqual(LinearArgument left, LinearArgument right, Variable iVar, boolean iValue) {
LinearExprBuilder difference = LinearExpr.newBuilder();
difference.addTerm(left, 1);
difference.addTerm(right, -1);
return addEnforcedLinearConstraint(difference, Double.NEGATIVE_INFINITY, 0.0, iVar, iValue);
}
/** Adds {@code expr >= value}. */
public EnforcedLinearConstraint addEnforcedGreaterOrEqual(LinearArgument expr, double value, Variable iVar, boolean iValue) {
return addEnforcedLinearConstraint(expr, value, Double.POSITIVE_INFINITY, iVar, iValue);
}
/** Adds {@code left >= right}. */
public EnforcedLinearConstraint addEnforcedGreaterOrEqual(LinearArgument left, LinearArgument right, Variable iVar, boolean iValue) {
LinearExprBuilder difference = LinearExpr.newBuilder();
difference.addTerm(left, 1);
difference.addTerm(right, -1);
return addEnforcedLinearConstraint(difference, 0.0, Double.POSITIVE_INFINITY, iVar, iValue);
}
/** Rebuilds a linear constraint from its index. */
public EnforcedLinearConstraint enforcedConstraintFromIndex(int index) {
return new EnforcedLinearConstraint(helper, index);
}
/** Minimize expression */
public void minimize(LinearArgument obj) {
optimize(obj, false);
@@ -214,12 +273,22 @@ public final class ModelBuilder {
}
/** Adds var == value as a hint to the model. Note that variables must not appear more than once in the list of hints. */
void addHint(Variable var, double value) {
helper.addHint(var.getIndex(), value);
void addHint(Variable v, double value) {
helper.addHint(v.getIndex(), value);
}
// Model getters, import, export.
/** Returns the number of variables in the model. */
public int numVariables() {
return helper.numVariables();
}
/** Returns the number of constraints in the model. */
public int numConstraints() {
return helper.numConstraints();
}
/** Returns the name of the model. */
public String getName() {
return helper.getName();

View File

@@ -27,9 +27,28 @@ public final class ModelBuilderTest {
}
@Test
public void runMinimalLinearExample_ok() {
public void testEnforcedLinearApi() {
ModelBuilder model = new ModelBuilder();
model.setName("minimal_linear_example");
model.setName("minimal enforced linear test");
double infinity = Double.POSITIVE_INFINITY;
Variable x = model.newNumVar(0.0, infinity, "x");
Variable y = model.newNumVar(0.0, infinity, "y");
Variable z = model.newBoolVar("z");
assertThat(model.numVariables()).isEqualTo(3);
EnforcedLinearConstraint c0 = model.addEnforcedGreaterOrEqual(
LinearExpr.newBuilder().add(x).addTerm(y, 2.0), 10.0, z, false);
assertThat(c0.getLowerBound()).isEqualTo(10.0);
assertThat(c0.getIndicatorVariable().getIndex()).isEqualTo(z.getIndex());
assertThat(c0.getIndicatorValue()).isFalse();
}
@Test
public void runMinimalLinearExample_ok() {
final String name = "minimal_linear_example";
ModelBuilder model = new ModelBuilder();
model.setName(name);
double infinity = Double.POSITIVE_INFINITY;
Variable x1 = model.newNumVar(0.0, infinity, "x1");
Variable x2 = model.newNumVar(0.0, infinity, "x2");
@@ -88,8 +107,8 @@ public final class ModelBuilderTest {
assertThat(solver.getActivity(c1)).isWithin(1e-5).of(600.0);
assertThat(solver.getActivity(c2)).isWithin(1e-5).of(200.0);
assertThat(model.exportToLpString(false)).contains("minimal_linear_example");
assertThat(model.exportToMpsString(false)).contains("minimal_linear_example");
assertThat(model.exportToLpString(false)).contains(name);
assertThat(model.exportToMpsString(false)).contains(name);
}
@Test

View File

@@ -117,14 +117,13 @@ class GlobalRefGuard {
%rename (getVarName) operations_research::ModelBuilderHelper::VarName;
%rename (getVarObjectiveCoefficient) operations_research::ModelBuilderHelper::VarObjectiveCoefficient;
%rename (getVarUpperBound) operations_research::ModelBuilderHelper::VarUpperBound;
%rename (numVariables) operations_research::ModelBuilderHelper::num_variables;
%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;
// Constraint API.
// Linear Constraint API.
%rename (addConstraintTerm) operations_research::ModelBuilderHelper::AddConstraintTerm;
%rename (addLinearConstraint) operations_research::ModelBuilderHelper::AddLinearConstraint;
%rename (getConstraintCoefficients) operations_research::ModelBuilderHelper::ConstraintCoefficients;
@@ -132,11 +131,29 @@ class GlobalRefGuard {
%rename (getConstraintName) operations_research::ModelBuilderHelper::ConstraintName;
%rename (getConstraintUpperBound) operations_research::ModelBuilderHelper::ConstraintUpperBound;
%rename (getConstraintVarIndices) operations_research::ModelBuilderHelper::ConstraintVarIndices;
%rename (numConstraints) operations_research::ModelBuilderHelper::num_constraints;
%rename (safeAddConstraintTerm) operations_research::ModelBuilderHelper::SafeAddConstraintTerm;
%rename (setConstraintCoefficients) operations_research::ModelBuilderHelper::SetConstraintCoefficients;
%rename (setConstraintLowerBound) operations_research::ModelBuilderHelper::SetConstraintLowerBound;
%rename (setConstraintName) operations_research::ModelBuilderHelper::SetConstraintName;
%rename (setConstraintUpperBound) operations_research::ModelBuilderHelper::SetConstraintUpperBound;
// Enforced Linear Constraint API.
%rename (addEnforcedConstraintTerm) operations_research::ModelBuilderHelper::AddEnforcedConstraintTerm;
%rename (addEnforcedLinearConstraint) operations_research::ModelBuilderHelper::AddEnforcedLinearConstraint;
%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 (safeAddEnforcedConstraintTerm) operations_research::ModelBuilderHelper::SafeAddEnforcedConstraintTerm;
%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 (setEnforcedIndicatorVariable) operations_research::ModelBuilderHelper::SetEnforcedIndicatorVariable;
// Objective API.
%rename (clearObjective) operations_research::ModelBuilderHelper::ClearObjective;
%rename (getMaximize) operations_research::ModelBuilderHelper::maximize;
@@ -149,6 +166,8 @@ class GlobalRefGuard {
%rename (addHint) operations_research::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 (writeModelToFile) operations_research::ModelBuilderHelper::WriteModelToFile;

View File

@@ -694,6 +694,10 @@ class LinearConstraint:
self.__helper.safe_add_term_to_constraint(self.__index, var.index,
coeff)
def clear_terms(self) -> None:
"""Clear all terms of the constraint."""
self.__helper.clear_constraint_terms(self.__index)
class EnforcedLinearConstraint:
"""Stores an enforced linear equation, also name indicator constraint.
@@ -809,6 +813,10 @@ class EnforcedLinearConstraint:
self.__helper.safe_add_term_to_enforced_constraint(
self.__index, var.index, coeff)
def clear_terms(self) -> None:
"""Clear all terms of the constraint."""
self.__helper.clear_enforced_constraint_terms(self.__index)
class ModelBuilder:
"""Methods for building a linear model.

View File

@@ -168,6 +168,12 @@ void ModelBuilderHelper::SetConstraintUpperBound(int ct_index, double ub) {
model_.mutable_constraint(ct_index)->set_upper_bound(ub);
}
void ModelBuilderHelper::ClearConstraintTerms(int ct_index) {
MPConstraintProto* ct_proto = model_.mutable_constraint(ct_index);
ct_proto->clear_var_index();
ct_proto->clear_coefficient();
}
void ModelBuilderHelper::AddConstraintTerm(int ct_index, int var_index,
double coeff) {
if (coeff == 0.0) return;
@@ -264,6 +270,14 @@ void ModelBuilderHelper::SetEnforcedConstraintUpperBound(int ct_index, double ub
ct_proto->set_upper_bound(ub);
}
void ModelBuilderHelper::ClearEnforcedConstraintTerms(int ct_index) {
MPConstraintProto* ct_proto = model_.mutable_general_constraint(ct_index)
->mutable_indicator_constraint()
->mutable_constraint();
ct_proto->clear_var_index();
ct_proto->clear_coefficient();
}
void ModelBuilderHelper::AddEnforcedConstraintTerm(int ct_index, int var_index,
double coeff) {
DCHECK(IsEnforcedConstraint(ct_index));

View File

@@ -84,6 +84,7 @@ class ModelBuilderHelper {
void SafeAddConstraintTerm(int ct_index, int var_index, double coeff);
void SetConstraintName(int ct_index, const std::string& name);
void SetConstraintCoefficient(int ct_index, int var_index, double coeff);
void ClearConstraintTerms(int ct_index);
double ConstraintLowerBound(int ct_index) const;
double ConstraintUpperBound(int ct_index) const;
@@ -102,6 +103,7 @@ class ModelBuilderHelper {
void SetEnforcedConstraintCoefficient(int ct_index, int var_index, double coeff);
void SetEnforcedIndicatorVariable(int ct_index, int var_index);
void SetEnforcedIndicatorValue(int ct_index, bool positive);
void ClearEnforcedConstraintTerms(int ct_index);
double EnforcedConstraintLowerBound(int ct_index) const;
double EnforcedConstraintUpperBound(int ct_index) const;