diff --git a/smt/sampling_methods/pydoe.py b/smt/sampling_methods/pydoe.py index c1a9707fa..8425e0254 100644 --- a/smt/sampling_methods/pydoe.py +++ b/smt/sampling_methods/pydoe.py @@ -3,71 +3,122 @@ """ from pyDOE3 import doe_box_behnken from pyDOE3 import doe_gsd +from pyDOE3 import doe_factorial +from pyDOE3 import doe_plackett_burman import numpy as np from smt.sampling_methods.sampling_method import SamplingMethod -class BoxBehnken(SamplingMethod): - def _compute (self, nt): - nlevels = 3 +class PyDoeSamplingMethod(SamplingMethod): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.nx = self.options["xlimits"].shape[0] + self.levels = None + def _compute(self, nt: int = None): xlimits = self.options["xlimits"] - nx = xlimits.shape[0] + levels = self.levels - box_behnken = doe_box_behnken.bbdesign(nx) - indices = np.array(box_behnken + 1, dtype=int) + doe = self._compute_doe() + indices = np.array(doe, dtype=int) print(indices) - values = np.zeros((nx, nlevels)) - for i in range(nx): - values[i, :] = np.linspace(xlimits[i, 0], xlimits[i, 1], num=nlevels) + values = np.zeros((self.nx, max(levels))) + for i in range(self.nx): + values[i, 0 : levels[i]] = np.linspace( + xlimits[i, 0], xlimits[i, 1], num=levels[i] + ) print(values) - res = np.zeros(box_behnken.shape) + res = np.zeros(doe.shape) i = 0 - for idx in indices: - for j in range(nx): + for idx in indices: + for j in range(self.nx): res[i, j] = values[j, idx[j]] - i = i+1 + i = i + 1 return res + def _compute_doe(): + raise NotImplementedError( + "You have to implement DOE generation method _compute_doe()" + ) + + +class BoxBehnken(PyDoeSamplingMethod): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.levels = [3] * self.nx # for box behnken the number of levels is fixed + + def _compute_doe(self): + box_behnken_doe = ( + doe_box_behnken.bbdesign(self.nx) + 1 + ) # We have to increment the elements of doe_box_behnken to have the indices + + return box_behnken_doe + + +class Gsd(PyDoeSamplingMethod): + def __init__(self, **kwargs): + super().__init__(**kwargs) -class Gsd(SamplingMethod): + self.levels = self.options["levels"] def _initialize(self, **kwargs): self.options.declare( - "levels", - types= list, - desc="number of factor levels per factor in design", - ) + "levels", + types=list, + desc="number of factor levels per factor in design", + ) self.options.declare( - "reduction", - types=int, - desc="Reduction factor (bigger than 1). Larger `reduction` means fewer experiments in the design and more possible complementary designs", - ) + "reduction", + types=int, + desc="Reduction factor (bigger than 1). Larger `reduction` means fewer experiments in the design and more possible complementary designs", + ) - def _compute (self, nt): + def _compute_doe(self): levels = self.options["levels"] reduction = self.options["reduction"] - xlimits = self.options["xlimits"] - nx = xlimits.shape[0] + gsd_doe = doe_gsd.gsd(levels, reduction) - gsd = doe_gsd.gsd(levels, reduction) - indices = np.array(gsd, dtype=int) - print(indices) + return gsd_doe - values = np.zeros((nx, max(levels))) - for i in range(nx): - values[i, 0:levels[i]] = np.linspace(xlimits[i, 0], xlimits[i, 1], num=levels[i]) - print(values) - res = np.zeros(gsd.shape) - i = 0 - for idx in indices: - for j in range(nx): - res[i, j] = values[j, idx[j]] - i = i+1 +class Factorial(PyDoeSamplingMethod): + def __init__(self, **kwargs): + super().__init__(**kwargs) - return res + self.levels = self.options["levels"] + + def _initialize(self, **kwargs): + self.options.declare( + "levels", + types=list, + desc="number of factor levels per factor in design", + ) + + def _compute_doe(self): + levels = self.options["levels"] + factorial_doe = doe_factorial.fullfact(levels) + + return factorial_doe + + +class PlackettBurman(PyDoeSamplingMethod): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.levels = [2] * self.nx # for plackett burman the number of levels is fixed + + def _compute_doe(self): + plackett_burman_doe = doe_plackett_burman.pbdesign(self.nx) + ny = plackett_burman_doe.shape[1] + nb_rows = 4 * ( + int(self.nx / 4) + 1 + ) # calculate the correct number of rows (multiple of 4) + for i in range(nb_rows): + for j in range(ny): + if plackett_burman_doe[i, j] == -1: + plackett_burman_doe[i, j] = 0 + + return plackett_burman_doe diff --git a/smt/sampling_methods/tests/test_pydoe.py b/smt/sampling_methods/tests/test_pydoe.py index 8a195921b..73dfdf5a7 100644 --- a/smt/sampling_methods/tests/test_pydoe.py +++ b/smt/sampling_methods/tests/test_pydoe.py @@ -3,94 +3,115 @@ from smt.sampling_methods import pydoe + class TestPyDOE3(unittest.TestCase): def test_bbdesign(self): - xlimits = np.array([[2., 5], [-5, 1], [0, 3]]) + xlimits = np.array([[2.0, 5], [-5, 1], [0, 3]]) sampling = pydoe.BoxBehnken(xlimits=xlimits) - - num = 10 actual = sampling() self.assertEqual((15, 3), actual.shape) print(actual) - expected = [[ 2., -5., 1.5], - [ 5., -5., 1.5], - [ 2., 1., 1.5], - [ 5., 1., 1.5], - [ 2., -2., 0.], - [ 5., -2., 0.], - [ 2., -2., 3.], - [ 5., -2., 3.], - [ 3.5, -5., 0.], - [ 3.5, 1., 0.], - [ 3.5, -5., 3.], - [ 3.5, 1., 3.], - [ 3.5, -2., 1.5], - [ 3.5, -2., 1.5], - [ 3.5, -2., 1.5] - ] - - + expected = [ + [2.0, -5.0, 1.5], + [5.0, -5.0, 1.5], + [2.0, 1.0, 1.5], + [5.0, 1.0, 1.5], + [2.0, -2.0, 0.0], + [5.0, -2.0, 0.0], + [2.0, -2.0, 3.0], + [5.0, -2.0, 3.0], + [3.5, -5.0, 0.0], + [3.5, 1.0, 0.0], + [3.5, -5.0, 3.0], + [3.5, 1.0, 3.0], + [3.5, -2.0, 1.5], + [3.5, -2.0, 1.5], + [3.5, -2.0, 1.5], + ] np.testing.assert_allclose(actual, expected) - def test_gsd1(self): - xlimits = np.array([[5, 11], [0,6], [-3, 4]]) - sampling = pydoe.Gsd(xlimits = xlimits, levels = [3, 4, 6], reduction = 4) + xlimits = np.array([[5, 11], [0, 6], [-3, 4]]) + sampling = pydoe.Gsd(xlimits=xlimits, levels=[3, 4, 6], reduction=4) + actual = sampling() + self.assertEqual((18, 3), actual.shape) + print(actual) + expected = [ + [5, 0, -3], + [5, 0, 2.6], + [5, 2, -1.6], + [5, 2, 4], + [5, 4, -0.2], + [5, 6, 1.2], + [8, 0, -1.6], + [8, 0, 4], + [8, 2, -0.2], + [8, 4, 1.2], + [8, 6, -3], + [8, 6, 2.6], + [11, 0, -0.2], + [11, 2, 1.2], + [11, 4, -3], + [11, 4, 2.6], + [11, 6, -1.6], + [11, 6, 4], + ] + + np.testing.assert_allclose(actual, expected) + + def test_factorial(self): + xlimits = np.array([[4, 10], [-3, 3], [5, 7]]) + sampling = pydoe.Factorial(xlimits=xlimits, levels=[2, 4, 3]) actual = sampling() - self.assertEqual((18,3), actual.shape) + self.assertEqual((24, 3), actual.shape) print(actual) - expected = [[5, 0, -3], - [5, 0, 2.6], - [5, 2, -1.6], - [5, 2, 4], - [5, 4, -0.2], - [5, 6, 1.2], - [8, 0, -1.6], - [8, 0, 4], - [8, 2, -0.2], - [8, 4, 1.2], - [8, 6, -3], - [8, 6, 2.6], - [11, 0, -0.2], - [11, 2, 1.2], - [11, 4, -3], - [11, 4, 2.6], - [11, 6, -1.6], - [11, 6, 4] - ] - + expected = [ + [4.0, -3.0, 5.0], + [10.0, -3.0, 5.0], + [4.0, -1.0, 5.0], + [10.0, -1.0, 5.0], + [4.0, 1.0, 5.0], + [10.0, 1.0, 5.0], + [4.0, 3.0, 5.0], + [10.0, 3.0, 5.0], + [4.0, -3.0, 6.0], + [10.0, -3.0, 6.0], + [4.0, -1.0, 6.0], + [10.0, -1.0, 6.0], + [4.0, 1.0, 6.0], + [10.0, 1.0, 6.0], + [4.0, 3.0, 6.0], + [10.0, 3.0, 6.0], + [4.0, -3.0, 7.0], + [10.0, -3.0, 7.0], + [4.0, -1.0, 7.0], + [10.0, -1.0, 7.0], + [4.0, 1.0, 7.0], + [10.0, 1.0, 7.0], + [4.0, 3.0, 7.0], + [10.0, 3.0, 7.0], + ] np.testing.assert_allclose(actual, expected) -# def test_gsd2(self): -# xlimits = np.array([[1, 7], [-5,1]]) -# sampling = pydoe.Gsd(xlimits = xlimits) -# -# actual = sampling() -# self.assertEqual((18,3), actual.shape) -# print(actual) -# expected = [ -# [ -# [1, -5], -# [1, -1], -# [7, -5], -# [7, -1], -# [4, -3], -# [4, 1] -# ] -# -# [ -# [1, -3], -# [1, 1], -# [7, -3], -# [7, 1], -# [4, -5], -# [4, -1] -# ] -# ] -# -# np.testing.assert_allclose(actual, expected) + def test_plackett_burman(self): + xlimits = np.array([[2, 5], [-5, 1], [0, 3], [4, 8], [-1, 2]]) + sampling = pydoe.PlackettBurman(xlimits=xlimits) + actual = sampling() + self.assertEqual((8, 5), actual.shape) + print(actual) + expected = [ + [2.0, -5.0, 3.0, 4.0, 2.0], + [5.0, -5.0, 0.0, 4.0, -1.0], + [2.0, 1.0, 0.0, 4.0, 2.0], + [5.0, 1.0, 3.0, 4.0, -1.0], + [2.0, -5.0, 3.0, 8.0, -1.0], + [5.0, -5.0, 0.0, 8.0, 2.0], + [2.0, 1.0, 0.0, 8.0, -1.0], + [5.0, 1.0, 3.0, 8.0, 2.0], + ] + np.testing.assert_allclose(actual, expected) if __name__ == "__main__":