From b6d3486adcfb22c838bc65631e55f783a6380155 Mon Sep 17 00:00:00 2001 From: Shruti Patel Date: Fri, 30 Jan 2026 10:52:26 -0800 Subject: [PATCH] Handle all-infeasible traces in UtilityProgressionAnalysis (#4837) Summary: When `get_trace` returns all infeasible points (represented as `inf` or `-inf` values), the utility progression plot would previously attempt to plot these infinite values. This change adds a check to raise an `ExperimentNotReadyError` with a clear message when all trials are infeasible, preventing the plot from displaying meaningless data. Differential Revision: D91751120 --- .../plotly/tests/test_utility_progression.py | 19 +++++++++++++++++++ ax/analysis/plotly/utility_progression.py | 8 ++++++++ 2 files changed, 27 insertions(+) diff --git a/ax/analysis/plotly/tests/test_utility_progression.py b/ax/analysis/plotly/tests/test_utility_progression.py index bf697753e1e..cfaf90e214e 100644 --- a/ax/analysis/plotly/tests/test_utility_progression.py +++ b/ax/analysis/plotly/tests/test_utility_progression.py @@ -5,12 +5,16 @@ # pyre-strict +import math +from unittest.mock import patch + from ax.analysis.plotly.plotly_analysis import PlotlyAnalysisCard from ax.analysis.plotly.utility_progression import UtilityProgressionAnalysis from ax.core.auxiliary import AuxiliaryExperiment, AuxiliaryExperimentPurpose from ax.core.metric import Metric from ax.core.objective import MultiObjective, Objective from ax.core.optimization_config import PreferenceOptimizationConfig +from ax.exceptions.core import ExperimentNotReadyError from ax.utils.common.testutils import TestCase from ax.utils.testing.core_stubs import ( get_branin_experiment, @@ -199,3 +203,18 @@ def test_scalarized_objective_support(self) -> None: # Assert: Check that title/subtitle show the formula self.assertIn("formula:", card.subtitle) + + def test_all_infeasible_points_raises_error(self) -> None: + """Test that an error is raised when all points are infeasible.""" + experiment = get_branin_experiment(with_completed_trial=True) + + with ( + patch( + "ax.analysis.plotly.utility_progression.get_trace", + return_value=[math.inf, -math.inf, math.inf], + ), + self.assertRaises(ExperimentNotReadyError) as cm, + ): + self.analysis.compute(experiment=experiment) + + self.assertIn("infeasible", str(cm.exception).lower()) diff --git a/ax/analysis/plotly/utility_progression.py b/ax/analysis/plotly/utility_progression.py index 2653cbf7d28..b9f7df7d668 100644 --- a/ax/analysis/plotly/utility_progression.py +++ b/ax/analysis/plotly/utility_progression.py @@ -7,6 +7,7 @@ from typing import final +import numpy as np import pandas as pd import plotly.express as px from ax.adapter.base import Adapter @@ -113,6 +114,13 @@ def compute( "constraints." ) + # Check if all points are infeasible (inf or -inf values) + if all(np.isinf(value) for value in trace): + raise ExperimentNotReadyError( + "All trials in the utility trace are infeasible (violate outcome " + "constraints). No feasible points to plot." + ) + # Create DataFrame with 1-based trace index for user-friendly display # (1st completed trial, 2nd completed trial, etc. instead of 0-indexed) df = pd.DataFrame(