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

Add default generators #431

Merged
merged 3 commits into from
Aug 27, 2024
Merged
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
170 changes: 170 additions & 0 deletions bofire/utils/default_fracfac_generators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import pandas as pd

# this are the default generators used for fractional factorial designs in BoFire
default_fracfac_generators = pd.DataFrame.from_dict(
[
{"n_factors": 3, "n_generators": 1, "generator": "C = AB"},
{"n_factors": 4, "n_generators": 1, "generator": "D = ABC"},
{"n_factors": 5, "n_generators": 1, "generator": "E = ABCD"},
{"n_factors": 5, "n_generators": 2, "generator": "D= AB ; E= AC"},
{"n_factors": 6, "n_generators": 1, "generator": "F = ABCDE"},
{"n_factors": 6, "n_generators": 2, "generator": "E = ABC ; F = BCD"},
{"n_factors": 6, "n_generators": 3, "generator": "D= AB ; E= AC ; F= BC"},
{"n_factors": 7, "n_generators": 1, "generator": "G = ABCDEF"},
{"n_factors": 7, "n_generators": 2, "generator": "F = ABCD ; G = ABDE"},
{"n_factors": 7, "n_generators": 3, "generator": "F = ABC ; F = BCD ; G = ACD"},
{
"n_factors": 7,
"n_generators": 4,
"generator": "D = AB ; E= AC ; F = BC ; G = ABC",
},
{"n_factors": 8, "n_generators": 1, "generator": "H = ABCDEFG"},
{"n_factors": 8, "n_generators": 2, "generator": "G = ABCD ; H = ABEF"},
{
"n_factors": 8,
"n_generators": 3,
"generator": "F = ABC ; G = ABD ; H = BCDE",
},
{
"n_factors": 8,
"n_generators": 4,
"generator": "E = BCD ; F = ACD ; G = ABC ; H = ABD ",
},
{"n_factors": 9, "n_generators": 2, "generator": "H = ACDFG ; J = BCEFG"},
{
"n_factors": 9,
"n_generators": 3,
"generator": "G = ABCD ; H = ACEF ; J = CDEF",
},
{
"n_factors": 9,
"n_generators": 4,
"generator": "F = BCDE ; G = ACDE ; H = ABDE ; J = ABCE",
},
{
"n_factors": 9,
"n_generators": 5,
"generator": "E = ABC ; F = BCD ; G = ACD ; H = ABD ; J = ABCD ",
},
{
"n_factors": 10,
"n_generators": 3,
"generator": "H = ABCG ; J = BCDE ; K = ACDF",
},
{
"n_factors": 10,
"n_generators": 4,
"generator": "G = BCDF ; H = ACDF ; J = ABDE ; K = ABCE ",
},
{
"n_factors": 10,
"n_generators": 5,
"generator": "F = ABCD ; G = ABCE ; H = ABDE ; J = ACDE ; K = BCDE ",
},
{
"n_factors": 10,
"n_generators": 6,
"generator": "E = ABC ; F = BCD ; G = ACD ; H = ABD ; J = ABCD ; K = AB ",
},
{
"n_factors": 11,
"n_generators": 4,
"generator": "H = ABCG ; J = BCDE ; K = ACDF ; L = ABCDEFG",
},
{
"n_factors": 11,
"n_generators": 5,
"generator": "G = CDE ; H = ABCD ; J = ABF ; K = BDEF ; L = ADEF ",
},
{
"n_factors": 11,
"n_generators": 6,
"generator": "F = ABC ; G = BCD ; H = CDE ; J = ACD ; K = ADE ; L = BDE ",
},
{
"n_factors": 11,
"n_generators": 7,
"generator": "E = ABC ; F = BCD ; G = ACD ; H = ABD ; J = ABCD ; K = AB ; L = AC ",
},
{
"n_factors": 12,
"n_generators": 5,
"generator": "H = ACDG ; J = ABCD ; K = BCFG ; L = ABDEFG ; M = CDEF",
},
{
"n_factors": 12,
"n_generators": 6,
"generator": "G = DEF ; H = ABC ; J = BCDE ; K = BCDF ; L = ABEF ; M = ACEF",
},
{
"n_factors": 12,
"n_generators": 7,
"generator": "F = ACE ; G = ACD ; H = ABD ; J = ABE ; K = CDE ; L = ABCDE ; M = ADE ",
},
{
"n_factors": 12,
"n_generators": 8,
"generator": "E = ABC ; F = ABD ; G = ACD ; H = BCD ; J = ABCD ; K = AB ; L = AC ; M = AD ",
},
{
"n_factors": 13,
"n_generators": 6,
"generator": "H = DEFG ; J = BCEG ; K = BCDFG ; L = ABDEF ; M = ACEF ; N = ABC ",
},
{
"n_factors": 13,
"n_generators": 7,
"generator": "G = ABC ; H = DEF ; J = BCDF ; K = BCDE ; L = ABEF ; M = ACEF ; N = BCEF ",
},
{
"n_factors": 13,
"n_generators": 8,
"generator": "F = ACE ; G = BCE ; H = ABC ; J = CDE ; K = ABCDE ; L = ABE ; M = ACD ; N = ADE ",
},
{
"n_factors": 13,
"n_generators": 9,
"generator": "E = ABC ; F = ABD ; G = ACD ; H = BCD ; J = ABCD ; K = AB ; L = AC ; M = AD ; N = BC ",
},
{
"n_factors": 14,
"n_generators": 7,
"generator": "H = EFG ; J = BCFG ; K = BCEG ; L = ABEF ; M = ACEF ; N = BCDEF ; O = ABC ",
},
{
"n_factors": 14,
"n_generators": 8,
"generator": "G = BEF ; H = BCF ; J = DEF ; K = CEF ; L = BCE ; M = CDF ; N = ACDE ; O = BCDEF ",
},
{
"n_factors": 14,
"n_generators": 9,
"generator": "F = ABC ; G = ABD ; H = ABE ; J = ACD ; K = ACE ; L = ADE ; M = BCD ; N = BCE ; O = BDE ",
},
{
"n_factors": 14,
"n_generators": 10,
"generator": "E = ABC ; F = ABD ; G = ACD ; H = BCD ; J = ABCD ; K = AB ; L = AC ; M = AD ; N = BC ; O = BD ",
},
{
"n_factors": 15,
"n_generators": 8,
"generator": "H = ABFG ; J = ACDEF ; K = BEF ; L = ABCEG ; M = CDFG ; N = ACDEG ; O = EFG ; P = ABDEFG ",
},
{
"n_factors": 15,
"n_generators": 9,
"generator": "G = ABC ; H = ABD ; J = ABE ; K = BCDE ; L = ACF ; M = ADF ; N = AEF ; O = CDEF ; P = ABCDEF",
},
{
"n_factors": 15,
"n_generators": 10,
"generator": "F = ABC ; G = ABD ; H = ABE ; J = ACD ; K = ACE ; L = ADE; M = BCD ; N = BCE ; O = BDE; P = CDE",
},
{
"n_factors": 15,
"n_generators": 11,
"generator": "E = ABC ; F = ABD ; G = ACD ; H = BCD ; J = ABCD ; K = AB ; L = AC ; M = AD ; N = BC ; O = BD ; P = CD ",
}, # type: ignore
]
)
57 changes: 56 additions & 1 deletion bofire/utils/doe.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from bofire.data_models.domain.api import Inputs
from bofire.data_models.features.api import CategoricalInput, ContinuousInput
from bofire.utils.default_fracfac_generators import default_fracfac_generators


