Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOE branch up to date #306

Merged
merged 35 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7c1c53b
feat: Initial work on expyrementor
SRFU-NN Dec 2, 2024
8eab1da
refactor: Moved caching suggested points to CachingStrategizer
SRFU-NN Dec 3, 2024
7ca2a2c
fix: Fixed bug in InitialPointSuggestor
SRFU-NN Dec 3, 2024
5351c81
feat: removed caching strategizer
SRFU-NN Dec 4, 2024
fb4c3b2
fix: Made Optmizer.update_next more flexible
SRFU-NN Dec 4, 2024
afffb9e
feat: Small improvements in Expyrementor and suggestors
SRFU-NN Dec 4, 2024
f2bd641
doc: Removed redundant n_initial_points fron Expyrementor example
SRFU-NN Dec 4, 2024
1556e71
chore: Removed testing for python 3.8
SRFU-NN Dec 4, 2024
4672fa5
chore: Expyrementor fully switched to return numpy.ndarray
SRFU-NN Dec 5, 2024
e43faf6
feat: Allow for instantiated suggestors in RandomStrategizer
SRFU-NN Dec 5, 2024
d121b85
doc: Documneting "forbidden" keys in Suggestor definition dicts
SRFU-NN Dec 5, 2024
3ff20bc
fix: One more dtype = object for numpy
SRFU-NN Dec 5, 2024
ed4427e
doc: Better description for Expyrementor notebook
SRFU-NN Dec 5, 2024
e3651da
Chore: rename Expyrementor to XpyriMentor
SRFU-NN Dec 5, 2024
63a88f0
chore: Rename test file
SRFU-NN Dec 5, 2024
7611efb
chore: Removed n_objectives from RandomStrategizzer init since it isn…
SRFU-NN Dec 5, 2024
6312e0a
Merge remote-tracking branch 'origin/develop' into director
SRFU-NN Dec 6, 2024
1dacbe8
fix: LHS sampling return an numpy.ndarray instead of a list of lists
SRFU-NN Dec 6, 2024
aa11390
feat: Space.sample gives errors oon wrongly sized input
SRFU-NN Dec 6, 2024
65bed88
test: Fixed last test for RandomStrategizer
SRFU-NN Dec 6, 2024
2d25c62
chore: Better type hints fro XpyriMentor
SRFU-NN Dec 6, 2024
ab90ab2
doc: Better docstrings for Suggestor protocol
SRFU-NN Dec 6, 2024
74fddd8
feat: Replaced InititalPointStrategizer with SequentialStrategizer.
SRFU-NN Dec 9, 2024
57bf808
chore: Renamed jupyter notebook
SRFU-NN Dec 9, 2024
ae58e2f
doc: Updated Xpyrimentor notebook to use SequentialStrategizer
SRFU-NN Dec 9, 2024
dd68eef
feat: SequentialStrategizer passes budget to its suggestors, LHSSugge…
SRFU-NN Dec 10, 2024
3c59437
doc: Comments in SequentialStrategizer suggest method describe why su…
SRFU-NN Dec 10, 2024
0a94fc8
fix: POSuggestor returns a numpy.ndarray
SRFU-NN Dec 10, 2024
d53d698
Merge remote-tracking branch 'origin/develop' into director
SRFU-NN Dec 11, 2024
47845e3
chore: "name" -> "suggestor_name"
SRFU-NN Dec 11, 2024
b1fdd2a
doc: Removed mention of "name" as reserved keyword in suggestors
SRFU-NN Dec 11, 2024
891788f
Merge pull request #296 from novonordisk-research/director
SRFU-NN Dec 11, 2024
832063d
chore: Readability updates from code review.
SRFU-NN Dec 12, 2024
2afc35e
Merge remote-tracking branch 'origin/develop' into director
SRFU-NN Dec 12, 2024
a8927a6
Merge pull request #304 from novonordisk-research/director
SRFU-NN Dec 12, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/python-package-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4
Expand Down
7 changes: 5 additions & 2 deletions ProcessOptimizer/optimizer/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1053,10 +1053,13 @@ def get_constraints(self):
def update_next(self):
"""Updates the value returned by opt.ask(). Useful if a parameter was updated after ask was called."""
self.cache_ = {}
self._n_initial_points = self.n_initial_points_-len(self.Xi)
copy = self.copy(random_state=self.rng)
self.models = copy.models
# Ask for a new next_x. Usefull if new constraints have been added or lenght_scale has been tweaked.
# We only need to overwrite _next_x if it exists.
if hasattr(self, "_next_x"):
opt = self.copy(random_state=self.rng)
if hasattr(copy, "_next_x"):
opt = copy
self._next_x = opt._next_x

