Discrete and Mixed-Integer Variables
By default every variable is continuous. Marking some or all of them as
integer-valued takes one field, variables.types, but it changes what the rest
of the configuration has to look like: an integer variable cannot be
differentiated, so the problem needs a method that searches without gradients,
and that method needs bounds.
There are two runnable scripts for this page: examples/simple/discrete.py, where every variable is an integer, and examples/simple/mixed.py, where continuous and integer variables appear in one problem.
Warning
Only differential_evolution handles integer variables. The other SciPy
methods silently treat them as continuous — no error, just a fractional
answer to a problem you meant to be discrete. Choosing the method is not
optional here.
All variables integer
discrete.py maximizes min(3x, y) over two integers, subject to
x + y <= 10. The types field takes a single value that applies to every
variable:
config: dict[str, Any] = {
"variables": {
"variable_count": 2,
"types": VariableType.INTEGER,
"lower_bounds": [0.0, 0.0],
"upper_bounds": [10.0, 10.0],
},
"optimizer": {
"max_functions": 5,
},
"backend": {
"method": "differential_evolution",
"options": {"rng": 4},
"parallel": False,
},
}
Three things go together. types marks the variables as integers; the backend
section selects differential_evolution, the only method that will respect
that; and lower_bounds / upper_bounds are mandatory, because that method
searches within a box rather than stepping from a start point.
VariableType comes from ropt.enums, not from ropt.simple:
The objective is an ordinary evaluation function. It receives the variables as floats that happen to hold integral values:
def function(
variables: NDArray[np.float64],
_context: EvaluationFunctionContext,
) -> float | list[float]:
x, y = variables
objective = -min(3.0 * x, y)
if linear:
return float(objective)
return [float(objective), float(x + y)]
The script imposes x + y <= 10 as a nonlinear constraint by default, and as a
linear one with --linear. Both forms work unchanged with integer variables;
see Constraints.
Mixing continuous and integer variables
To make only some variables discrete, give types one entry per variable
instead of a single value. mixed.py does this for the first four variables of
an ensemble Rosenbrock problem, keeping two continuous and two integer:
CONFIG: dict[str, Any] = {
"variables": {
"variable_count": DIM,
"perturbation_magnitudes": 1e-6,
"lower_bounds": 0.0,
"upper_bounds": 10.0,
"types": [
VariableType.REAL,
VariableType.REAL,
VariableType.INTEGER,
VariableType.INTEGER,
],
},
"backend": {
"method": "differential_evolution",
"options": {"rng": 4},
"max_iterations": 50,
},
"realizations": {
"weights": [1.0] * REALIZATIONS,
},
}
Nothing else changes. The realizations, the objective and the call to optimize
are the same as in Ensemble-Based Optimization
— being partly discrete is a property of the variables, not of the problem
around them.
What to expect from the search
A gradient-free method reaches an answer by evaluating many points rather than by following a slope, so plan for a different cost profile:
perturbation_magnitudesis unused. No perturbations are evaluated, because no gradient is estimated.- Budget the run with
max_functionsormax_iterationsrather than a convergence tolerance. - The result is reproducible only if the method's own generator is seeded —
hence
"options": {"rng": 4}in both scripts.
Where to next
- The fields used here, in full:
variablesandbackend. - Which methods support what:
SciPyBackend. - Constraining a discrete problem: Constraints.