Constrained optimization#

OMADS handles inequality constraints \(c_j(x) \le 0\) returned by the blackbox. Each constraint gets a barrier type in param["constraints_type"]:

Progressive barrier ("PB")

The constraint is relaxable. Infeasible points are retained and ranked by their violation \(h(x) = \sum_j \max\{c_j(x), 0\}^2\), and the threshold \(h_{\max}\) decreases over the iterations to drive the search towards the feasible region [Audet and Dennis, 2009].

Extreme barrier ("EB")

The constraint is unrelaxable (for example, the simulation cannot be run when it is violated), and any point that violates it is rejected. This is the default for constraints without a declared type.

Prefer "PB" whenever the blackbox can still be evaluated at points that violate the constraint. The barrier then also learns from infeasible points, and you can start from an infeasible baseline.

A thin feasible region#

The feasible region of this problem is a thin band of height \(0.1\) that follows a sine wave, \(\sin(x_1) - 0.1 \le x_2 \le \sin(x_1)\). The objective is the distance to \((20, 1)\), which lies just outside the band, and the starting point \((0, -10)\) is far from feasible.

Listing 11 constrained.py#
"""A thin, curved feasible region handled with the progressive barrier."""
import numpy as np
from OMADS import mads


def thin_con(x):
  f = np.sqrt((x[0] - 20.0) ** 2 + (x[1] - 1.0) ** 2)
  c1 = np.sin(x[0]) - 0.1 - x[1]          # x2 >= sin(x1) - 0.1
  c2 = x[1] - np.sin(x[0])                # x2 <= sin(x1)
  return [f, [c1, c2]]


data = {
    "evaluator": {"blackbox": thin_con},
    "param": {
        "name": "thin_con",
        "baseline": [0.0, -10.0],          # infeasible start
        "lb": [0.0, -10.0],
        "ub": [25.0, 10.0],
        "scaling": [25.0, 20.0],
        "constraints_type": ["PB", "PB"],
        "post_dir": "./post",
    },
    "options": {"seed": 0, "budget": 2000, "tol": 1e-9,
                "check_cache": True, "store_cache": True, "rich_direction": True},
    "search": {"type": "sampling", "s_method": "ACTIVE", "ns": 10},
}

if __name__ == "__main__":
  out, *_ = mads.main(data)
  print("xmin =", out["xmin"])
  print("fmin =", out["fmin"], " hmin =", out["hmin"])
  print("evaluations:", out["nbb_evals"])
xmin = [20.0288661159575, 0.9243430346250534]
fmin = [0.08097671924829766]  hmin = 1e-15
evaluations: 536

The run starts in phase one, minimizing \(h\) until a feasible point is found, then improves the objective along the band. The reported hmin of 1e-15 is the machine zero, which means the solution is feasible. The best known value is \(f \approx 0.0989\) at the start of the band, so this run found an even better point near the band’s upper edge.

Reading the results#

  • out["hmin"] is the violation of the returned point. A value above the machine zero means OMADS found no feasible point within the budget, and xmin is then the least infeasible point.

  • poll.main also returns out["Secondary fmin"] and out["Secondary hmin"], the best infeasible point kept by the progressive barrier.

  • With options["save_results"] the Status column of the results file shows FEASIBLE or INFEASIBLE for each evaluation; see Results post-processing.

Tip

  • Scale the constraints so that their violations have comparable magnitudes, because \(h\) sums their squares.

  • Mixing barrier types is allowed: "constraints_type": ["PB", "EB", "PB"].

  • Set param["h_max"] to a finite value to reject, from the start, points that are too infeasible.