def get_result(self):
Expand Down
32 changes: 29 additions & 3 deletions ProcessOptimizer/space/space.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Iterable, List, Union

Expand All @@ -11,7 +12,7 @@
from .transformers import Identity
from .transformers import Log10
from .transformers import Pipeline
from ..utils import get_random_generator
from ..utils import get_random_generator, is_2Dlistlike

# helper class to be able to print [1, ..., 4] instead of [1, '...', 4]

Expand Down Expand Up @@ -39,7 +40,7 @@ def space_factory(input: Union["Space", List]) -> "Space":
return Space(input)


def check_dimension(dimension, transform=None):
def check_dimension(dimension, transform=None) -> Dimension:
"""Turn a provided dimension description into a dimension object.

Checks that the provided dimension falls into one of the
Expand Down Expand Up @@ -201,7 +202,7 @@ def sample(
unique_points.append(point)
seen.add(point)
sampled_points = unique_points
return np.array(sampled_points)
return np.array(sampled_points, dtype=object)

@abstractmethod
def _sample(self, points: Iterable[float]) -> np.array:
Expand Down Expand Up @@ -817,6 +818,31 @@ def inverse_transform(self, Xt):

return rows

def sample(self, points: Iterable[Union[float, Iterable[float]]]) -> np.ndarray:
"""Draw points from the space.

Parameters
----------
* `points` [float or list[float]]:
A single point or a list of points to sample. All must be between 0 and 1.

