Full and Fractional Factorial Designs¶
BoFire can be used to setup full (two level) and fractional factorial designs (https://en.wikipedia.org/wiki/Fractional_factorial_design). This tutorial notebook shows how.
Imports and helper functions¶
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import bofire.strategies.api as strategies
from bofire.data_models.domain.api import Domain
from bofire.data_models.features.api import ContinuousInput
from bofire.data_models.strategies.api import FractionalFactorialStrategy
from bofire.utils.doe import get_alias_structure, get_confounding_matrix, get_generator
def plot_design(design: pd.DataFrame):
# we do a plot with three subplots in one row in which the three degrees of freedom (temperature, time and ph) are plotted
_, axs = plt.subplots(1, 3, figsize=(15, 5))
axs[0].scatter(design["temperature"], design["time"])
axs[0].set_xlabel("Temperature")
axs[0].set_ylabel("Time")
axs[1].scatter(design["temperature"], design["ph"])
axs[1].set_xlabel("Temperature")
axs[1].set_ylabel("pH")
axs[2].scatter(design["time"], design["ph"])
axs[2].set_xlabel("Time")
axs[2].set_ylabel("pH")
plt.show()
Setup the problem domain¶
The designs are generated for a simple three dimensional problem comprised of three continuous factors/features.
domain = Domain(
inputs=[
ContinuousInput(key="temperature", bounds=(20, 80)),
ContinuousInput(key="time", bounds=(60, 120)),
ContinuousInput(key="ph", bounds=(7, 13)),
],
)
Setup a full factorial design¶
Here we setup a full two-level factorial design including a center point and plot it.
strategy_data = FractionalFactorialStrategy(
domain=domain,
n_center=1, # number of center points
n_repetitions=1, # number of repetitions, we do only one round here
)
strategy = strategies.map(strategy_data)
design = strategy.ask()
display(design)
plot_design(design=design)
The confounding structure is shown below, as expected for a full factorial design, no confound is present.
m = get_confounding_matrix(domain.inputs, design=design, interactions=[2])
sns.heatmap(m, annot=True, annot_kws={"fontsize": 7}, fmt="2.1f")
plt.show()
Setup a fractional factorial design¶
Here a fractional factorial design of the form $2^{3-1}$ is setup by specifying the number of generators (here 1). In comparison to the full factorial design with 9 candidates, it features only 5 experiments.
strategy_data = FractionalFactorialStrategy(
domain=domain,
n_center=1, # number of center points
n_repetitions=1, # number of repetitions, we do only one round here
n_generators=1, # number of generators, ie number of reducing factors
)
strategy = strategies.map(strategy_data)
design = strategy.ask()
display(design)
The generator string is automatically generated by making use of the method get_generator
and specifying the total number of factors (here 3) and the number of generators (here 1).
get_generator(n_factors=3, n_generators=1)
As expected for a type III design the main effects are confounded with the two factor interactions:
m = get_confounding_matrix(domain.inputs, design=design, interactions=[2])
sns.heatmap(m, annot=True, annot_kws={"fontsize": 7}, fmt="2.1f")
plt.show()
This can also be expressed by the so called alias structure that can be calculated as following:
get_alias_structure("a b ab")
Here again a fractional factorial design of the form $2^{3-1}$ is setup by providing the complete generator string of the form a b -ab
explicitly to the strategy.
strategy_data = FractionalFactorialStrategy(
domain=domain,
n_center=1, # number of center points
n_repetitions=1, # number of repetitions, we do only one round here
generator="a b -ab", # the exact generator
)
strategy = strategies.map(strategy_data)
design = strategy.ask()
display(design)
The last two designs differ only in the last feature time
, since the generator strings are different. In the first one it holds time=ph x temperature
whereas in the second it holds time=-ph x temperature
, which is also reflected in the confounding structure.
m = get_confounding_matrix(domain.inputs, design=design, interactions=[2])
sns.heatmap(m, annot=True, annot_kws={"fontsize": 7}, fmt="2.1f")
plt.show()