Skip to content

[Bug]: Parameter constraints not working for ChoiceParameters #5256

Description

@benrabe93

What happened?

Hello.

I've noticed that parameter_constraints provided to ax.api.client.Client.configure_experiment don't work properly for ChoiceParameters.
Below you'll find a minimum example as a demonstration.

Thanks in advance for taking the time to address this bug!

Please provide a minimal, reproducible example of the unexpected behavior.

from ax.api.client import Client
from ax.api.configs import RangeParameterConfig

parameters = [
    RangeParameterConfig(name="p1", bounds=(0.0, 1.0), parameter_type="float"),
    RangeParameterConfig(name="p2", bounds=(0.0, 1.0), parameter_type="float", step_size=0.01),
]

parameter_constraints = ["p1 <= p2"]

client = Client()
client.configure_experiment(parameters=parameters, parameter_constraints=parameter_constraints)
client.configure_optimization(objective="score")
client.configure_generation_strategy(initialize_with_center=False)

client.get_next_trials(max_trials=5)

Output:

{0: {'p1': 0.39548560976982117, 'p2': 0.31},
 1: {'p1': 0.8505668397992849, 'p2': 0.73},
 2: {'p1': 0.5795025620609522, 'p2': 0.03},
 3: {'p1': 0.1595833096653223, 'p2': 0.89},
 4: {'p1': 0.10090977512300014, 'p2': 0.17}}

Please paste any relevant traceback/logs produced by the example provided.

Ax Version

1.3.1

Python Version

3.12.13

Operating System

Ubuntu 26

(Optional) Describe any potential fixes you've considered to the issue outlined above.

OrderedChoiceToIntegerRange re-encodes an ordered numeric choice to its list indices 0..n-1, so value v_j becomes index j. The constraint is carried through ParameterConstraint.clone_with_transformed_parameters, which is a silent no-op — coefficients are never rescaled, so the constraint ends up enforced against the index instead of the value. (By contrast, UnitX correctly rescales constraints on float RangeParameters, which is why an all-
RangeParameter search space respects the same constraint.)

A linear constraint w·p <= b stays linear after index encoding only if j -> v_j is affine, i.e. the choice values are evenly spaced. This gives a two-part fix:

  1. Remap constraints for the evenly-spaced (representable) case, inline in OrderedChoiceToIntegerRange.transform_search_space, mirroring how UnitX rescales float-range constraints. With affine encoding v_j = a·j + c (step a, offset c):

    w·v_j <= b   =>   (w·a)·j <= b - w·c
    

    i.e. new weight w·a, new bound b - w·c.

  2. Reject the non-representable case early. For non-uniformly-spaced values no correct linear constraint exists, so add a check to validate_constraint_parameters (next to the existing log-scale rejection) that raises a clear error when a constrained ordered ChoiceParameter's values are not evenly spaced — turning a silently-violating optimization into an actionable configuration-time error.

Note: clone_with_transformed_parameters receives only the post-transform parameters, so it cannot recover a/c on its own; doing the remap inside the transform (where both original and encoded parameters are in scope) is the
cleaner fix.

Pull Request

None

Code of Conduct

  • I agree to follow Ax's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions