From 5e3edf471361d91477db100794451650658dc81b Mon Sep 17 00:00:00 2001 From: Bas Nijholt <basnijholt@gmail.com> Date: Fri, 19 Oct 2018 14:19:42 +0200 Subject: [PATCH] documentation improvements --- AUTHORS.md | 2 +- README.rst | 30 ++++----- adaptive/learner/average_learner.py | 17 +++++- adaptive/learner/balancing_learner.py | 37 ++++++++---- adaptive/learner/base_learner.py | 17 ++++-- adaptive/learner/data_saver.py | 2 + adaptive/learner/integrator_learner.py | 1 + adaptive/learner/learner1D.py | 57 +++++++++++------- adaptive/learner/learner2D.py | 41 +++++++++++++ adaptive/learner/learnerND.py | 12 ++++ adaptive/learner/skopt_learner.py | 9 +-- adaptive/runner.py | 13 ++-- docs/source/_static/logo.png | Bin 0 -> 5093 bytes docs/source/conf.py | 1 + docs/source/{rest_of_readme.rst => docs.rst} | 40 ++++++++---- docs/source/index.rst | 2 +- .../adaptive.learner.base_learner.rst | 2 +- .../adaptive.learner.triangulation.rst | 7 +++ docs/source/reference/adaptive.rst | 2 + .../reference/adaptive.runner.extras.rst | 6 +- docs/source/tutorial/tutorial.custom_loss.rst | 2 +- docs/source/tutorial/tutorial.rst | 1 + 22 files changed, 216 insertions(+), 85 deletions(-) create mode 100644 docs/source/_static/logo.png rename docs/source/{rest_of_readme.rst => docs.rst} (85%) create mode 100644 docs/source/reference/adaptive.learner.triangulation.rst diff --git a/AUTHORS.md b/AUTHORS.md index aa414900..3da642df 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,4 +1,4 @@ -# Adaptive Authors +## Authors Below is a list of the contributors to Adaptive: + [Anton Akhmerov](<https://antonakhmerov.org>) diff --git a/README.rst b/README.rst index f92ed5d7..c32d133a 100644 --- a/README.rst +++ b/README.rst @@ -1,12 +1,10 @@ .. summary-start -.. _logo-adaptive: +|logo| adaptive +=============== -|image0| adaptive -================= - -|PyPI| |Conda| |Downloads| |pipeline status| |DOI| |Binder| |Join the -chat at https://gitter.im/python-adaptive/adaptive| |Documentation Status| +|PyPI| |Conda| |Downloads| |Pipeline status| |DOI| |Binder| |Gitter| +|Documentation| |GitHub| **Tools for adaptive parallel sampling of mathematical functions.** @@ -126,7 +124,9 @@ We would like to give credits to the following people: Mathematical Software, 37 (3), art. no. 26, 2010. - Pauli Virtanen for his ``AdaptiveTriSampling`` script (no longer available online since SciPy Central went down) which served as - inspiration for the ``~adaptive.Learner2D``. + inspiration for the `~adaptive.Learner2D`. + +.. credits-end For general discussion, we have a `Gitter chat channel <https://gitter.im/python-adaptive/adaptive>`_. If you find any @@ -136,21 +136,23 @@ or submit a `merge request <https://gitlab.kwant-project.org/qt/adaptive/merge_requests>`_. .. references-start -.. |image0| image:: https://gitlab.kwant-project.org/qt/adaptive/uploads/d20444093920a4a0499e165b5061d952/logo.png +.. |logo| image:: https://adaptive.readthedocs.io/en/latest/_static/logo.png .. |PyPI| image:: https://img.shields.io/pypi/v/adaptive.svg :target: https://pypi.python.org/pypi/adaptive -.. |Conda| image:: https://anaconda.org/conda-forge/adaptive/badges/installer/conda.svg +.. |Conda| image:: https://img.shields.io/badge/install%20with-conda-green.svg :target: https://anaconda.org/conda-forge/adaptive -.. |Downloads| image:: https://anaconda.org/conda-forge/adaptive/badges/downloads.svg +.. |Downloads| image:: https://img.shields.io/conda/dn/conda-forge/adaptive.svg :target: https://anaconda.org/conda-forge/adaptive -.. |pipeline status| image:: https://gitlab.kwant-project.org/qt/adaptive/badges/master/pipeline.svg +.. |Pipeline status| image:: https://gitlab.kwant-project.org/qt/adaptive/badges/master/pipeline.svg :target: https://gitlab.kwant-project.org/qt/adaptive/pipelines .. |DOI| image:: https://zenodo.org/badge/113714660.svg - :target: https://zenodo.org/badge/latestdoi/113714660 + :target: https://doi.org/10.5281/zenodo.1446400 .. |Binder| image:: https://mybinder.org/badge.svg :target: https://mybinder.org/v2/gh/python-adaptive/adaptive/master?filepath=learner.ipynb -.. |Join the chat at https://gitter.im/python-adaptive/adaptive| image:: https://img.shields.io/gitter/room/nwjs/nw.js.svg +.. |Gitter| image:: https://img.shields.io/gitter/room/nwjs/nw.js.svg :target: https://gitter.im/python-adaptive/adaptive -.. |Documentation Status| image:: https://readthedocs.org/projects/adaptive/badge/?version=latest +.. |Documentation| image:: https://readthedocs.org/projects/adaptive/badge/?version=latest :target: https://adaptive.readthedocs.io/en/latest/?badge=latest +.. |GitHub| image:: https://img.shields.io/github/stars/python-adaptive/adaptive.svg?style=social + :target: https://github.com/python-adaptive/adaptive/stargazers .. references-end diff --git a/adaptive/learner/average_learner.py b/adaptive/learner/average_learner.py index 312101bf..704a9bb8 100644 --- a/adaptive/learner/average_learner.py +++ b/adaptive/learner/average_learner.py @@ -27,6 +27,8 @@ class AverageLearner(BaseLearner): Sampled points and values. pending_points : set Points that still have to be evaluated. + npoints : int + Number of evaluated points. """ def __init__(self, function, atol=None, rtol=None): @@ -48,7 +50,7 @@ class AverageLearner(BaseLearner): @property def n_requested(self): - return len(self.data) + len(self.pending_points) + return self.npoints + len(self.pending_points) def ask(self, n, tell_pending=True): points = list(range(self.n_requested, self.n_requested + n)) @@ -59,7 +61,7 @@ class AverageLearner(BaseLearner): - set(self.data) - set(self.pending_points))[:n] - loss_improvements = [self.loss_improvement(n) / n] * n + loss_improvements = [self._loss_improvement(n) / n] * n if tell_pending: for p in points: self.tell_pending(p) @@ -81,10 +83,13 @@ class AverageLearner(BaseLearner): @property def mean(self): + """The average of all values in `data`.""" return self.sum_f / self.npoints @property def std(self): + """The corrected sample standard deviation of the values + in `data`.""" n = self.npoints if n < 2: return np.inf @@ -106,7 +111,7 @@ class AverageLearner(BaseLearner): return max(standard_error / self.atol, standard_error / abs(self.mean) / self.rtol) - def loss_improvement(self, n): + def _loss_improvement(self, n): loss = self.loss() if np.isfinite(loss): return loss - self.loss(n=self.npoints + n) @@ -118,6 +123,12 @@ class AverageLearner(BaseLearner): self.pending_points = set() def plot(self): + """Returns a histogram of the evaluated data. + + Returns + ------- + holoviews.element.Histogram + A histogram of the evaluated data.""" hv = ensure_holoviews() vals = [v for v in self.data.values() if v is not None] if not vals: diff --git a/adaptive/learner/balancing_learner.py b/adaptive/learner/balancing_learner.py index 764758ba..f8ec0b08 100644 --- a/adaptive/learner/balancing_learner.py +++ b/adaptive/learner/balancing_learner.py @@ -22,7 +22,7 @@ class BalancingLearner(BaseLearner): Parameters ---------- - learners : sequence of `BaseLearner` + learners : sequence of `~adaptive.BaseLearner`\s The learners from which to choose. These must all have the same type. cdims : sequence of dicts, or (keys, iterable of values), optional Constant dimensions; the parameters that label the learners. Used @@ -42,6 +42,13 @@ class BalancingLearner(BaseLearner): >>> cdims = (['A', 'B'], [(True, 0), (True, 1), ... (False, 0), (False, 1)]) + Attributes + ---------- + learners : list + The sequence of `~adaptive.BaseLearner`\s. + function : callable + A function that calls the functions of the underlying learners. + Its signature is ``function(learner_index, point)``. strategy : 'loss_improvements' (default), 'loss', or 'npoints' The points that the `BalancingLearner` choses can be either based on: the best 'loss_improvements', the smallest total 'loss' of the @@ -51,13 +58,13 @@ class BalancingLearner(BaseLearner): Notes ----- - This learner compares the 'loss' calculated from the "child" learners. + This learner compares the `loss` calculated from the "child" learners. This requires that the 'loss' from different learners *can be meaningfully compared*. For the moment we enforce this restriction by requiring that all learners are the same type but (depending on the internals of the learner) it may be that the loss cannot be compared *even between learners - of the same type*. In this case the `BalancingLearner` will behave in an - undefined way. + of the same type*. In this case the `~adaptive.BalancingLearner` will + behave in an undefined way. Change the `strategy` in that case. """ def __init__(self, learners, *, cdims=None, strategy='loss_improvements'): @@ -81,6 +88,12 @@ class BalancingLearner(BaseLearner): @property def strategy(self): + """Can be either 'loss_improvements' (default), 'loss', or 'npoints' + The points that the `BalancingLearner` choses can be either based on: + the best 'loss_improvements', the smallest total 'loss' of the + child learners, or the number of points per learner, using 'npoints'. + One can dynamically change the strategy while the simulation is + running by changing the ``learner.strategy`` attribute.""" return self._strategy @strategy.setter @@ -122,7 +135,7 @@ class BalancingLearner(BaseLearner): points = [] loss_improvements = [] for _ in range(n): - losses = self.losses(real=False) + losses = self._losses(real=False) max_ind = np.argmax(losses) xs, ls = self.learners[max_ind].ask(1) points.append((max_ind, xs[0])) @@ -165,7 +178,7 @@ class BalancingLearner(BaseLearner): self._loss.pop(index, None) self.learners[index].tell_pending(x) - def losses(self, real=True): + def _losses(self, real=True): losses = [] loss_dict = self._loss if real else self._pending_loss @@ -178,7 +191,7 @@ class BalancingLearner(BaseLearner): @cache_latest def loss(self, real=True): - losses = self.losses(real) + losses = self._losses(real) return max(losses) def plot(self, cdims=None, plotter=None, dynamic=True): @@ -215,8 +228,8 @@ class BalancingLearner(BaseLearner): Returns ------- dm : `holoviews.core.DynamicMap` (default) or `holoviews.core.HoloMap` - A `DynamicMap` (dynamic=True) or `HoloMap` (dynamic=False) with - sliders that are defined by `cdims`. + A `DynamicMap` ``(dynamic=True)`` or `HoloMap` + ``(dynamic=False)`` with sliders that are defined by `cdims`. """ hv = ensure_holoviews() cdims = cdims or self._cdims_default @@ -295,7 +308,7 @@ class BalancingLearner(BaseLearner): Notes ----- The order of the child learners inside `learner.learners` is the same - as `adaptive.utils.named_product(**combos)`. + as ``adaptive.utils.named_product(**combos)``. """ learners = [] arguments = named_product(**combos) @@ -313,7 +326,7 @@ class BalancingLearner(BaseLearner): folder : str Directory in which the learners's data will be saved. compress : bool, default True - Compress the data upon saving using 'gzip'. When saving + Compress the data upon saving using `gzip`. When saving using compression, one must load it with compression too. Notes @@ -364,7 +377,7 @@ class BalancingLearner(BaseLearner): Example ------- - See the example in the 'BalancingLearner.save' doc-string. + See the example in the `BalancingLearner.save` doc-string. """ for l in self.learners: l.load(os.path.join(folder, l.fname), compress=compress) diff --git a/adaptive/learner/base_learner.py b/adaptive/learner/base_learner.py index b33cc018..eed2c5a8 100644 --- a/adaptive/learner/base_learner.py +++ b/adaptive/learner/base_learner.py @@ -20,6 +20,9 @@ class BaseLearner(metaclass=abc.ABCMeta): npoints : int, optional The number of evaluated points that have been added to the learner. Subclasses do not *have* to implement this attribute. + pending_points : set, optional + Points that have been requested but have not been evaluated yet. + Subclasses do not *have* to implement this attribute. Notes ----- @@ -118,10 +121,11 @@ class BaseLearner(metaclass=abc.ABCMeta): Notes ----- - There are __two ways__ of naming the files: - 1. Using the 'fname' argument in 'learner.save(fname='example.p') - 2. Setting the 'fname' attribute, like - 'learner.fname = "data/example.p"' and then 'learner.save()'. + There are **two ways** of naming the files: + + 1. Using the ``fname`` argument in ``learner.save(fname='example.p')`` + 2. Setting the ``fname`` attribute, like + ``learner.fname = "data/example.p"`` and then ``learner.save()``. """ fname = fname or self.fname data = self._get_data() @@ -142,7 +146,7 @@ class BaseLearner(metaclass=abc.ABCMeta): Notes ----- - See the notes in the 'BaseLearner.save' doc-string. + See the notes in the `save` doc-string. """ fname = fname or self.fname with suppress(FileNotFoundError, EOFError): @@ -157,6 +161,9 @@ class BaseLearner(metaclass=abc.ABCMeta): @property def fname(self): + """Filename for the learner when it is saved (or loaded) using + `~adaptive.BaseLearner.save` (or `~adaptive.BaseLearner.load` ). + """ # This is a property because then it will be availible in the DataSaver try: return self._fname diff --git a/adaptive/learner/data_saver.py b/adaptive/learner/data_saver.py index 832c1a9e..8e035994 100644 --- a/adaptive/learner/data_saver.py +++ b/adaptive/learner/data_saver.py @@ -35,11 +35,13 @@ class DataSaver: def __getattr__(self, attr): return getattr(self.learner, attr) + @copy_docstring_from(BaseLearner.tell) def tell(self, x, result): y = self.arg_picker(result) self.extra_data[x] = result self.learner.tell(x, y) + @copy_docstring_from(BaseLearner.tell_pending) def tell_pending(self, x): self.learner.tell_pending(x) diff --git a/adaptive/learner/integrator_learner.py b/adaptive/learner/integrator_learner.py index 931d4531..0290a387 100644 --- a/adaptive/learner/integrator_learner.py +++ b/adaptive/learner/integrator_learner.py @@ -487,6 +487,7 @@ class IntegratorLearner(BaseLearner): @property def npoints(self): + """Number of evaluated points.""" return len(self.done_points) @property diff --git a/adaptive/learner/learner1D.py b/adaptive/learner/learner1D.py index 343a9def..8868800d 100644 --- a/adaptive/learner/learner1D.py +++ b/adaptive/learner/learner1D.py @@ -34,7 +34,7 @@ def uniform_loss(interval, scale, function_values): def default_loss(interval, scale, function_values): - """Calculate loss on a single interval + """Calculate loss on a single interval. Currently returns the rescaled length of the interval. If one of the y-values is missing, returns 0 (so the intervals with missing data are @@ -148,6 +148,12 @@ class Learner1D(BaseLearner): @property def vdim(self): + """Length of the output of ``learner.function``. + If the output is unsized (when it's a scalar) + then `vdim = 1`. + + As long as no data is known `vdim = 1`. + """ if self._vdim is None: if self.data: y = next(iter(self.data.values())) @@ -162,6 +168,7 @@ class Learner1D(BaseLearner): @property def npoints(self): + """Number of evaluated points.""" return len(self.data) @cache_latest @@ -169,7 +176,7 @@ class Learner1D(BaseLearner): losses = self.losses if real else self.losses_combined return max(losses.values()) if len(losses) > 0 else float('inf') - def update_interpolated_loss_in_interval(self, x_left, x_right): + def _update_interpolated_loss_in_interval(self, x_left, x_right): if x_left is not None and x_right is not None: dx = x_right - x_left if dx < self._dx_eps: @@ -187,13 +194,13 @@ class Learner1D(BaseLearner): self.losses_combined[a, b] = (b - a) * loss / dx a = b - def update_losses(self, x, real=True): + def _update_losses(self, x, real=True): # When we add a new point x, we should update the losses # (x_left, x_right) are the "real" neighbors of 'x'. - x_left, x_right = self.find_neighbors(x, self.neighbors) + x_left, x_right = self._find_neighbors(x, self.neighbors) # (a, b) are the neighbors of the combined interpolated # and "real" intervals. - a, b = self.find_neighbors(x, self.neighbors_combined) + a, b = self._find_neighbors(x, self.neighbors_combined) # (a, b) is splitted into (a, x) and (x, b) so if (a, b) exists self.losses_combined.pop((a, b), None) # we get rid of (a, b). @@ -202,8 +209,8 @@ class Learner1D(BaseLearner): # We need to update all interpolated losses in the interval # (x_left, x) and (x, x_right). Since the addition of the point # 'x' could change their loss. - self.update_interpolated_loss_in_interval(x_left, x) - self.update_interpolated_loss_in_interval(x, x_right) + self._update_interpolated_loss_in_interval(x_left, x) + self._update_interpolated_loss_in_interval(x, x_right) # Since 'x' is in between (x_left, x_right), # we get rid of the interval. @@ -230,7 +237,7 @@ class Learner1D(BaseLearner): self.losses_combined[x, b] = float('inf') @staticmethod - def find_neighbors(x, neighbors): + def _find_neighbors(x, neighbors): if x in neighbors: return neighbors[x] pos = neighbors.bisect_left(x) @@ -239,14 +246,14 @@ class Learner1D(BaseLearner): x_right = keys[pos] if pos != len(neighbors) else None return x_left, x_right - def update_neighbors(self, x, neighbors): + def _update_neighbors(self, x, neighbors): if x not in neighbors: # The point is new - x_left, x_right = self.find_neighbors(x, neighbors) + x_left, x_right = self._find_neighbors(x, neighbors) neighbors[x] = [x_left, x_right] neighbors.get(x_left, [None, None])[1] = x neighbors.get(x_right, [None, None])[0] = x - def update_scale(self, x, y): + def _update_scale(self, x, y): """Update the scale with which the x and y-values are scaled. For a learner where the function returns a single scalar the scale @@ -291,16 +298,16 @@ class Learner1D(BaseLearner): if not self.bounds[0] <= x <= self.bounds[1]: return - self.update_neighbors(x, self.neighbors_combined) - self.update_neighbors(x, self.neighbors) - self.update_scale(x, y) - self.update_losses(x, real=True) + self._update_neighbors(x, self.neighbors_combined) + self._update_neighbors(x, self.neighbors) + self._update_scale(x, y) + self._update_losses(x, real=True) # If the scale has increased enough, recompute all losses. if self._scale[1] > 2 * self._oldscale[1]: for interval in self.losses: - self.update_interpolated_loss_in_interval(*interval) + self._update_interpolated_loss_in_interval(*interval) self._oldscale = deepcopy(self._scale) @@ -309,8 +316,8 @@ class Learner1D(BaseLearner): # The point is already evaluated before return self.pending_points.add(x) - self.update_neighbors(x, self.neighbors_combined) - self.update_losses(x, real=False) + self._update_neighbors(x, self.neighbors_combined) + self._update_losses(x, real=False) def tell_many(self, xs, ys, *, force=False): if not force and not (len(xs) > 0.5 * len(self.data) and len(xs) > 2): @@ -379,10 +386,10 @@ class Learner1D(BaseLearner): if ival in self.losses: # If this interval does not exist it should already # have an inf loss. - self.update_interpolated_loss_in_interval(*ival) + self._update_interpolated_loss_in_interval(*ival) def ask(self, n, tell_pending=True): - """Return n points that are expected to maximally reduce the loss.""" + """Return 'n' points that are expected to maximally reduce the loss.""" points, loss_improvements = self._ask_points_without_adding(n) if tell_pending: @@ -392,7 +399,7 @@ class Learner1D(BaseLearner): return points, loss_improvements def _ask_points_without_adding(self, n): - """Return n points that are expected to maximally reduce the loss. + """Return 'n' points that are expected to maximally reduce the loss. Without altering the state of the learner""" # Find out how to divide the n points over the intervals # by finding positive integer n_i that minimize max(L_i / n_i) subject @@ -466,6 +473,14 @@ class Learner1D(BaseLearner): return points, loss_improvements def plot(self): + """Returns a plot of the evaluated data. + + Returns + ------- + plot : `holoviews.element.Scatter` (if vdim=1)\ + else `holoviews.element.Path` + Plot of the evaluated data. + """ hv = ensure_holoviews() if not self.data: p = hv.Scatter([]) * hv.Path([]) diff --git a/adaptive/learner/learner2D.py b/adaptive/learner/learner2D.py index bbc3f644..11ac92a4 100644 --- a/adaptive/learner/learner2D.py +++ b/adaptive/learner/learner2D.py @@ -15,6 +15,19 @@ from ..utils import cache_latest # Learner2D and helper functions. def deviations(ip): + """Returns the deviation of the linear estimate. + + Is useful when defining custom loss functions. + + Parameters + ---------- + ip : `scipy.interpolate.LinearNDInterpolator` instance + + Returns + ------- + numpy array + The deviation per triangle. + """ values = ip.values / (ip.values.ptp(axis=0).max() or 1) gradients = interpolate.interpnd.estimate_gradients_2d_global( ip.tri, values, tol=1e-6) @@ -37,6 +50,20 @@ def deviations(ip): def areas(ip): + """Returns the area per triangle of the triangulation inside + a `LinearNDInterpolator` instance. + + Is useful when defining custom loss functions. + + Parameters + ---------- + ip : `scipy.interpolate.LinearNDInterpolator` instance + + Returns + ------- + numpy array + The area per triangle in ``ip.tri``. + """ p = ip.tri.points[ip.tri.vertices] q = p[:, :-1, :] - p[:, -1, None, :] areas = abs(q[:, 0, 0] * q[:, 1, 1] - q[:, 0, 1] * q[:, 1, 0]) / 2 @@ -289,10 +316,17 @@ class Learner2D(BaseLearner): @property def npoints(self): + """Number of evaluated points.""" return len(self.data) @property def vdim(self): + """Length of the output of ``learner.function``. + If the output is unsized (when it's a scalar) + then `vdim = 1`. + + As long as no data is known `vdim = 1`. + """ if self._vdim is None and self.data: try: value = next(iter(self.data.values())) @@ -337,11 +371,15 @@ class Learner2D(BaseLearner): return points_combined, values_combined def data_combined(self): + """Like `data`, however this includes the points in + `pending_points` for which the values are interpolated.""" # Interpolate the unfinished points points, values = self._data_combined() return {tuple(k): v for k, v in zip(points, values)} def ip(self): + """A `scipy.interpolate.LinearNDInterpolator` instance + containing the learner's data.""" if self._ip is None: points, values = self._data_in_bounds() points = self._scale(points) @@ -349,6 +387,9 @@ class Learner2D(BaseLearner): return self._ip def ip_combined(self): + """A `scipy.interpolate.LinearNDInterpolator` instance + containing the learner's data *and* interpolated data of + the `pending_points`.""" if self._ip_combined is None: points, values = self._data_combined() points = self._scale(points) diff --git a/adaptive/learner/learnerND.py b/adaptive/learner/learnerND.py index 274ae16f..c8081878 100644 --- a/adaptive/learner/learnerND.py +++ b/adaptive/learner/learnerND.py @@ -188,10 +188,17 @@ class LearnerND(BaseLearner): @property def npoints(self): + """Number of evaluated points.""" return len(self.data) @property def vdim(self): + """Length of the output of ``learner.function``. + If the output is unsized (when it's a scalar) + then `vdim = 1`. + + As long as no data is known `vdim = 1`. + """ if self._vdim is None and self.data: try: value = next(iter(self.data.values())) @@ -205,6 +212,8 @@ class LearnerND(BaseLearner): return all(p in self.data for p in self._bounds_points) def ip(self): + """A `scipy.interpolate.LinearNDInterpolator` instance + containing the learner's data.""" # XXX: take our own triangulation into account when generating the ip return interpolate.LinearNDInterpolator(self.points, self.values) @@ -227,10 +236,12 @@ class LearnerND(BaseLearner): @property def values(self): + """Get the values from `data` as a numpy array.""" return np.array(list(self.data.values()), dtype=float) @property def points(self): + """Get the points from `data` as a numpy array.""" return np.array(list(self.data.keys()), dtype=float) def tell(self, point, value): @@ -262,6 +273,7 @@ class LearnerND(BaseLearner): return simplex in self.tri.simplices def inside_bounds(self, point): + """Check whether a point is inside the bounds.""" return all(mn <= p <= mx for p, (mn, mx) in zip(point, self.bounds)) def tell_pending(self, point, *, simplex=None): diff --git a/adaptive/learner/skopt_learner.py b/adaptive/learner/skopt_learner.py index 2b778ba5..9aac8d4c 100644 --- a/adaptive/learner/skopt_learner.py +++ b/adaptive/learner/skopt_learner.py @@ -8,18 +8,18 @@ from ..utils import cache_latest class SKOptLearner(Optimizer, BaseLearner): - """Learn a function minimum using 'skopt.Optimizer'. + """Learn a function minimum using ``skopt.Optimizer``. - This is an 'Optimizer' from 'scikit-optimize', + This is an ``Optimizer`` from ``scikit-optimize``, with the necessary methods added to make it conform - to the 'adaptive' learner interface. + to the ``adaptive`` learner interface. Parameters ---------- function : callable The function to learn. **kwargs : - Arguments to pass to 'skopt.Optimizer'. + Arguments to pass to ``skopt.Optimizer``. """ def __init__(self, function, **kwargs): @@ -63,6 +63,7 @@ class SKOptLearner(Optimizer, BaseLearner): @property def npoints(self): + """Number of evaluated points.""" return len(self.Xi) def plot(self, nsamples=200): diff --git a/adaptive/runner.py b/adaptive/runner.py index ce2b61ad..b6d0de49 100644 --- a/adaptive/runner.py +++ b/adaptive/runner.py @@ -54,7 +54,7 @@ else: class BaseRunner: - """Base class for runners that use concurrent.futures.Executors. + """Base class for runners that use `concurrent.futures.Executors`. Parameters ---------- @@ -346,7 +346,7 @@ class BlockingRunner(BaseRunner): class AsyncRunner(BaseRunner): - """Run a learner asynchronously in an executor using asyncio. + """Run a learner asynchronously in an executor using `asyncio`. Parameters ---------- @@ -548,7 +548,7 @@ class AsyncRunner(BaseRunner): Parameters ---------- save_kwargs : dict - Key-word arguments for 'learner.save(**save_kwargs)'. + Key-word arguments for ``learner.save(**save_kwargs)``. interval : int Number of seconds between saving the learner. @@ -586,7 +586,7 @@ def simple(learner, goal): Parameters ---------- - learner : adaptive.BaseLearner + learner : ~`adaptive.BaseLearner` instance goal : callable The end condition for the calculation. This function must take the learner as its sole argument, and return True if we should stop. @@ -605,9 +605,10 @@ def replay_log(learner, log): Parameters ---------- - learner : learner.BaseLearner + learner : `~adaptive.BaseLearner` instance + New learner where the log will be applied. log : list - contains tuples: '(method_name, *args)'. + contains tuples: ``(method_name, *args)``. """ for method, *args in log: getattr(learner, method)(*args) diff --git a/docs/source/_static/logo.png b/docs/source/_static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ff7a0f4e8abb071626512f5eb9e9d9df245fba6f GIT binary patch literal 5093 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 H&OGd2KJ>0A literal 0 HcmV?d00001 diff --git a/docs/source/conf.py b/docs/source/conf.py index 5a70aa0c..707b987b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -43,6 +43,7 @@ release = adaptive.__version__ extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', + 'sphinx.ext.autosectionlabel', 'sphinx.ext.intersphinx', 'sphinx.ext.mathjax', 'sphinx.ext.viewcode', diff --git a/docs/source/rest_of_readme.rst b/docs/source/docs.rst similarity index 85% rename from docs/source/rest_of_readme.rst rename to docs/source/docs.rst index 2fcab204..299cfc46 100644 --- a/docs/source/rest_of_readme.rst +++ b/docs/source/docs.rst @@ -19,9 +19,13 @@ The following learners are implemented: - `~adaptive.AverageLearner`, For stochastic functions where you want to average the result over many evaluations, - `~adaptive.IntegratorLearner`, for - when you want to intergrate a 1D function ``f: ℠→ â„``, + when you want to intergrate a 1D function ``f: ℠→ â„``. + +Meta-learners (to be used with other learners): + - `~adaptive.BalancingLearner`, for when you want to run several learners at once, - selecting the “best†one each time you get more points. + selecting the “best†one each time you get more points, +- `~adaptive.DataSaver`, for when your function doesn't just return a scalar or a vector. In addition to the learners, ``adaptive`` also provides primitives for running the sampling across several cores and even several machines, @@ -47,8 +51,6 @@ on the *Play* :fa:`play` button or move the sliders. adaptive.notebook_extension() %output holomap='scrubber' - - `adaptive.Learner1D` ~~~~~~~~~~~~~~~~~~~~ @@ -82,8 +84,6 @@ on the *Play* :fa:`play` button or move the sliders. (get_hm(uniform_loss).relabel('homogeneous samping') + get_hm(default_loss).relabel('with adaptive')) - - `adaptive.Learner2D` ~~~~~~~~~~~~~~~~~~~~ @@ -111,8 +111,6 @@ on the *Play* :fa:`play` button or move the sliders. plots = {n: plot(learner, n) for n in range(4, 1010, 20)} hv.HoloMap(plots, kdims=['npoints']).collate() - - `adaptive.AverageLearner` ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -134,15 +132,31 @@ on the *Play* :fa:`play` button or move the sliders. plots = {n: plot(learner, n) for n in range(10, 10000, 200)} hv.HoloMap(plots, kdims=['npoints']) +`adaptive.LearnerND` +~~~~~~~~~~~~~~~~~~~~ -see more in the :ref:`Tutorial Adaptive`. +.. jupyter-execute:: + :hide-code: + def sphere(xyz): + import numpy as np + x, y, z = xyz + a = 0.4 + return np.exp(-(x**2 + y**2 + z**2 - 0.75**2)**2/a**4) -.. include:: ../../README.rst - :start-after: not-in-documentation-end + learner = adaptive.LearnerND(sphere, bounds=[(-1, 1), (-1, 1), (-1, 1)]) + adaptive.runner.simple(learner, lambda l: l.npoints == 3000) + + learner.plot_3D() +see more in the :ref:`Tutorial Adaptive`. -Authors -------- +.. include:: ../../README.rst + :start-after: not-in-documentation-end + :end-before: credits-end .. mdinclude:: ../../AUTHORS.md + +.. include:: ../../README.rst + :start-after: credits-end + :end-before: references-start diff --git a/docs/source/index.rst b/docs/source/index.rst index 42f0d393..c95748e1 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -16,6 +16,6 @@ :maxdepth: 2 :hidden: - rest_of_readme + docs tutorial/tutorial reference/adaptive diff --git a/docs/source/reference/adaptive.learner.base_learner.rst b/docs/source/reference/adaptive.learner.base_learner.rst index 28ff6160..7a908ab5 100644 --- a/docs/source/reference/adaptive.learner.base_learner.rst +++ b/docs/source/reference/adaptive.learner.base_learner.rst @@ -1,4 +1,4 @@ -adaptive.learner.BaseLearner +adaptive.BaseLearner ============================ .. autoclass:: adaptive.learner.BaseLearner diff --git a/docs/source/reference/adaptive.learner.triangulation.rst b/docs/source/reference/adaptive.learner.triangulation.rst new file mode 100644 index 00000000..8e4e4dfc --- /dev/null +++ b/docs/source/reference/adaptive.learner.triangulation.rst @@ -0,0 +1,7 @@ +adaptive.learner.triangulation module +===================================== + +.. automodule:: adaptive.learner.triangulation + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference/adaptive.rst b/docs/source/reference/adaptive.rst index ab5049c9..54eeb9aa 100644 --- a/docs/source/reference/adaptive.rst +++ b/docs/source/reference/adaptive.rst @@ -7,6 +7,7 @@ Learners .. toctree:: adaptive.learner.average_learner + adaptive.learner.base_learner adaptive.learner.balancing_learner adaptive.learner.data_saver adaptive.learner.integrator_learner @@ -22,6 +23,7 @@ Runners adaptive.runner.Runner adaptive.runner.AsyncRunner adaptive.runner.BlockingRunner + adaptive.runner.BaseRunner adaptive.runner.extras Other diff --git a/docs/source/reference/adaptive.runner.extras.rst b/docs/source/reference/adaptive.runner.extras.rst index 00b809c5..90786f4e 100644 --- a/docs/source/reference/adaptive.runner.extras.rst +++ b/docs/source/reference/adaptive.runner.extras.rst @@ -1,5 +1,5 @@ -adaptive.runner.simple -====================== +Runner extras +============= Simple executor --------------- @@ -9,7 +9,7 @@ Simple executor Sequential excecutor -------------------- -.. autofunction:: adaptive.runner.SequentialExecutor +.. autoclass:: adaptive.runner.SequentialExecutor Replay log diff --git a/docs/source/tutorial/tutorial.custom_loss.rst b/docs/source/tutorial/tutorial.custom_loss.rst index f19151f4..b1cca295 100644 --- a/docs/source/tutorial/tutorial.custom_loss.rst +++ b/docs/source/tutorial/tutorial.custom_loss.rst @@ -8,7 +8,7 @@ Custom adaptive logic for 1D and 2D .. seealso:: The complete source code of this tutorial can be found in - :jupyter-download:notebook:`tutorial.custom-loss-function` + :jupyter-download:notebook:`tutorial.custom-loss` .. jupyter-execute:: :hide-code: diff --git a/docs/source/tutorial/tutorial.rst b/docs/source/tutorial/tutorial.rst index 2afd0011..e1476082 100644 --- a/docs/source/tutorial/tutorial.rst +++ b/docs/source/tutorial/tutorial.rst @@ -22,6 +22,7 @@ on the following packages - ``bokeh`` - ``ipywidgets`` +We recommend to start with the :ref:`Tutorial `~adaptive.Learner1D``. .. note:: Because this documentation consists of static html, the ``live_plot`` -- GitLab