Repository navigation
[Bug]: Multi-metric outcome constraints are silently reduced to a single metric #5309
Description
What happened?
A linear outcome constraint over several metrics, such as "a + 2 * b <= 1.0", is accepted by Client.configure_optimization. When candidates are generated, though, all but one metric is dropped and the remaining one gets weight 1. There is no error or warning, so the optimizer works with a different constraint than the one the user specified.
Please provide a minimal, reproducible example of the unexpected behavior.
import ax.adapter.torch as torch_adapter
from ax.adapter.adapter_utils import extract_outcome_constraints
from ax.api.client import Client
from ax.api.configs import RangeParameterConfig
captured = []
def spy(outcome_constraints, outcomes):
result = extract_outcome_constraints(outcome_constraints, outcomes)
captured.append((outcomes, result))
return result
torch_adapter.extract_outcome_constraints = spy
client = Client()
client.configure_experiment(
parameters=[RangeParameterConfig(name="x", parameter_type="float", bounds=(0.0, 1.0))]
)
client.configure_optimization(objective="obj", outcome_constraints=["a + 2 * b <= 1.0"])
while not captured: # run until the model-based node is used
for idx, params in client.get_next_trials(max_trials=1).items():
x = params["x"]
client.complete_trial(trial_index=idx, raw_data={"obj": x, "a": x, "b": 1 - x})
outcomes, (A, b) = captured[-1]
print(outcomes, A)Expected: one row of A with non-zero entries for both a and b, in the ratio 1 : 2.
Actual:
['a', 'b', 'obj'] [[0. 1. 0.]]
Only b is left, with weight 1.
Please paste any relevant traceback/logs produced by the example provided.
Ax Version
1.3.1
Python Version
3.12
Operating System
Linux Mint
(Optional) Describe any potential fixes you've considered to the issue outlined above.
Cause
extract_outcome_constraints (ax/adapter/adapter_utils.py) only applies all of a constraint's weights when the constraint is an instance of ScalarizedOutcomeConstraint. Otherwise it uses c.metric_signatures[0] with weight 1. ScalarizedOutcomeConstraint is now deprecated in favour of OutcomeConstraint(expression=...), and optimization_config_from_string creates a plain OutcomeConstraint for multi-metric expressions. Those constraints therefore always take the single-metric branch.
The same isinstance check in Derelativize._compute_sq_val (ax/adapter/transforms/derelativize.py) has the same effect on relative constraints. For example, "a + b >= 0.9 * baseline" is accepted, even though the docstring of configure_optimization says scalarized constraints cannot be relative. Its bound is then derived from only the first metric's status quo value.
Suggested fix
In both places, branch on len(c.metric_weights) > 1 instead of isinstance(c, ScalarizedOutcomeConstraint), and use c.metric_weights for the coefficients. Separately, reject relative constraints over several metrics when parsing, if they're not meant to be supported.
Pull Request
None
Code of Conduct
- I agree to follow Ax's Code of Conduct
Activity
[-][Bug]: Multi-metric outcome constraints from are silently reduced to a single metric[/-][+][Bug]: Multi-metric outcome constraints are silently reduced to a single metric[/+]akhil9tiet commentedon Oct 22, 2026
I'd like to work on this — please assign it to me.
saitcakmak commentedon Oct 22, 2026
Thanks for flagging and providing a nice repro. Fixed in #5310.
Thanks for the offer @akhil9tiet. Due to review requirements, it is often simpler for me to fix the bug directly than to review & land an external PR these days.