‹ Back to Tutorials

The response optimization class

ResponseOptimization searches the input domain of a trained neural network for feasible points that minimize, maximize or fix selected inputs and outputs.

It supports continuous, integer and categorical inputs, multiple objectives, mathematical constraints, discrete choices and cardinality rules. The neural network is used as a fast surrogate model during the search.

Contents:

  1. Prepare the trained model
  2. Create the optimization
  3. Set objectives
  4. Constrain individual variables
  5. Add formula constraints
  6. Add cardinality constraints
  7. Control the search
  8. Run and interpret the result

1. Prepare the trained model

Response optimization requires a trained network with meaningful input and output names. For tabular models, its scaling and unscaling descriptives define the original search domains:

NeuralNetwork neural_network(
    "trained_process_model.json");

neural_network.set_input_names(
    {"temperature", "pressure", "flow"});
neural_network.set_output_names(
    {"yield", "energy"});

Normally the names and descriptives are already stored in the trained model. Set them explicitly only when constructing a model in code. Optimization values are expressed in the original engineering units, not in scaled network units.

2. Create the optimization

The optimizer keeps a non-owning pointer to the network:

ResponseOptimization optimization(
    &neural_network);

The network must remain alive until the search ends. Calling set with another network resets the cached variable domains and derivatives.

3. Set objectives

An objective can refer to an input or an output. Use Minimize, Maximize or Fixed:

optimization.set_objective(
    "yield",
    ResponseOptimization::Sense::Maximize);

optimization.set_objective(
    "energy",
    ResponseOptimization::Sense::Minimize);

One minimizing or maximizing objective returns the best point found. Two or more return a Pareto front of non-dominated trade-offs. A fixed input becomes an equality constraint, while a fixed output is enforced within a tolerance band:

optimization.set_objective(
    "flow",
    ResponseOptimization::Sense::Fixed,
    12.5f);

4. Constrain individual variables

Restrict inputs and outputs by their model names:

using enum ComparisonOperator;

optimization.set_constraint(
    "temperature", Between, 20.0f, 80.0f);

optimization.set_constraint(
    "pressure", GreaterEqualTo, 1.0f);

optimization.set_constraint(
    "energy", LessEqualTo, 0.0f, 140.0f);

For Between, pass the lower and upper bounds. Greater-than comparisons use the lower-bound argument; less-than comparisons use the upper-bound argument. Constraints may target both inputs and predicted outputs.

Allowed sets keep a variable on explicit discrete values:

optimization.set_constraint(
    "pressure",
    vector<float>{1.0f, 1.5f, 2.0f});

5. Add formula constraints

Formula constraints combine named inputs and outputs. The parser supports arithmetic, powers and common mathematical functions such as sqrt, exp, log, abs, trigonometric functions, min and max:

optimization.set_formula_constraint(
    "temperature + 0.4*pressure",
    ComparisonOperator::LessEqualTo,
    0.0f,
    82.0f);

optimization.set_formula_constraint(
    "yield / max(energy, 1)",
    ComparisonOperator::GreaterEqualTo,
    0.10f);

Input-only formulas can often be repaired directly during sampling. Constraints involving outputs use network evaluations and derivatives where available. A callback overload is available when an expression cannot represent the project rule.

6. Add cardinality constraints

A cardinality constraint requires exactly k variables in a group to be active. This is useful for selecting operating units, channels or optional resources:

optimization.set_cardinality_constraint(
    {"pump_1", "pump_2", "pump_3"},
    2,
    true);

With force_nonzero set to true, selected variables must take a non-zero feasible value. Integer, categorical and allowed-set variables remain on their valid discrete lattice throughout the search.

7. Control the search

Configure the number of samples per iteration, refinement iterations, global budget and exploration rate:

optimization.set_evaluations_number(4000);
optimization.set_iterations(20);
optimization.set_max_total_evaluations(50000);
optimization.set_max_oversample_factor(8);
optimization.set_exploration_ratio(0.10f);
optimization.set_relative_tolerance(1.0e-6f);
optimization.set_branch_mode(
    ResponseOptimization::BranchMode::Budgeted);

Budgeted limits combinatorial branching to the evaluation budget. Exhaustive explores every discrete branch and can become expensive when many allowed sets or categorical choices are present.

If no feasible points are found, first check contradictory constraints, then increase the evaluation or oversampling budget.

8. Run and interpret the result

Run the optimization after all objectives and constraints have been defined:

const MatrixR optimal_points =
    optimization.perform_response_optimization();

Each row contains the input features followed by the predicted outputs. A single objective normally produces one best row; a multi-objective search produces the Pareto front.

Select one balanced point from a Pareto front by assigning an importance to every objective:

const auto [row_index, advised_point] =
    optimization.get_advised_point(
        optimal_points,
        VectorR::Ones(
            optimization.get_objectives_number()));

The advised point is the Pareto row nearest to the weighted utopian objective. Always validate the selected operating point against real constraints and domain knowledge before deployment; it is an optimum of the surrogate model, not a physical guarantee.

References