Skip to content

Commit

Permalink
Merge pull request #306 from novonordisk-research/develop
Browse files Browse the repository at this point in the history
DOE branch up to date
  • Loading branch information
RuneChristensen-NN authored Dec 16, 2024
2 parents 0695175 + a8927a6 commit 35c322d
Show file tree
Hide file tree
Showing 24 changed files with 1,372 additions and 12 deletions.
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

0 comments on commit 35c322d

Please sign in to comment.