Skip to content

Commit 482f129

Browse files
shrutipatel31meta-codesync[bot]
authored andcommitted
Improve validate_applicable_state error messages - High Priority Updates (#5126)
Summary: Pull Request resolved: #5126 Fix actively misleading or broken NotApplicableState error messages across ax/analysis: - **utils.py**: Replace raw Python kwarg f-string syntax ({trial_indices=}, {trial_statuses=}) with readable key=value output in `validate_experiment_has_trials` messages - **utils.py**: Remove raw Adapter repr ({adapter}) from `validate_adapter_can_predict` messages; replace with user-readable text - **best_trials.py**: Replace backtick-quoted class names (`OptimizationConfig`, `GenerationStrategy`) with plain-language descriptions and actionable steps - **sensitivity.py**: Replace opaque "`TorchAdapter` is required." with a message explaining what the user needs to do - **top_surfaces.py**: Remove {type(relevant_adapter)} type repr; replace with plain-language message matching `sensitivity.py` - **marginal_effects.py**: Fix typo "but got.'" (missing space before quote) and remove {type(parameter).`__name__`} internal class name from error message - **objective_p_feasible_frontier.py**: Fix "Optimization_config" underscore bug (should read "optimization config"), and replace raw Adapter repr ({relevant_adapter}, {relevant_adapter.generator}) with `__name__` type strings Audited in https://docs.google.com/document/d/1mxkLvXz0SvdWqO9UrVq5hsFY6MlcwDSk7oVRzdU6kN0/edit?tab=t.0 with the help of AI Agent Reviewed By: bernardbeckerman Differential Revision: D97528392 fbshipit-source-id: c4557c66977f18b545a0bb70093361b7705ff45f
1 parent 7292287 commit 482f129

11 files changed

Lines changed: 95 additions & 41 deletions

ax/analysis/best_trials.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ def validate_applicable_state(
100100
# Validate optimization config exists
101101
if experiment is None or experiment.optimization_config is None:
102102
return (
103-
"`BestTrials` analysis requires an `OptimizationConfig`. "
104-
"Ensure the `Experiment` has an `optimization_config` set to compute "
105-
"this analysis."
103+
"BestTrials requires an OptimizationConfig with defined objective(s). "
104+
"Define the optimization objective during experiment setup (via "
105+
"configure_optimization in the Client) before running this analysis."
106106
)
107107

108108
# Check for trials with required status
@@ -121,9 +121,10 @@ def validate_applicable_state(
121121
if self.use_model_predictions or optimization_config.is_moo_problem:
122122
if generation_strategy is None:
123123
return (
124-
"`BestTrials` analysis requires a `GenerationStrategy` input "
125-
"when using model predictions or for multi-objective "
126-
"optimization problems."
124+
"BestTrials requires a GenerationStrategy when using model "
125+
"predictions or for multi-objective optimization. This analysis "
126+
"requires a predictive model, which becomes available after "
127+
"enough trials have completed. Run more trials to enable it."
127128
)
128129

129130
return None

ax/analysis/plotly/marginal_effects.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,9 @@ def validate_applicable_state(
104104
parameter = experiment.parameters.get(param_name)
105105
if not isinstance(parameter, ChoiceParameter):
106106
return (
107-
"MarginalEffectsPlot is only for `ChoiceParameter`s, but got."
108-
f"'{param_name}' which is of type {type(parameter).__name__}."
107+
f"MarginalEffectsPlot is only applicable to ChoiceParameters, "
108+
f"but '{param_name}' is a {type(parameter).__name__}, not a "
109+
"ChoiceParameter. Check the experiment's parameter types."
109110
)
110111

111112
@override

ax/analysis/plotly/objective_p_feasible_frontier.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,10 @@ def validate_applicable_state(
128128
experiment = none_throws(experiment)
129129

130130
if experiment.optimization_config is None:
131-
return "Optimization_config must be set to compute frontier."
131+
return (
132+
"The experiment must have an OptimizationConfig set in "
133+
"order to compute the objective vs. P(feasible) frontier."
134+
)
132135

133136
if isinstance(experiment.optimization_config, MultiObjectiveOptimizationConfig):
134137
return "Multi-objective optimization is not supported."
@@ -158,10 +161,11 @@ def validate_applicable_state(
158161
relevant_adapter.generator, BoTorchGenerator
159162
):
160163
return (
161-
"The Objective vs P(feasible) plot cannot be computed using the"
162-
f" current Adapter ({relevant_adapter}) and generator"
163-
f" ({relevant_adapter.generator}). Only TorchAdapters using"
164-
" BoTorchGenerators are supported."
164+
"This plot requires a TorchAdapter using a BoTorchGenerator. "
165+
f"The current adapter is a {type(relevant_adapter).__name__} with "
166+
f"a {type(relevant_adapter.generator).__name__} generator. "
167+
"This error will resolve once the optimization progresses "
168+
"to a Bayesian modeling stage."
165169
)
166170

167171
@override

ax/analysis/plotly/sensitivity.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,11 @@ def validate_applicable_state(
116116
)
117117

118118
if not isinstance(relevant_adapter, TorchAdapter):
119-
return "TorchAdapter is required."
119+
return (
120+
"This analysis requires a fitted Bayesian model (TorchAdapter). "
121+
"Ensure the optimization has run enough trials and the generation "
122+
"strategy has reached a model-based stage."
123+
)
120124

121125
@override
122126
def compute(

ax/analysis/plotly/tests/test_marginal_effects.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,16 @@ def test_validate_applicable_state(self) -> None:
8686
data=self.experiment.lookup_data(trial_indices=[0]),
8787
)
8888

89+
result = none_throws(
90+
self.analysis_all_variables.validate_applicable_state(
91+
experiment=self.experiment, adapter=adapter
92+
)
93+
)
8994
self.assertIn(
90-
"MarginalEffectsPlot is only for `ChoiceParameter`s",
91-
none_throws(
92-
self.analysis_all_variables.validate_applicable_state(
93-
experiment=self.experiment, adapter=adapter
94-
)
95-
),
95+
"MarginalEffectsPlot is only applicable to ChoiceParameters", result
9696
)
97+
# Verify type(parameter).__name__ rendered correctly (x is a RangeParameter)
98+
self.assertIn("RangeParameter", result)
9799

98100
def test_compute(self) -> None:
99101
adapter = get_thompson(

ax/analysis/plotly/tests/test_objective_p_feasible_frontier.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ def test_validate_applicable_state(self) -> None:
162162
opt_config = self.experiment.optimization_config
163163
self.experiment._optimization_config = None
164164
self.assertIn(
165-
"Optimization_config must be set to compute frontier",
165+
"The experiment must have an OptimizationConfig set in "
166+
"order to compute the objective vs. P(feasible) frontier",
166167
none_throws(
167168
ObjectivePFeasibleFrontierPlot().validate_applicable_state(
168169
experiment=self.experiment
@@ -215,6 +216,24 @@ def test_validate_applicable_state(self) -> None:
215216
),
216217
)
217218

219+
# Restore valid constraints and verify type().__name__ renders correctly
220+
# for the adapter/generator type check
221+
metric_name = self.experiment.metrics["branin_b"].name
222+
opt_config.outcome_constraints = [
223+
OutcomeConstraint(
224+
expression=f"{metric_name} <= 10.0",
225+
metric_name_to_signature={metric_name: metric_name},
226+
),
227+
]
228+
adapter = Generators.SOBOL(experiment=self.experiment)
229+
result = none_throws(
230+
ObjectivePFeasibleFrontierPlot().validate_applicable_state(
231+
experiment=self.experiment, adapter=adapter
232+
)
233+
)
234+
self.assertIn("RandomAdapter", result)
235+
self.assertIn("SobolGenerator", result)
236+
218237
def test_scalarized_objective_raises(self) -> None:
219238
"""Scalarized objectives should be rejected in validate_applicable_state."""
220239
self.experiment.optimization_config = OptimizationConfig(

ax/analysis/plotly/tests/test_sensitivity.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,18 @@ def test_task_params_excluded(self) -> None:
251251
exclude_task=True,
252252
)
253253

254+
def test_validate_applicable_state_non_torch_adapter(self) -> None:
255+
client = get_test_client()
256+
adapter = Generators.SOBOL(
257+
experiment=client.experiment,
258+
)
259+
analysis = SensitivityAnalysisPlot(metric_name="bar")
260+
reason = analysis.validate_applicable_state(adapter=adapter)
261+
self.assertIn(
262+
"This analysis requires a fitted Bayesian model (TorchAdapter). ",
263+
none_throws(reason),
264+
)
265+
254266
def test_wrap_label(self) -> None:
255267
cases = [
256268
("short name unchanged", "x", "x"),

ax/analysis/plotly/tests/test_top_surfaces.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,17 @@ def test_validate_applicable_state(self) -> None:
5454
},
5555
)
5656

57+
result = none_throws(
58+
TopSurfacesAnalysis(
59+
metric_name="bar", order="first"
60+
).validate_applicable_state(client._experiment, client._generation_strategy)
61+
)
5762
self.assertIn(
58-
"TorchAdapter is required",
59-
none_throws(
60-
TopSurfacesAnalysis(
61-
metric_name="bar", order="first"
62-
).validate_applicable_state(
63-
client._experiment, client._generation_strategy
64-
)
65-
),
63+
"This analysis requires a fitted Bayesian model (TorchAdapter)",
64+
result,
6665
)
66+
# Verify type(relevant_adapter).__name__ rendered correctly
67+
self.assertIn("RandomAdapter", result)
6768
for _ in range(5):
6869
for trial_index, parameterization in client.get_next_trials(
6970
max_trials=1
@@ -77,7 +78,7 @@ def test_validate_applicable_state(self) -> None:
7778
)
7879

7980
self.assertIn(
80-
"no data for metrics {'baz'}",
81+
"Data for the following metrics is not yet available: {'baz'}",
8182
none_throws(
8283
TopSurfacesAnalysis(
8384
metric_name="baz", order="first"

ax/analysis/plotly/top_surfaces.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,11 @@ def validate_applicable_state(
112112
)
113113

114114
if not isinstance(relevant_adapter, TorchAdapter):
115-
return f"TorchAdapter is required, found {type(relevant_adapter)}."
115+
return (
116+
"This analysis requires a fitted Bayesian model (TorchAdapter). "
117+
f"The current adapter is a {type(relevant_adapter).__name__}. "
118+
"Wait for the optimization to progress to a model-based stage."
119+
)
116120
except UserInputError as e:
117121
return e.message
118122

ax/analysis/tests/test_best_trials.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ def test_generation_strategy_requirements(self) -> None:
162162
experiment=self.experiment, generation_strategy=None
163163
)
164164
self.assertIsNotNone(error)
165-
self.assertIn("requires a `GenerationStrategy`", error)
165+
self.assertIn("requires a GenerationStrategy", error)
166166

167167
def test_trial_status_filter(self) -> None:
168168
"""Test that trial_statuses parameter filters correctly."""

0 commit comments

Comments
 (0)