Skip to content

Commit

Permalink
Add default generators (#431)
Browse files Browse the repository at this point in the history
  • Loading branch information
jduerholt authored Aug 27, 2024
1 parent 13e2184 commit 3b75089
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 5 deletions.
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)

0 comments on commit 3b75089

Please sign in to comment.