From 0f77ca5b2fb7dca573fd523e36b7d4040fe32e16 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sat, 8 Oct 2022 19:24:53 -0700 Subject: [PATCH] Add Learner.new() method that returns the same but empty learner --- adaptive/learner/average_learner.py | 4 ++++ adaptive/learner/average_learner1D.py | 14 ++++++++++++++ adaptive/learner/balancing_learner.py | 10 ++++++++++ adaptive/learner/base_learner.py | 5 +++++ adaptive/learner/data_saver.py | 4 ++++ adaptive/learner/integrator_learner.py | 5 +++++ adaptive/learner/learner1D.py | 6 +++++- adaptive/learner/learner2D.py | 3 +++ adaptive/learner/learnerND.py | 4 ++++ adaptive/learner/sequence_learner.py | 4 ++++ adaptive/learner/skopt_learner.py | 7 +++++++ adaptive/tests/test_learners.py | 17 ++++++++--------- 12 files changed, 73 insertions(+), 10 deletions(-) diff --git a/adaptive/learner/average_learner.py b/adaptive/learner/average_learner.py index 319607ecf..ef27a2ef4 100644 --- a/adaptive/learner/average_learner.py +++ b/adaptive/learner/average_learner.py @@ -74,6 +74,10 @@ def __init__( self.sum_f: Real = 0.0 self.sum_f_sq: Real = 0.0 + def new(self) -> AverageLearner: + """Create a copy of `~adaptive.AverageLearner` without the data.""" + return AverageLearner(self.function, self.atol, self.rtol, self.min_npoints) + @property def n_requested(self) -> int: return self.npoints + len(self.pending_points) diff --git a/adaptive/learner/average_learner1D.py b/adaptive/learner/average_learner1D.py index 847b1431a..ef99e50a5 100644 --- a/adaptive/learner/average_learner1D.py +++ b/adaptive/learner/average_learner1D.py @@ -125,6 +125,20 @@ def __init__( # {xii: error[xii]/min(_distances[xi], _distances[xii], ...} self.rescaled_error: dict[Real, float] = decreasing_dict() + def new(self) -> AverageLearner1D: + """Create a copy of `~adaptive.AverageLearner1D` without the data.""" + return AverageLearner1D( + self.function, + self.bounds, + self.loss_per_interval, + self.delta, + self.alpha, + self.neighbor_sampling, + self.min_samples, + self.max_samples, + self.min_error, + ) + @property def nsamples(self) -> int: """Returns the total number of samples""" diff --git a/adaptive/learner/balancing_learner.py b/adaptive/learner/balancing_learner.py index 57ca01dfa..0215b3af6 100644 --- a/adaptive/learner/balancing_learner.py +++ b/adaptive/learner/balancing_learner.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import itertools from collections import defaultdict from collections.abc import Iterable @@ -96,6 +98,14 @@ def __init__(self, learners, *, cdims=None, strategy="loss_improvements"): self.strategy = strategy + def new(self) -> BalancingLearner: + """Create a new `BalancingLearner` with the same parameters.""" + return BalancingLearner( + [learner.new() for learner in self.learners], + cdims=self._cdims_default, + strategy=self.strategy, + ) + @property def data(self): data = {} diff --git a/adaptive/learner/base_learner.py b/adaptive/learner/base_learner.py index abca3f069..2f3f09af1 100644 --- a/adaptive/learner/base_learner.py +++ b/adaptive/learner/base_learner.py @@ -149,6 +149,11 @@ def _get_data(self): def _set_data(self): pass + @abc.abstractmethod + def new(self): + """Return a new learner with the same function and parameters.""" + pass + def copy_from(self, other): """Copy over the data from another learner. diff --git a/adaptive/learner/data_saver.py b/adaptive/learner/data_saver.py index bb074e530..8da281d7f 100644 --- a/adaptive/learner/data_saver.py +++ b/adaptive/learner/data_saver.py @@ -45,6 +45,10 @@ def __init__(self, learner, arg_picker): self.function = learner.function self.arg_picker = arg_picker + def new(self) -> DataSaver: + """Return a new `DataSaver` with the same `arg_picker` and `learner`.""" + return DataSaver(self.learner.new(), self.arg_picker) + def __getattr__(self, attr): return getattr(self.learner, attr) diff --git a/adaptive/learner/integrator_learner.py b/adaptive/learner/integrator_learner.py index 2d1a571a4..da9bd7ffc 100644 --- a/adaptive/learner/integrator_learner.py +++ b/adaptive/learner/integrator_learner.py @@ -1,4 +1,5 @@ # Based on an adaptive quadrature algorithm by Pedro Gonnet +from __future__ import annotations import sys from collections import defaultdict @@ -381,6 +382,10 @@ def __init__(self, function, bounds, tol): self.add_ival(ival) self.first_ival = ival + def new(self) -> IntegratorLearner: + """Create a copy of `~adaptive.Learner2D` without the data.""" + return IntegratorLearner(self.function, self.bounds, self.tol) + @property def approximating_intervals(self): return self.first_ival.done_leaves diff --git a/adaptive/learner/learner1D.py b/adaptive/learner/learner1D.py index 2bb9981e3..72716c368 100644 --- a/adaptive/learner/learner1D.py +++ b/adaptive/learner/learner1D.py @@ -303,11 +303,15 @@ def __init__( # The precision in 'x' below which we set losses to 0. self._dx_eps = 2 * max(np.abs(bounds)) * np.finfo(float).eps - self.bounds = list(bounds) + self.bounds = tuple(bounds) self.__missing_bounds = set(self.bounds) # cache of missing bounds self._vdim: int | None = None + def new(self) -> Learner1D: + """Create a copy of `~adaptive.Learner1D` without the data.""" + return Learner1D(self.function, self.bounds, self.loss_per_interval) + @property def vdim(self) -> int: """Length of the output of ``learner.function``. diff --git a/adaptive/learner/learner2D.py b/adaptive/learner/learner2D.py index 8c1c1cef5..6c5be845f 100644 --- a/adaptive/learner/learner2D.py +++ b/adaptive/learner/learner2D.py @@ -384,6 +384,9 @@ def __init__(self, function, bounds, loss_per_triangle=None): self.stack_size = 10 + def new(self) -> Learner2D: + return Learner2D(self.function, self.bounds, self.loss_per_triangle) + @property def xy_scale(self): xy_scale = self._xy_scale diff --git a/adaptive/learner/learnerND.py b/adaptive/learner/learnerND.py index 45cca3072..014692f12 100644 --- a/adaptive/learner/learnerND.py +++ b/adaptive/learner/learnerND.py @@ -376,6 +376,10 @@ def __init__(self, func, bounds, loss_per_simplex=None): # _pop_highest_existing_simplex self._simplex_queue = SortedKeyList(key=_simplex_evaluation_priority) + def new(self) -> LearnerND: + """Create a new learner with the same function and bounds.""" + return LearnerND(self.function, self.bounds, self.loss_per_simplex) + @property def npoints(self): """Number of evaluated points.""" diff --git a/adaptive/learner/sequence_learner.py b/adaptive/learner/sequence_learner.py index bf264da79..0cedfe09b 100644 --- a/adaptive/learner/sequence_learner.py +++ b/adaptive/learner/sequence_learner.py @@ -77,6 +77,10 @@ def __init__(self, function, sequence): self.data = SortedDict() self.pending_points = set() + def new(self) -> SequenceLearner: + """Return a new `~adaptive.SequenceLearner` without the data.""" + return SequenceLearner(self._original_function, self.sequence) + def ask(self, n, tell_pending=True): indices = [] points = [] diff --git a/adaptive/learner/skopt_learner.py b/adaptive/learner/skopt_learner.py index 88911d096..e12f49daa 100644 --- a/adaptive/learner/skopt_learner.py +++ b/adaptive/learner/skopt_learner.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import collections import numpy as np @@ -27,8 +29,13 @@ def __init__(self, function, **kwargs): self.function = function self.pending_points = set() self.data = collections.OrderedDict() + self._kwargs = kwargs super().__init__(**kwargs) + def new(self) -> SKOptLearner: + """Return a new `~adaptive.SKOptLearner` without the data.""" + return SKOptLearner(self.function, **self._kwargs) + def tell(self, x, y, fit=True): if isinstance(x, collections.abc.Iterable): self.pending_points.discard(tuple(x)) diff --git a/adaptive/tests/test_learners.py b/adaptive/tests/test_learners.py index 5accf8e0e..8c616e6bb 100644 --- a/adaptive/tests/test_learners.py +++ b/adaptive/tests/test_learners.py @@ -294,7 +294,7 @@ def test_adding_existing_data_is_idempotent(learner_type, f, learner_kwargs): """ f = generate_random_parametrization(f) learner = learner_type(f, **learner_kwargs) - control = learner_type(f, **learner_kwargs) + control = learner.new() if learner_type in (Learner1D, AverageLearner1D): learner._recompute_losses_factor = 1 control._recompute_losses_factor = 1 @@ -345,7 +345,7 @@ def test_adding_non_chosen_data(learner_type, f, learner_kwargs): # XXX: learner, control and bounds are not defined f = generate_random_parametrization(f) learner = learner_type(f, **learner_kwargs) - control = learner_type(f, **learner_kwargs) + control = learner.new() if learner_type is Learner2D: # If the stack_size is bigger then the number of points added, @@ -395,7 +395,7 @@ def test_point_adding_order_is_irrelevant(learner_type, f, learner_kwargs): """ f = generate_random_parametrization(f) learner = learner_type(f, **learner_kwargs) - control = learner_type(f, **learner_kwargs) + control = learner.new() if learner_type in (Learner1D, AverageLearner1D): learner._recompute_losses_factor = 1 @@ -581,7 +581,7 @@ def test_balancing_learner(learner_type, f, learner_kwargs): def test_saving(learner_type, f, learner_kwargs): f = generate_random_parametrization(f) learner = learner_type(f, **learner_kwargs) - control = learner_type(f, **learner_kwargs) + control = learner.new() if learner_type in (Learner1D, AverageLearner1D): learner._recompute_losses_factor = 1 control._recompute_losses_factor = 1 @@ -614,7 +614,7 @@ def test_saving(learner_type, f, learner_kwargs): def test_saving_of_balancing_learner(learner_type, f, learner_kwargs): f = generate_random_parametrization(f) learner = BalancingLearner([learner_type(f, **learner_kwargs)]) - control = BalancingLearner([learner_type(f, **learner_kwargs)]) + control = learner.new() if learner_type in (Learner1D, AverageLearner1D): for l, c in zip(learner.learners, control.learners): @@ -654,7 +654,7 @@ def test_saving_with_datasaver(learner_type, f, learner_kwargs): g = lambda x: {"y": f(x), "t": random.random()} # noqa: E731 arg_picker = operator.itemgetter("y") learner = DataSaver(learner_type(g, **learner_kwargs), arg_picker) - control = DataSaver(learner_type(g, **learner_kwargs), arg_picker) + control = learner.new() if learner_type in (Learner1D, AverageLearner1D): learner.learner._recompute_losses_factor = 1 @@ -742,7 +742,7 @@ def test_to_dataframe(learner_type, f, learner_kwargs): assert len(df) == learner.npoints # Add points from the DataFrame to a new empty learner - learner2 = learner_type(learner.function, **learner_kwargs) + learner2 = learner.new() learner2.load_dataframe(df, **kw) assert learner2.npoints == learner.npoints @@ -787,8 +787,7 @@ def test_to_dataframe(learner_type, f, learner_kwargs): assert len(df) == data_saver.npoints # Test loading from a DataFrame into a new DataSaver - learner2 = learner_type(learner.function, **learner_kwargs) - data_saver2 = DataSaver(learner2, operator.itemgetter("result")) + data_saver2 = data_saver.new() data_saver2.load_dataframe(df, **kw) assert data_saver2.extra_data.keys() == data_saver.extra_data.keys() assert all(