From 83b51800eadbbaeb41a735179e4dc0756e78b25a Mon Sep 17 00:00:00 2001 From: vizier-team Date: Tue, 16 Sep 2025 08:19:11 -0700 Subject: [PATCH] Error in early stopping when dataset size is zero. NB: numpy.concatenate([]) errors out. PiperOrigin-RevId: 807701489 --- vizier/pyvizier/converters/spatio_temporal.py | 24 +++++++++---------- .../converters/spatio_temporal_test.py | 17 +++++++++++++ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/vizier/pyvizier/converters/spatio_temporal.py b/vizier/pyvizier/converters/spatio_temporal.py index 35215552a..4b87834e4 100644 --- a/vizier/pyvizier/converters/spatio_temporal.py +++ b/vizier/pyvizier/converters/spatio_temporal.py @@ -215,7 +215,7 @@ def extract_all_timestamps( This is a cheaper alternative to: ``` ts = np.concatenate([np.asarray(tl.times).flatten() for tl in timed_labels]) - return sorted(list(set(ts))) + return sorted(set(ts)) ``` because it skips the label conversions. @@ -225,9 +225,10 @@ def extract_all_timestamps( Returns: Sorted list of temporal indices. """ - all_timestamps = np.concatenate( - [self.to_timestamps(t.measurements).flatten() for t in trials]) - return sorted(list(set(all_timestamps))) + all_timestamps: set[float] = set() + for t in trials: + all_timestamps.update(self.to_timestamps(t.measurements).flatten()) + return sorted(all_timestamps) class SparseSpatioTemporalConverter(core.TrialToNumpyDict): @@ -359,7 +360,7 @@ def __init__(self, logging.info('Empty temporal_index_points.') self.temporal_index_points = np.zeros([0]) else: - self.temporal_index_points = temporal_index_points + self.temporal_index_points = np.sort(temporal_index_points) def _single_timedlabels_to_temporal_observations( self, timed_labels: TimedLabels, ts: Union[Sequence[float], Sequence[int]] @@ -493,7 +494,8 @@ def to_xty( 3-tuple of (input, temporal_index_points, observations). input: A Dict whose values correspond to parameters. temporal_index_points: 1-D array of temporal index points. If provided - as input, then it's the same as the input value. + as input, then it's the same as the input value. The return value is + np.array([]) if there are no labels. observations: Dict of length equal to metrics, whose values have shape [len(trials), len(temporal_index_points)]. May contain NaNs. """ @@ -506,12 +508,10 @@ def to_xty( temporal_index_points = self.temporal_index_points elif temporal_selection == 'infer' or (temporal_selection == 'auto' and not self.temporal_index_points.size): - temporal_index_points = sorted( - list( - set( - np.concatenate([ - np.asarray(tl.times).flatten() for tl in timed_labels - ])))) + tmp: set[float | int] = set() + for tl in timed_labels: + tmp.update(tl.times.flatten()) + temporal_index_points = sorted(tmp) # [] when tmp is the empty set. else: raise ValueError('Invalid value for temporal_index_points: ' f'{temporal_selection}.') diff --git a/vizier/pyvizier/converters/spatio_temporal_test.py b/vizier/pyvizier/converters/spatio_temporal_test.py index 6e2251f33..6af882667 100644 --- a/vizier/pyvizier/converters/spatio_temporal_test.py +++ b/vizier/pyvizier/converters/spatio_temporal_test.py @@ -267,6 +267,23 @@ def test_xty(self): }, err_msg=f'{labels}') + def test_xty_no_trials(self): + """Tests that to_xty returns empty temporal_index_points for no trials.""" + trials = [] + extractor = st.TimedLabelsExtractor( + _metric_converters, 'steps', value_extraction='raw' + ) + parameter = core.DefaultModelInputConverter( + pyvizier.ParameterConfig.factory(name='x1', bounds=(0, 5)) + ) + converter = st.DenseSpatioTemporalConverter( + [parameter], extractor, temporal_index_points=None + ) + _, temporal_index_points, _ = converter.to_xty( + trials, temporal_selection='infer' + ) + self.assertEqual(temporal_index_points.size, 0) + if __name__ == '__main__': absltest.main()