From 4c483aa6c4c03cd88917dcd1bd7c2c6ff0a7e035 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 14 May 2020 13:56:08 +0200 Subject: [PATCH 1/7] AverageLearner: implement min_npoints Closes https://github.com/python-adaptive/adaptive/issues/273 --- adaptive/learner/average_learner.py | 11 ++++++++--- adaptive/tests/test_average_learner.py | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/adaptive/learner/average_learner.py b/adaptive/learner/average_learner.py index e2c9f476a..8b7c545b8 100644 --- a/adaptive/learner/average_learner.py +++ b/adaptive/learner/average_learner.py @@ -19,6 +19,8 @@ class AverageLearner(BaseLearner): Desired absolute tolerance. rtol : float Desired relative tolerance. + min_npoints : int + Minimum number of points required to estimate the standard deviation. Attributes ---------- @@ -30,7 +32,9 @@ class AverageLearner(BaseLearner): Number of evaluated points. """ - def __init__(self, function, atol=None, rtol=None): + def __init__(self, function, atol=None, rtol=None, min_npoints=2): + if min_npoints < 2: + raise ValueError("`min_npoints` should be at least 2.") if atol is None and rtol is None: raise Exception("At least one of `atol` and `rtol` should be set.") if atol is None: @@ -44,6 +48,7 @@ def __init__(self, function, atol=None, rtol=None): self.atol = atol self.rtol = rtol self.npoints = 0 + self.min_npoints = min_npoints self.sum_f = 0 self.sum_f_sq = 0 @@ -92,7 +97,7 @@ def std(self): """The corrected sample standard deviation of the values in `data`.""" n = self.npoints - if n < 2: + if n < self.min_npoints: return np.inf numerator = self.sum_f_sq - n * self.mean ** 2 if numerator < 0: @@ -106,7 +111,7 @@ def loss(self, real=True, *, n=None): n = self.npoints if real else self.n_requested else: n = n - if n < 2: + if n < self.min_npoints: return np.inf standard_error = self.std / sqrt(n) return max( diff --git a/adaptive/tests/test_average_learner.py b/adaptive/tests/test_average_learner.py index 90d0c7b6c..2c731860f 100644 --- a/adaptive/tests/test_average_learner.py +++ b/adaptive/tests/test_average_learner.py @@ -4,6 +4,7 @@ import numpy as np from adaptive.learner import AverageLearner +from adaptive.runner import simple def test_only_returns_new_points(): @@ -46,3 +47,20 @@ def test_avg_std_and_npoints(): assert learner.npoints == len(learner.data) assert abs(learner.sum_f - values.sum()) < 1e-13 assert abs(learner.std - std) < 1e-13 + + +def test_min_npoints(): + def f(npoints_similar: int): + def _f(seed): + if seed < npoints_similar: + return 0.1 + 1e-8 * random.random() + return random.random() + + return _f + + for npoints_similar in range(1, 5): + learner = AverageLearner( + f(npoints_similar), atol=0.01, rtol=0.01, min_npoints=npoints_similar + 1 + ) + simple(learner, lambda l: l.loss() < 1) + assert learner.npoints > npoints_similar From 02c4047fc705fb3b79a16e5b9cb2fd98897babd1 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 14 May 2020 14:07:02 +0200 Subject: [PATCH 2/7] simplify test_min_npoints --- adaptive/tests/test_average_learner.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/adaptive/tests/test_average_learner.py b/adaptive/tests/test_average_learner.py index 2c731860f..1ee764892 100644 --- a/adaptive/tests/test_average_learner.py +++ b/adaptive/tests/test_average_learner.py @@ -50,17 +50,11 @@ def test_avg_std_and_npoints(): def test_min_npoints(): - def f(npoints_similar: int): - def _f(seed): - if seed < npoints_similar: - return 0.1 + 1e-8 * random.random() - return random.random() - - return _f - - for npoints_similar in range(1, 5): - learner = AverageLearner( - f(npoints_similar), atol=0.01, rtol=0.01, min_npoints=npoints_similar + 1 - ) - simple(learner, lambda l: l.loss() < 1) - assert learner.npoints > npoints_similar + def f(seed): + if seed < 2: # first two numbers are similar + return 0.1 + 1e-8 * random.random() + return random.random() + + learner = AverageLearner(f, atol=0.01, rtol=0.01, min_npoints=3) + simple(learner, lambda l: l.loss() < 1) + assert learner.npoints > 2 From b2a3bc904f8d53cf4ff17d6c18c8ac89983e64f7 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 14 May 2020 14:15:37 +0200 Subject: [PATCH 3/7] require that min_npoints is always at least 2 --- adaptive/learner/average_learner.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/adaptive/learner/average_learner.py b/adaptive/learner/average_learner.py index 8b7c545b8..184168d46 100644 --- a/adaptive/learner/average_learner.py +++ b/adaptive/learner/average_learner.py @@ -33,8 +33,6 @@ class AverageLearner(BaseLearner): """ def __init__(self, function, atol=None, rtol=None, min_npoints=2): - if min_npoints < 2: - raise ValueError("`min_npoints` should be at least 2.") if atol is None and rtol is None: raise Exception("At least one of `atol` and `rtol` should be set.") if atol is None: @@ -48,7 +46,8 @@ def __init__(self, function, atol=None, rtol=None, min_npoints=2): self.atol = atol self.rtol = rtol self.npoints = 0 - self.min_npoints = min_npoints + # Cannot estimate standard deviation with less than 2 points. + self.min_npoints = max(min_npoints, 2) self.sum_f = 0 self.sum_f_sq = 0 From 825da0d3d4224c73f30ba7e04b262604da6973d6 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 14 May 2020 14:17:04 +0200 Subject: [PATCH 4/7] rephrase doc-string Co-authored-by: Anton Akhmerov --- adaptive/learner/average_learner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adaptive/learner/average_learner.py b/adaptive/learner/average_learner.py index 184168d46..ca19f337d 100644 --- a/adaptive/learner/average_learner.py +++ b/adaptive/learner/average_learner.py @@ -20,7 +20,7 @@ class AverageLearner(BaseLearner): rtol : float Desired relative tolerance. min_npoints : int - Minimum number of points required to estimate the standard deviation. + Minimum number of points to sample. Attributes ---------- From 70d48c1585d87bcdb8a9d1f160ccb47d1255e40a Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 14 May 2020 14:25:21 +0200 Subject: [PATCH 5/7] fix spelling Co-authored-by: Anton Akhmerov --- adaptive/learner/average_learner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adaptive/learner/average_learner.py b/adaptive/learner/average_learner.py index ca19f337d..bd2423566 100644 --- a/adaptive/learner/average_learner.py +++ b/adaptive/learner/average_learner.py @@ -46,7 +46,7 @@ def __init__(self, function, atol=None, rtol=None, min_npoints=2): self.atol = atol self.rtol = rtol self.npoints = 0 - # Cannot estimate standard deviation with less than 2 points. + # Cannot estimate standard deviation with fewer than 2 points. self.min_npoints = max(min_npoints, 2) self.sum_f = 0 self.sum_f_sq = 0 From 9805b071dd966605cd3535397ab7c98edc1fb59c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 14 May 2020 14:40:24 +0200 Subject: [PATCH 6/7] set min_npoints in __getstate__ and __setstate__ --- adaptive/learner/average_learner.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/adaptive/learner/average_learner.py b/adaptive/learner/average_learner.py index bd2423566..4209b85d4 100644 --- a/adaptive/learner/average_learner.py +++ b/adaptive/learner/average_learner.py @@ -154,10 +154,11 @@ def __getstate__(self): self.function, self.atol, self.rtol, + self.min_npoints, self._get_data(), ) def __setstate__(self, state): - function, atol, rtol, data = state - self.__init__(function, atol, rtol) + function, atol, rtol, min_npoints, data = state + self.__init__(function, atol, rtol, min_npoints) self._set_data(data) From 52a72522dc953ce985a374088c1a10c4855e83dd Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 14 May 2020 14:53:03 +0200 Subject: [PATCH 7/7] simplify test_average_learner.py::test_min_npoints further --- adaptive/tests/test_average_learner.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/adaptive/tests/test_average_learner.py b/adaptive/tests/test_average_learner.py index 1ee764892..aa93241fa 100644 --- a/adaptive/tests/test_average_learner.py +++ b/adaptive/tests/test_average_learner.py @@ -50,11 +50,12 @@ def test_avg_std_and_npoints(): def test_min_npoints(): - def f(seed): - if seed < 2: # first two numbers are similar - return 0.1 + 1e-8 * random.random() - return random.random() - - learner = AverageLearner(f, atol=0.01, rtol=0.01, min_npoints=3) - simple(learner, lambda l: l.loss() < 1) - assert learner.npoints > 2 + def constant_function(seed): + return 0.1 + + for min_npoints in [1, 2, 3]: + learner = AverageLearner( + constant_function, atol=0.01, rtol=0.01, min_npoints=min_npoints + ) + simple(learner, lambda l: l.loss() < 1) + assert learner.npoints >= max(2, min_npoints)