Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 45 additions & 10 deletions adaptive/learner/learnerND.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,8 @@ def __init__(self, func, bounds, loss_per_simplex=None):
# been returned has not been deleted. This checking is done by
# _pop_highest_existing_simplex
self._simplex_queue = SortedKeyList(key=_simplex_evaluation_priority)
self._next_bound_idx = 0
self._bound_match_tol = 1e-10

def new(self) -> LearnerND:
"""Create a new learner with the same function and bounds."""
Expand Down Expand Up @@ -488,6 +490,7 @@ def load_dataframe( # type: ignore[override]
self.function = partial_function_from_dataframe(
self.function, df, function_prefix
)
self._next_bound_idx = 0

@property
def bounds_are_done(self):
Expand Down Expand Up @@ -555,6 +558,29 @@ def _simplex_exists(self, simplex):
simplex = tuple(sorted(simplex))
return simplex in self.tri.simplices

def _is_known_point(self, point):
point = tuple(map(float, point))
if point in self.data or point in self.pending_points:
return True

tolerances = [
max(self._bound_match_tol, self._bound_match_tol * (hi - lo))
for lo, hi in self._bbox
]

def _close(other):
return all(
abs(a - b) <= tol for (a, b, tol) in zip(point, other, tolerances)
)

for existing in self.data.keys():
if _close(existing):
return True
for existing in self.pending_points:
if _close(existing):
return True
return False

def inside_bounds(self, point):
"""Check whether a point is inside the bounds."""
if self._interior is not None:
Expand Down Expand Up @@ -602,7 +628,12 @@ def _try_adding_pending_point_to_simplex(self, point, simplex):
self._subtriangulations[simplex] = Triangulation(vertices)

self._pending_to_simplex[point] = simplex
return self._subtriangulations[simplex].add_point(point)
try:
return self._subtriangulations[simplex].add_point(point)
except ValueError as exc:
if str(exc) == "Point already in triangulation.":
self._pending_to_simplex.pop(point, None)
raise

def _update_subsimplex_losses(self, simplex, new_subsimplices):
loss = self._losses[simplex]
Expand All @@ -627,13 +658,17 @@ def ask(self, n, tell_pending=True):

def _ask_bound_point(self):
# get the next bound point that is still available
new_point = next(
p
for p in self._bounds_points
if p not in self.data and p not in self.pending_points
)
self.tell_pending(new_point)
return new_point, np.inf
while self._next_bound_idx < len(self._bounds_points):
new_point = self._bounds_points[self._next_bound_idx]
self._next_bound_idx += 1

if self._is_known_point(new_point):
continue

self.tell_pending(new_point)
return new_point, np.inf

raise StopIteration

def _ask_point_without_known_simplices(self):
assert not self._bounds_available
Expand Down Expand Up @@ -699,8 +734,8 @@ def _ask_best_point(self):
@property
def _bounds_available(self):
return any(
(p not in self.pending_points and p not in self.data)
for p in self._bounds_points
not self._is_known_point(p)
for p in self._bounds_points[self._next_bound_idx :]
)

def _ask(self):
Expand Down
76 changes: 76 additions & 0 deletions adaptive/tests/unit/test_learnernd_integration.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import math

import numpy as np
import pytest
from scipy.spatial import ConvexHull

from adaptive.learner import LearnerND
from adaptive.learner.learner1D import with_pandas
from adaptive.learner.learnerND import curvature_loss_function
from adaptive.runner import BlockingRunner
from adaptive.runner import simple as SimpleRunner
Expand Down Expand Up @@ -53,3 +56,76 @@ def test_learnerND_log_works():
learner.ask(2)
# At this point, there should! be one simplex in the triangulation,
# furthermore the last two points that were asked should be in this simplex


@pytest.mark.skipif(not with_pandas, reason="pandas is not installed")
def test_learnerND_resume_after_loading_dataframe_convex_hull(monkeypatch):
from types import MethodType

import pandas

hull_points = [
(4.375872112626925, 8.917730007820797),
(4.236547993389047, 6.458941130666561),
(6.027633760716439, 5.448831829968968),
(9.636627605010293, 3.8344151882577773),
]

data_points = [
(4.375872112626925, 8.917730007820797),
(4.236547993389047, 6.458941130666561),
(6.027633760716439, 5.448831829968968),
(9.636627605086398, 3.834415188269945),
(0.7103605819788694, 0.8712929970154071),
(0.2021839744032572, 8.32619845547938),
(7.781567509498505, 8.700121482468191),
]

df = pandas.DataFrame(data_points, columns=["x", "y"])
df["value"] = df["x"] + df["y"]

hull = ConvexHull(hull_points)

def some_f(xy):
return xy[0] + xy[1]

learner_old = LearnerND(some_f, hull)
learner_old.load_dataframe(
df,
with_default_function_args=False,
point_names=("x", "y"),
value_name="value",
)

def old_ask_bound_point(self):
new_point = next(
p
for p in self._bounds_points
if p not in self.data and p not in self.pending_points
)
self.tell_pending(new_point)
return new_point, np.inf

learner_old._ask_bound_point = MethodType(old_ask_bound_point, learner_old)

def naive_is_known_point(self, point):
point = tuple(map(float, point))
return point in self.data or point in self.pending_points

learner_old._is_known_point = MethodType(naive_is_known_point, learner_old)
learner_old._bound_match_tol = 0.0

with pytest.raises(ValueError):
BlockingRunner(learner_old, npoints_goal=len(df) + 1)

learner = LearnerND(some_f, hull)
learner.load_dataframe(
df,
with_default_function_args=False,
point_names=("x", "y"),
value_name="value",
)

target = len(df) + 1
BlockingRunner(learner, npoints_goal=target)
assert learner.npoints >= target
Loading