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
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:
Network network(
"trained_process_model.json");
network.set_input_names(
{"temperature", "pressure", "flow"});
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
ResponseOptimization defines the problem; two classes implement the search. DomainContraction samples feasible points and contracts the search box around the best ones, and GeneticResponse evolves a population of feasible points. Both keep a non-owning pointer to the network:
DomainContraction optimization(
&network);
The network must remain alive until the search ends. Calling set with another network clears the objectives, constraints and cached variable domains.
3. Set objectives
An objective is an expression of the inputs and outputs, often a single variable name. Use Minimize, Maximize or Fixed:
using Sense = ResponseOptimization::Objective::Sense;
optimization.add_objective("yield", Sense::Maximize);
optimization.add_objective("energy", Sense::Minimize);
One objective returns the best point found. Two or more return a Pareto front of non-dominated trade-offs. A fixed objective drives its expression towards a target value; use an Equal constraint when the value must be met exactly:
optimization.add_objective("flow", Sense::Fixed, 12.5f);
4. Constrain individual variables
Restrict inputs and outputs by their model names:
using Condition = ResponseOptimization::Constraint::Condition;
optimization.add_constraint(
"temperature", Condition::Between, {20.0f, 80.0f});
optimization.add_constraint(
"pressure", Condition::GreaterEqual, {1.0f});
optimization.add_constraint(
"energy", Condition::LessEqual, {140.0f});
For Between, pass the lower and upper bounds. Equal, GreaterEqual, LessEqual, Greater and Less take one value. Constraints may target both inputs and predicted outputs.
Allowed sets keep a variable on explicit discrete values, and Integer keeps it on whole numbers:
optimization.add_constraint(
"pressure",
Condition::AllowedSet, {1.0f, 1.5f, 2.0f});
optimization.add_constraint("flow", Condition::Integer);
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.add_constraint(
"temperature + 0.4*pressure",
Condition::LessEqual, {82.0f});
optimization.add_constraint(
"yield / max(energy, 1)",
Condition::GreaterEqual, {0.10f});
Names that contain spaces or operators are written between backticks, as in `Flow rate (m3/s)` * 2. Input-only formulas can often be repaired directly during sampling. Constraints involving outputs use network evaluations and derivatives where available.
6. Add cardinality constraints
A cardinality constraint allows at most k input variables in a group to be non-zero. This is useful for selecting operating units, channels or optional resources. List the group separated by semicolons:
optimization.add_constraint(
"pump_1; pump_2; pump_3",
Condition::Cardinality, {2.0f});
The count must be a whole number no larger than the group, and every listed variable must be able to reach zero. Integer, categorical and allowed-set variables remain on their valid discrete lattice throughout the search.
7. Control the search
Configure the number of feasible points per iteration, refinement iterations, sampling budget and feasibility repair:
optimization.set_points_number(1000); optimization.set_iterations_number(20); optimization.set_sampling_budget_multiplier(40); optimization.set_maximum_consecutive_failures(500); optimization.set_feasibility_rounds(1); optimization.set_feasibility_evaluations(50); optimization.set_contraction_factor(0.85f);
The sampling budget multiplier limits the sampling rounds per iteration (zero uses the iteration count), and the consecutive-failure limit stops sampling early when candidates cannot be repaired (zero disables it). The feasibility settings control the solver that moves each candidate onto the constraints. set_contraction_factor belongs to DomainContraction and sets how much the search box shrinks at each iteration.
If no feasible points are found, first check contradictory constraints, then increase the sampling budget or the feasibility evaluations.
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 produces one best row; a multi-objective search produces the Pareto front.
Select one balanced point from a Pareto front:
const auto [row_index, advised_point] =
optimization.get_advised_point(optimal_points);
The advised point is the Pareto row nearest to the utopian point, with each objective scaled by its range on the front. 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.