def get_confounding_matrix(
Expand Down Expand Up @@ -224,7 +225,42 @@ def get_alias_structure(gen: str, order: int = 4) -> List[str]:
return aliases_readable


def get_generator(n_factors: int, n_generators: int) -> str:
def get_default_generator(n_factors: int, n_generators: int) -> str:
"""Returns the default generator for a given number of factors and generators.

In case the combination is not available, the function will raise an error.

Args:
n_factors: The number of factors.
n_generators: The number of generators.

Returns:
The generator.
"""
if n_generators == 0:
return " ".join(list(string.ascii_lowercase[:n_factors]))
df_generators = default_fracfac_generators
n_base_factors = n_factors - n_generators
if df_generators.loc[
(df_generators.n_factors == n_factors)
& (df_generators.n_generators == n_generators)
].empty:
raise ValueError("No generator available for the requested combination.")
generators = (
df_generators.loc[
(df_generators.n_factors == n_factors)
& (df_generators.n_generators == n_generators),
"generator",
]
.to_list()[0]
.split(";")
)
assert len(generators) == n_generators, "Number of generators does not match."
generators = [generator.split("=")[1].strip().lower() for generator in generators]
return " ".join(list(string.ascii_lowercase[:n_base_factors]) + generators)


def compute_generator(n_factors: int, n_generators: int) -> str:
"""Computes a generator for a given number of factors and generators.

Args:
Expand Down Expand Up @@ -268,3 +304,22 @@ def get_generator(n_factors: int, n_generators: int) -> str:
"Design not possible, as main factors are confounded with each other."
)
return " ".join(list(string.ascii_lowercase[:n_base_factors]) + generators)


def get_generator(n_factors: int, n_generators: int) -> str:
"""Returns a generator for a given number of factors and generators.

If the requested combination is available in the default generators, it will return
this one. Otherwise, it will compute a new one using `get_bofire_generator`.

Args:
n_factors: The number of factors.
n_generators: The number of generators.

Returns:
The generator.
"""
try:
return get_default_generator(n_factors, n_generators)
except ValueError:
return compute_generator(n_factors, n_generators)
28 changes: 24 additions & 4 deletions tests/bofire/utils/test_doe.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@

from bofire.data_models.domain.api import Inputs
from bofire.data_models.domain.features import ContinuousInput
from bofire.utils.default_fracfac_generators import default_fracfac_generators
from bofire.utils.doe import (
compute_generator,
ff2n,
fracfact,
get_alias_structure,
get_confounding_matrix,
get_default_generator,
get_generator,
validate_generator,
)
Expand Down Expand Up @@ -159,17 +162,34 @@ def test_validate_generator_invalid(n_factors: int, generator: str, message: str
(8, 4, "a b c d abc abd acd bcd"),
],
)
def test_get_generator(n_factors, n_generators, expected):
assert get_generator(n_factors, n_generators) == expected
def test_compute_generator(n_factors, n_generators, expected):
assert compute_generator(n_factors, n_generators) == expected


@pytest.mark.parametrize(
"n_factors, n_generators",
[(2, 1), (3, 2), (4, 3), (4, 2), (5, 3), (6, 4), (7, 5), (8, 5)],
)
def test_get_generator_invalid(n_factors, n_generators):
def test_compute_generator_invalid(n_factors, n_generators):
with pytest.raises(
ValueError,
match="Design not possible, as main factors are confounded with each other.",
):
get_generator(n_factors, n_generators)
compute_generator(n_factors, n_generators)


def test_get_default_generator():
for _, row in default_fracfac_generators.iterrows():
n_factors = row["n_factors"]
n_generators = row["n_generators"]
g = get_default_generator(n_factors, n_generators)
validate_generator(n_factors, g)
with pytest.raises(
ValueError, match="No generator available for the requested combination."
):
get_default_generator(100, 1)


def test_get_generator():
assert get_generator(6, 2) != compute_generator(6, 2)
assert get_generator(16, 1) == compute_generator(16, 1)
Loading