Skip to content

Commit bdd8491

Browse files
eonofreymeta-codesync[bot]
authored andcommitted
Make Metric Name Space checks less broad (#5042)
Summary: Pull Request resolved: #5042 Isolate checks for spaces in metric names to only areas where they are relevant (e.g. parsing string constraints for experiment creation) Reviewed By: ItsMrLin Differential Revision: D96814558 fbshipit-source-id: b5a0ae8f850f5e4d3960ec2da3522600726b7597
1 parent ef72a7d commit bdd8491

File tree

3 files changed

+57
-31
lines changed

3 files changed

+57
-31
lines changed

ax/core/tests/test_outcome_constraint.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ def setUp(self) -> None:
165165
self.minimize_metric = Metric(name="bar", lower_is_better=True)
166166
self.maximize_metric = Metric(name="baz", lower_is_better=False)
167167
self.ambiguous_metric = Metric(name="buz")
168+
self.bound = 0.5
168169

169170
def test_Init(self) -> None:
170171
with warnings.catch_warnings(record=True) as w:

ax/service/tests/test_instantiation_utils.py

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,19 @@ def test_parameter_type_validation(self) -> None:
3434
InstantiationBase._get_parameter_type(list)
3535

3636
def test_make_search_space(self) -> None:
37-
with self.assertRaisesRegex(ValueError, "cannot contain spaces"):
38-
InstantiationBase.make_search_space(
39-
parameters=[
40-
{
41-
"name": "x space 1",
42-
"type": "range",
43-
"bounds": [0.0, 1.0],
44-
}
45-
],
46-
parameter_constraints=None,
47-
)
37+
# Parameter names with spaces should be allowed in search spaces
38+
# (only rejected in constraint string parsing).
39+
ss = InstantiationBase.make_search_space(
40+
parameters=[
41+
{
42+
"name": "x space 1",
43+
"type": "range",
44+
"bounds": [0.0, 1.0],
45+
}
46+
],
47+
parameter_constraints=None,
48+
)
49+
self.assertIn("x space 1", ss.parameters)
4850

4951
def test_constraint_from_str(self) -> None:
5052
x1 = RangeParameter(
@@ -96,7 +98,7 @@ def test_constraint_from_str(self) -> None:
9698
"x1 + x2 + x3 <= 3", {"x1": x1, "x2": x2, "x3": x3}
9799
)
98100

99-
with self.assertRaisesRegex(AssertionError, "Outcome constraint 'm1"):
101+
with self.assertRaisesRegex(ValueError, "Outcome constraint 'm1"):
100102
InstantiationBase.outcome_constraint_from_str("m1 == 2*m2")
101103

102104
self.assertEqual(three_val_constaint.bound, 3.0)
@@ -191,6 +193,29 @@ def test_constraint_from_str(self) -> None:
191193
"x1 + x2 / 2.0 + x3 >= 3", {"x1": x1, "x2": x2, "x3": x3}
192194
)
193195

196+
def test_spaces_in_metric_and_parameter_names(self) -> None:
197+
# Metric and parameter names with spaces are allowed everywhere
198+
# except in constraint string parsing, where split() would break.
199+
metric = InstantiationBase._make_metric(name="my metric")
200+
self.assertEqual(metric.name, "my metric")
201+
202+
experiment = InstantiationBase.make_experiment(
203+
parameters=[{"name": "x", "type": "range", "bounds": [0, 1]}],
204+
tracking_metric_names=["my metric", "another metric"],
205+
)
206+
tracking_metric_names = [m.name for m in experiment.tracking_metrics]
207+
self.assertIn("my metric", tracking_metric_names)
208+
self.assertIn("another metric", tracking_metric_names)
209+
210+
# Constraint string parsing rejects spaces (can't tokenize correctly).
211+
x1 = RangeParameter(
212+
name="x 1", parameter_type=ParameterType.FLOAT, lower=0.1, upper=4.0
213+
)
214+
with self.assertRaisesRegex(ValueError, "cannot contain spaces"):
215+
InstantiationBase.constraint_from_str("x 1 <= 3", {"x 1": x1})
216+
with self.assertRaisesRegex(ValueError, "cannot contain spaces"):
217+
InstantiationBase.outcome_constraint_from_str("my metric <= 3")
218+
194219
def test_add_tracking_metrics(self) -> None:
195220
experiment = InstantiationBase.make_experiment(
196221
parameters=[{"name": "x", "type": "range", "bounds": [0, 1]}],
@@ -212,8 +237,12 @@ def test_make_objectives(self) -> None:
212237
with self.assertRaisesRegex(ValueError, "specify 'minimize' or 'maximize'"):
213238
InstantiationBase.make_objectives({"branin": "unknown"})
214239

215-
with self.assertRaisesRegex(ValueError, "cannot contain spaces"):
216-
InstantiationBase.make_objectives({"branin space": "maximize"})
240+
# Metric names with spaces should be allowed in objectives
241+
# (only rejected in constraint string parsing).
242+
objectives_with_spaces = InstantiationBase.make_objectives(
243+
{"branin space": "maximize"}
244+
)
245+
self.assertEqual(objectives_with_spaces[0].metric_names[0], "branin space")
217246

218247
objectives = InstantiationBase.make_objectives(
219248
{"branin": "minimize", "currin": "maximize"}

ax/service/utils/instantiation.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,6 @@ def _make_metric(
174174
for_opt_config: bool = False,
175175
metric_definitions: dict[str, dict[str, Any]] | None = None,
176176
) -> Metric:
177-
if " " in name:
178-
raise ValueError(
179-
"Metric names cannot contain spaces when used with AxClient. Got "
180-
f"{name!r}."
181-
)
182-
183177
metric_definitions = metric_definitions or {}
184178

185179
metric_class, kwargs = cls._get_deserialized_metric_kwargs(
@@ -374,12 +368,6 @@ def parameter_from_json(
374368
"'bool' or 'str'."
375369
)
376370

377-
if " " in name:
378-
raise ValueError(
379-
"Parameter names cannot contain spaces when used with AxClient. Got "
380-
f"{name!r}."
381-
)
382-
383371
if parameter_class == "range":
384372
return cls._make_range_param(
385373
name=name,
@@ -440,6 +428,12 @@ def constraint_from_str(
440428
An instantiated ParameterConstraint, either an OrderConstraint or a
441429
ParameterConstraint, representing a linear constraint.
442430
"""
431+
for param_name in parameters:
432+
if " " in param_name:
433+
raise ValueError(
434+
"Parameter names cannot contain spaces when used in "
435+
f"constraint strings. Got {param_name!r}."
436+
)
443437
tokens = representation.split()
444438
try:
445439
float(tokens[-1])
@@ -482,11 +476,13 @@ def outcome_constraint_from_str(
482476
) -> OutcomeConstraint:
483477
"""Parse string representation of an outcome constraint."""
484478
tokens = representation.split()
485-
assert len(tokens) == 3 and tokens[1] in COMPARISON_OPS, (
486-
f"Outcome constraint '{representation}' should be of "
487-
"form `metric_name >= x`, where x is a "
488-
"float bound and comparison operator is >= or <=."
489-
)
479+
if len(tokens) != 3 or tokens[1] not in COMPARISON_OPS:
480+
raise ValueError(
481+
f"Outcome constraint '{representation}' should be of "
482+
"form `metric_name >= x`, where x is a float bound and "
483+
"comparison operator is >= or <=. Note that metric names "
484+
"cannot contain spaces in constraint strings."
485+
)
490486
op = COMPARISON_OPS[tokens[1]]
491487
rel = False
492488
try:

0 commit comments

Comments
 (0)