from .base import Strategy, Design, Transform
from summit.domain import *
from summit.utils.dataset import DataSet
import numpy as np
[docs]class FullFactorial(Strategy):
"""Full factorial DoE
Strategy for full factorial design of experiments in all decision variables.
Parameters
----------
domain: :class:`~summit.domain.Domain`
The Summit domain describing the optimization problem.
Examples
-------
>>> from summit.domain import Domain, ContinuousVariable
>>> from summit.strategies import FullFactorial
>>> import numpy as np
>>> domain = Domain()
>>> domain += ContinuousVariable(name='temperature', description='reaction temperature in celsius', bounds=[50, 100])
>>> domain += ContinuousVariable(name='flowrate_a', description='flow of reactant a in mL/min', bounds=[0.1, 0.5])
>>> domain += ContinuousVariable(name='flowrate_b', description='flow of reactant b in mL/min', bounds=[0.1, 0.5])
>>> levels = dict(temperature=[50,100], flowrate_a=[0.1,0.5], flowrate_b=[0.1,0.5])
>>> strategy = FullFactorial(domain)
>>> strategy.suggest_experiments(levels)
NAME temperature flowrate_a flowrate_b strategy
TYPE DATA DATA DATA METADATA
0 50.0 0.1 0.1 FullFactorial
1 100.0 0.1 0.1 FullFactorial
2 50.0 0.5 0.1 FullFactorial
3 100.0 0.5 0.1 FullFactorial
4 50.0 0.1 0.5 FullFactorial
5 100.0 0.1 0.5 FullFactorial
6 50.0 0.5 0.5 FullFactorial
7 100.0 0.5 0.5 FullFactorial
Notes
-----
We rely on the implementation from `pyDoE2 <https://github.com/clicumu/pydoe2>`_.
"""
def __init__(self, domain: Domain, transform: Transform = None, **kwargs):
super().__init__(domain, transform, **kwargs)
[docs] def suggest_experiments(self, levels_dict, **kwargs) -> DataSet:
"""Suggest experiments for a full factorial experimental design
Parameters
----------
levels_dict : dict
A dictionary with the number of levels for each variable. Keys are
the variable names and values are arrays with the values of each level.
Returns
-------
ds
A `Dataset` object with the random design
"""
num_experiments = np.prod([len(level) for level in levels_dict.values()])
design = Design(self.domain, num_experiments, "random")
levels = []
for v in self.domain.input_variables:
# Set number of levels per variable
var_levels = levels_dict.get(v.name)
num_levels = len(var_levels) if var_levels is not None else 2
levels.append(num_levels)
# Create full factorial design
doe = fullfact(levels)
for i, v in enumerate(self.domain.input_variables):
indices = doe[:, i]
indices = indices.astype(int)
values = np.array([levels_dict[v.name][i] for i in indices])
values = np.atleast_2d(values)
design.add_variable(v.name, values, indices=indices[:, np.newaxis])
ds = design.to_dataset()
ds[("strategy", "METADATA")] = "FullFactorial"
return ds
def reset(self):
pass
def fullfact(levels):
"""
Create a general full-factorial design
Parameters
----------
levels : array-like
An array of integers that indicate the number of levels of each input
design factor.
Returns
-------
mat : 2d-array
The design matrix with coded levels 0 to k-1 for a k-level factor
Notes
------
This code is copied from pydoe2: https://github.com/clicumu/pyDOE2/blob/master/pyDOE2/doe_factorial.py
"""
n = len(levels) # number of factors
nb_lines = np.prod(levels) # number of trial conditions
H = np.zeros((nb_lines, n))
level_repeat = 1
range_repeat = np.prod(levels)
for i in range(n):
range_repeat //= levels[i]
lvl = []
for j in range(levels[i]):
lvl += [j] * level_repeat
rng = lvl * range_repeat
level_repeat *= levels[i]
H[:, i] = rng
return H