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)