Returns
-------
* `sampled_points` [np.ndarray]:
The sampled points.
"""
if not is_2Dlistlike(points):
points = [points]
if any(len(point) != len(self) for point in points):
raise ValueError(
"One or more points does not have the same length as the space " +
str(({len(self)}))
)
sampled_points = []
for dim in self.dimensions:
sampled_points.append(dim.sample([p[0] for p in points]))
return np.array(sampled_points, dtype = object).transpose()

@property
def n_dims(self):
"""The dimensionality of the original space."""
Expand Down
60 changes: 56 additions & 4 deletions ProcessOptimizer/tests/test_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@ def test_real_bounds():
@pytest.mark.parametrize(
"dimension, ismember, point_type",
[
(Real(1, 10), lambda x: 1 <= x <= 10, np.float64),
(Real(1, 10), lambda x: 1 <= x <= 10, float),
(
Real(10**-5, 10**5, prior="log-uniform"),
lambda x: 10**-5 <= x <= 10**5,
np.float64,
float,
),
(Integer(1, 10), lambda x: 1 <= x <= 10, np.integer),
(Integer(1, 10, transform="normalize"), lambda x: 0 <= x <= 10, np.integer),
(Integer(1, 10), lambda x: 1 <= x <= 10, int),
(Integer(1, 10, transform="normalize"), lambda x: 0 <= x <= 10, int),
(Categorical(["cat", "dog", "rat"]), lambda x: x in ["cat", "dog", "rat"], str),
],
)
Expand Down Expand Up @@ -744,3 +744,55 @@ def test_lhs():
# Asserting the the values are the same for both the lhs, even though the order is different
for i in range(4):
assert set([x[i] for x in lhs_one]) == set([x[i] for x in lhs_two])


def test_sample():
SPACE = Space(
[
Integer(1, 6),
Real(1, 7),
Real(10**-3, 10**3, prior="log-uniform"),
Categorical(list("abc")),
]
)
# Getting one sample
samples = SPACE.sample([0.5]*len(SPACE))
assert len(samples) == 1
assert isinstance(samples, np.ndarray)
assert len(samples[0]) == 4
values = samples[0]
assert isinstance(values, np.ndarray)
assert isinstance(values[0], int)
assert isinstance(values[1], float)
assert isinstance(values[2], float)
assert isinstance(values[3], str)
# Getting multiple samples
samples = SPACE.sample([[0.5]*len(SPACE)]*5)
assert len(samples) == 5
assert len(samples[0]) == 4
assert isinstance(samples, np.ndarray)
for sample in samples:
assert isinstance(sample, np.ndarray)
assert isinstance(sample[0], int)
assert isinstance(sample[1], float)
assert isinstance(sample[2], float)
assert isinstance(sample[3], str)


def test_sample_wrong_size():
SPACE = Space(
[
Integer(1, 6),
Real(1, 7),
Real(10**-3, 10**3, prior="log-uniform"),
Categorical(list("abc")),
]
)
with pytest.raises(ValueError):
SPACE.sample([0.5]*3)
with pytest.raises(ValueError):
SPACE.sample([0.5]*5)
with pytest.raises(ValueError):
SPACE.sample([[0.5]*3]*5)
with pytest.raises(ValueError):
SPACE.sample([[0.5]*5]*3)
Empty file.
62 changes: 62 additions & 0 deletions ProcessOptimizer/tests/test_suggestors/test_lhs_suggestor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import numpy as np
import pytest
from ProcessOptimizer.space import space_factory
from XpyriMentor.suggestors import LHSSuggestor, Suggestor, suggestor_factory, IncompatibleNumberAsked


def test_initializaton():
suggestor = LHSSuggestor(
space=space_factory([[1, 2], [1, 2]]),
rng=np.random.default_rng(1),
)
assert isinstance(suggestor, Suggestor)
assert suggestor.n_points == 5
assert len(suggestor.cache) == 5


def test_factory():
space = space_factory([[1, 2], [1, 2]])
suggestor = suggestor_factory(
space=space,
definition={"suggestor_name": "LHS", "n_points": 10},
)
assert isinstance(suggestor, LHSSuggestor)
assert suggestor.n_points == 10


def test_suggest():
space = space_factory([[0, 10], [0.0, 1.0], ["cat", "dog"]])
suggestor = LHSSuggestor(
space, rng=np.random.default_rng(1), n_points=5
)
suggestions = suggestor.suggest([], [])
assert len(suggestions) == 1
assert len(suggestions[0]) == 3
assert suggestions[0] in space
suggestions = suggestor.suggest([], [], n_asked=5)
# Testing that the values for each dimension is regularly spaced over the range
assert set(suggestion[0] for suggestion in suggestions) == {1, 3, 5, 7, 9}
assert set(suggestion[1] for suggestion in suggestions) == {0.1, 0.3, 0.5, 0.7, 0.9}
assert set(suggestion[2] for suggestion in suggestions) == {"cat", "dog"}


def test_suggest_too_many():
space = space_factory([[0, 10], [0.0, 1.0], ["cat", "dog"]])
suggestor = LHSSuggestor(
space, rng=np.random.default_rng(1), n_points=5
)
for n_told in range(1, 5):
told = suggestor.suggest([], [], n_asked=n_told)
suggestor.suggest(told, [0]*n_told, n_asked=1)
with pytest.raises(IncompatibleNumberAsked):
suggestor.suggest([], [], n_asked=6)
with pytest.raises(IncompatibleNumberAsked):
suggestor.suggest([[1, 0.0, "cat"]], [1], n_asked=5)


def test_n():
space = space_factory([[0, 10], [0.0, 1.0], ["cat", "dog"]])
for n in range(1, 10):
suggestor = LHSSuggestor(space, rng=np.random.default_rng(1), n_points=n)
assert suggestor.n_points == n
assert len(suggestor.cache) == n
37 changes: 37 additions & 0 deletions ProcessOptimizer/tests/test_suggestors/test_po_suggestor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import numpy as np
from XpyriMentor.suggestors import POSuggestor, suggestor_factory
from ProcessOptimizer.space import space_factory


def test_initialization():
space = space_factory([[0, 1], [0, 1]])
suggestor = POSuggestor(space, rng=np.random.default_rng(1))
assert isinstance(suggestor, POSuggestor)
assert suggestor.optimizer._n_initial_points == 0
nonstandard_suggestor = POSuggestor(
space, rng=np.random.default_rng(1), n_initial_points=5
)
assert nonstandard_suggestor.optimizer._n_initial_points == 5


def test_factory():
space = space_factory([[0, 1], [0, 1]])
suggestor = suggestor_factory(
space=space,
definition={"suggestor_name": "PO"},
)
assert isinstance(suggestor, POSuggestor)
assert suggestor.optimizer._n_initial_points == 0


def test_suggest():
space = space_factory([[0, 1], [0, 1]])
suggestor = POSuggestor(space, rng=np.random.default_rng(1))
suggestions = suggestor.suggest([[1, 1]], [1])
assert len(suggestions) == 1
assert len(suggestions[0]) == 2
assert suggestions[0] in space
suggestions = suggestor.suggest([[1, 1]], [1], n_asked=5)
assert len(suggestions) == 5
for suggestion in suggestions:
assert suggestion in space
125 changes: 125 additions & 0 deletions ProcessOptimizer/tests/test_suggestors/test_random_strategizer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import numpy as np
import pytest
import warnings
from XpyriMentor.suggestors import (
RandomStragegizer,
Suggestor,
suggestor_factory,
POSuggestor,
LHSSuggestor,
DefaultSuggestor
)
from XpyriMentor.suggestors.default_suggestor import NoDefaultSuggestorError
from ProcessOptimizer.space import space_factory


class MockSuggestor:
def __init__(self, suggestions: list):
self.suggestions = suggestions
self.last_input = {}

def suggest(self, Xi, Yi, n_asked=1):
self.last_input = {"Xi": Xi, "Yi": Yi}
return self.suggestions*n_asked


def test_random_strategizer():
suggestor = RandomStragegizer(
suggestors=[(0.8, MockSuggestor([[1]])), (0.2, MockSuggestor([[2]]))],
rng=np.random.default_rng(1)
)
assert isinstance(suggestor, Suggestor)
# np.random.default_rng(1).random() gives 0.5118216247148916, 0.9504636963259353,
# and 0.14415961271963373 on the first three calls, so the first three calls
# should return the suggestors with weights 0.8, 0.2, and 0.8, respectively.
assert suggestor.suggest([], []) == [[1]]
assert suggestor.suggest([], []) == [[2]]
assert suggestor.suggest([], []) == [[1]]


def test_factory():
space = space_factory([[0, 1], [0, 1]])
suggestor = suggestor_factory(
space=space,
definition={"suggestor_name": "Random", "suggestors": [
{"suggestor_usage_ratio": 0.8, "suggestor_name": "PO"},
{"suggestor_usage_ratio": 0.2, "suggestor_name": "LHS"},]},
)
assert isinstance(suggestor, RandomStragegizer)
assert len(suggestor.suggestors) == 2
assert suggestor.suggestors[0][0] == 0.8
assert suggestor.suggestors[1][0] == 0.2
assert isinstance(suggestor.suggestors[0][1], POSuggestor)
assert isinstance(suggestor.suggestors[1][1], LHSSuggestor)


def test_random_multiple_ask():
suggestor = RandomStragegizer(
suggestors=[(0.8, MockSuggestor([[1]])), (0.2, MockSuggestor([[2]]))],
rng=np.random.default_rng(1)
)
assert all(suggestor.suggest([], [], n_asked=2) == [[1], [2]])
assert all(suggestor.suggest([], [], n_asked=3) == [[1], [1], [2]])


def test_default_suggestor():
with pytest.raises(NoDefaultSuggestorError):
RandomStragegizer(
suggestors=[
(0.8, MockSuggestor([[1]])),
(0.2, DefaultSuggestor(space=[], n_objectives=1, rng=None))
],
rng=np.random.default_rng(1)
)


def test_wrong_sum():
with pytest.warns(UserWarning):
# Warning if the sum of usage ratios is not 1 or 100
RandomStragegizer(
suggestors=[(0.8, MockSuggestor([[1]])), (0.3, MockSuggestor([[2]]))],
rng=np.random.default_rng(1)
)
with warnings.catch_warnings():
warnings.simplefilter("error")
# No warnings if the sum of usage ratios is 1
RandomStragegizer(
suggestors=[(0.8, MockSuggestor([[1]])), (0.2, MockSuggestor([[2]]))],
rng=np.random.default_rng(1)
)
# No warnings if the sum of usage ratios is 100
RandomStragegizer(
suggestors=[(80, MockSuggestor([[1]])), (20, MockSuggestor([[2]]))],
rng=np.random.default_rng(1)
)


def test_random_with_suggestor_given():
space = space_factory([[0, 1], [0, 1]])
suggestor = suggestor_factory(
space=space,
definition={"suggestor_name": "Random", "suggestors": [
{"suggestor_usage_ratio": 0.8, "suggestor": MockSuggestor([[1]])},
{"suggestor_usage_ratio": 0.2, "suggestor": MockSuggestor([[2]])},]},
)
assert isinstance(suggestor, RandomStragegizer)
assert len(suggestor.suggestors) == 2
assert suggestor.suggestors[0][0] == 0.8
assert suggestor.suggestors[1][0] == 0.2
assert isinstance(suggestor.suggestors[0][1], MockSuggestor)
assert isinstance(suggestor.suggestors[1][1], MockSuggestor)


def test_random_with_suggestor_given_wrong_keys():
space = space_factory([[0, 1], [0, 1]])
with pytest.raises(ValueError):
suggestor_factory(
space=space,
definition={"name": "Random", "suggestors": [
{"suggestor_usage_ratio": 0.8, "suggestor": MockSuggestor([[1]])},
{
"suggestor_usage_ratio": 0.2,
"suggestor": MockSuggestor([[2]]),
"additional_key": "Can't have this key",
},]},
)
Loading
Loading