Constraint Classes

To make applying constraints to your model easier, some constraints have been provided as a part of ENTMOOT.

[ ]:
from entmoot.problem_config import ProblemConfig
from entmoot.models.enting import Enting
from entmoot.optimizers.pyomo_opt import PyomoOptimizer

NChooseKConstraint

This constraint is often used in the design of experiments. This applies a bound on the number of non-zero variables.

[ ]:
from entmoot.benchmarks import build_reals_only_problem, eval_reals_only_testfunc

# standard setting up of problem
problem_config = ProblemConfig(rnd_seed=73)
build_reals_only_problem(problem_config)
rnd_sample = problem_config.get_rnd_sample_list(num_samples=50)
testfunc_evals = eval_reals_only_testfunc(rnd_sample)

params = {"unc_params": {"dist_metric": "l1", "acq_sense": "penalty"}}
enting = Enting(problem_config, params=params)
# fit tree ensemble
enting.fit(rnd_sample, testfunc_evals)

[ ]:
from entmoot.constraints import NChooseKConstraint
model_pyo = problem_config.get_pyomo_model_core()

# define the constraint
# then immediately apply it to the model
model_pyo.nchoosek = NChooseKConstraint(
    feature_keys=["x1", "x2", "x3", "x4", "x5"],
    min_count=1,
    max_count=3,
    none_also_valid=True
).as_pyomo_constraint(model_pyo, problem_config.feat_list)


# optimise the model
params_pyomo = {"solver_name": "gurobi"}
opt_pyo = PyomoOptimizer(problem_config, params=params_pyomo)
res_pyo = opt_pyo.solve(enting, model_core=model_pyo)
[ ]:
print(res_pyo.opt_point)
assert 1 <= sum(x > 1e-6 for x in res_pyo.opt_point) <= 3

Defining your own constraint

We have provided some constraints already as a part of ENTMOOT. If these do not fit your needs, then you can define your own!

The easiest approach is to subclass ExpressionConstraint, and define some custom expression that is a function of the variables. From that, you should be able to use the constraint as shown above. This needs to return a pyomo.Expression object. If you need to do a more involved procedure that modifies the model, you can use a FunctionalConstraint instead (see NChooseKConstraint).

[ ]:
from entmoot.constraints import ExpressionConstraint

class SumLessThanTen(ExpressionConstraint):
    """A constraint that enforces selected features to sum to less than ten."""
    def _get_expr(self, features):
        return sum(features) <= 10

Constraint Lists

For a problem definition, it may be easier to define a set of constraints.

[ ]:
problem_config = ProblemConfig(rnd_seed=73)
build_reals_only_problem(problem_config)
rnd_sample = problem_config.get_rnd_sample_list(num_samples=50)
testfunc_evals = eval_reals_only_testfunc(rnd_sample)

params = {"unc_params": {"dist_metric": "l1", "acq_sense": "penalty"}}
enting = Enting(problem_config, params=params)
# fit tree ensemble
enting.fit(rnd_sample, testfunc_evals)

[ ]:
from entmoot.constraints import LinearInequalityConstraint, ConstraintList
import pyomo.environ as pyo
model_pyo = problem_config.get_pyomo_model_core()

# define the constraint
# then immediately apply it to the model
constraints = [
    NChooseKConstraint(
        feature_keys=["x1", "x2", "x3", "x4", "x5"],
        min_count=1,
        max_count=4,
        none_also_valid=True
    ),
    LinearInequalityConstraint(
        feature_keys=["x3", "x4", "x5"],
        coefficients=[1, 1, 1],
        rhs=12.0
    )
]

model_pyo.problem_constraints = pyo.ConstraintList()
ConstraintList(constraints).apply_pyomo_constraints(
    model_pyo, problem_config.feat_list, model_pyo.problem_constraints
)

[ ]:
# optimise the model
params_pyomo = {"solver_name": "gurobi"}
opt_pyo = PyomoOptimizer(problem_config, params=params_pyomo)
res_pyo = opt_pyo.solve(enting, model_core=model_pyo)
[ ]:
print(res_pyo.opt_point)