<reponame>liujiamingustc/phd<filename>surrogate/sampling/samFullFact.py
# Copyright 2016 <NAME>
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#    http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Author: <NAME> <<EMAIL>>
# License: Apache License, Version 2.0
# Create: 2016-12-02

import re

import numpy as np

def samFullFact(levels):
    """Create a general full-factorial design

    :param levels: An array of integers that indicate the number of levels of each input
           design factor.
    :returns: The design matrix with coded levels 0 to k-1 for a k-level factor

    This code was originally published by the following individuals for use with
    Scilab:

    - Copyright (C) 2012 - 2013 - <NAME>
    - Copyright (C) 2012 - <NAME>
    - Copyright (C) 2010 - 2011 - INRIA - <NAME>
    - Copyright (C) 2009 - <NAME>
    - Copyright (C) 2009 - CEA - <NAME>

    website: forge.scilab.org/index.php/p/scidoe/sourcetree/master/macros

    Much thanks goes to these individuals. It has been converted to Python by
    Abraham Lee.

    :Example:

    >>> samFullFact([2, 4, 3])
    array([[ 0.,  0.,  0.],
           [ 1.,  0.,  0.],
           [ 0.,  1.,  0.],
           [ 1.,  1.,  0.],
           [ 0.,  2.,  0.],
           [ 1.,  2.,  0.],
           [ 0.,  3.,  0.],
           [ 1.,  3.,  0.],
           [ 0.,  0.,  1.],
           [ 1.,  0.,  1.],
           [ 0.,  1.,  1.],
           [ 1.,  1.,  1.],
           [ 0.,  2.,  1.],
           [ 1.,  2.,  1.],
           [ 0.,  3.,  1.],
           [ 1.,  3.,  1.],
           [ 0.,  0.,  2.],
           [ 1.,  0.,  2.],
           [ 0.,  1.,  2.],
           [ 1.,  1.,  2.],
           [ 0.,  2.,  2.],
           [ 1.,  2.,  2.],
           [ 0.,  3.,  2.],
           [ 1.,  3.,  2.]])

    """
    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


################################################################################

def samFF2n(n):
    """
    Create a 2-Level full-factorial design

    Parameters
    ----------
    n : int
        The number of factors in the design.

    Returns
    -------
    mat : 2d-array
        The design matrix with coded levels -1 and 1

    Example
    -------
    ::

        >>> samFF2n(3)
        array([[-1., -1., -1.],
               [ 1., -1., -1.],
               [-1.,  1., -1.],
               [ 1.,  1., -1.],
               [-1., -1.,  1.],
               [ 1., -1.,  1.],
               [-1.,  1.,  1.],
               [ 1.,  1.,  1.]])

    """
    return 2 * samFullFact([2] * n) - 1


################################################################################

def samFracFact(gen):
    """
    Create a 2-level fractional-factorial design with a generator string.

    Parameters
    ----------
    gen : str
        A string, consisting of lowercase, uppercase letters or operators "-"
        and "+", indicating the factors of the experiment

    Returns
    -------
    H : 2d-array
        A m-by-n matrix, the fractional factorial design. m is 2^k, where k
        is the number of letters in ``gen``, and n is the total number of
        entries in ``gen``.

    Notes
    -----
    In ``gen`` we define the main factors of the experiment and the factors
    whose levels are the products of the main factors. For example, if

        gen = "a b ab"

    then "a" and "b" are the main factors, while the 3rd factor is the product
    of the first two. If we input uppercase letters in ``gen``, we get the same
    result. We can also use the operators "+" and "-" in ``gen``.

    For example, if

        gen = "a b -ab"

    then the 3rd factor is the opposite of the product of "a" and "b".

    The output matrix includes the two level full factorial design, built by
    the main factors of ``gen``, and the products of the main factors. The
    columns of ``H`` follow the sequence of ``gen``.

    For example, if

        gen = "a b ab c"

    then columns H[:, 0], H[:, 1], and H[:, 3] include the two level full
    factorial design and H[:, 2] includes the products of the main factors.

    Examples
    --------
    ::

        >>> samFracFact("a b ab")
        array([[-1., -1.,  1.],
               [ 1., -1., -1.],
               [-1.,  1., -1.],
               [ 1.,  1.,  1.]])

        >>> samFracFact("A B AB")
        array([[-1., -1.,  1.],
               [ 1., -1., -1.],
               [-1.,  1., -1.],
               [ 1.,  1.,  1.]])

        >>> samFracFact("a b -ab c +abc")
        array([[-1., -1., -1., -1., -1.],
               [ 1., -1.,  1., -1.,  1.],
               [-1.,  1.,  1., -1.,  1.],
               [ 1.,  1., -1., -1., -1.],
               [-1., -1., -1.,  1.,  1.],
               [ 1., -1.,  1.,  1., -1.],
               [-1.,  1.,  1.,  1., -1.],
               [ 1.,  1., -1.,  1.,  1.]])

    """
    # Recognize letters and combinations
    A = [item for item in re.split('\-?\s?\+?', gen) if item]  # remove empty strings
    C = [len(item) for item in A]

    # Indices of single letters (main factors)
    I = [i for i, item in enumerate(C) if item == 1]

    # Indices of letter combinations (we need them to fill out H2 properly).
    J = [i for i, item in enumerate(C) if item != 1]

    # Check if there are "-" or "+" operators in gen
    U = [item for item in gen.split(' ') if item]  # remove empty strings

    # If R1 is either None or not, the result is not changed, since it is a
    # multiplication of 1.
    R1 = _grep(U, '+')
    R2 = _grep(U, '-')

    # Fill in design with two level factorial design
    H1 = samFF2n(len(I))
    H = np.zeros((H1.shape[0], len(C)))
    H[:, I] = H1

    # Recognize combinations and fill in the rest of matrix H2 with the proper
    # products
    for k in J:
        # For lowercase letters
        xx = np.array([ord(c) for c in A[k]]) - 97

        # For uppercase letters
        if np.any(xx < 0):
            xx = np.array([ord(c) for c in A[k]]) - 65

        H[:, k] = np.prod(H1[:, xx], axis=1)

    # Update design if gen includes "-" operator
    if R2:
        H[:, R2] *= -1

    # Return the fractional factorial design
    return H


def _grep(haystack, needle):
    try:
        haystack[0]
    except (TypeError, AttributeError):
        return [0] if needle in haystack else []
    else:
        locs = []
        for idx, item in enumerate(haystack):
            if needle in item:
                locs += [idx]
        return locs

# Copyright 2016 <NAME>
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#    http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Author: <NAME> <<EMAIL>>
# License: Apache License, Version 2.0
# Create: 2016-12-02

import re

import numpy as np

def samFullFact(levels):
    """Create a general full-factorial design

    :param levels: An array of integers that indicate the number of levels of each input
           design factor.
    :returns: The design matrix with coded levels 0 to k-1 for a k-level factor

    This code was originally published by the following individuals for use with
    Scilab:

    - Copyright (C) 2012 - 2013 - <NAME>
    - Copyright (C) 2012 - <NAME>
    - Copyright (C) 2010 - 2011 - INRIA - <NAME>
    - Copyright (C) 2009 - <NAME>
    - Copyright (C) 2009 - CEA - <NAME>

    website: forge.scilab.org/index.php/p/scidoe/sourcetree/master/macros

    Much thanks goes to these individuals. It has been converted to Python by
    Abraham Lee.

    :Example:

    >>> samFullFact([2, 4, 3])
    array([[ 0.,  0.,  0.],
           [ 1.,  0.,  0.],
           [ 0.,  1.,  0.],
           [ 1.,  1.,  0.],
           [ 0.,  2.,  0.],
           [ 1.,  2.,  0.],
           [ 0.,  3.,  0.],
           [ 1.,  3.,  0.],
           [ 0.,  0.,  1.],
           [ 1.,  0.,  1.],
           [ 0.,  1.,  1.],
           [ 1.,  1.,  1.],
           [ 0.,  2.,  1.],
           [ 1.,  2.,  1.],
           [ 0.,  3.,  1.],
           [ 1.,  3.,  1.],
           [ 0.,  0.,  2.],
           [ 1.,  0.,  2.],
           [ 0.,  1.,  2.],
           [ 1.,  1.,  2.],
           [ 0.,  2.,  2.],
           [ 1.,  2.,  2.],
           [ 0.,  3.,  2.],
           [ 1.,  3.,  2.]])

    """
    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


################################################################################

def samFF2n(n):
    """
    Create a 2-Level full-factorial design

    Parameters
    ----------
    n : int
        The number of factors in the design.

    Returns
    -------
    mat : 2d-array
        The design matrix with coded levels -1 and 1

    Example
    -------
    ::

        >>> samFF2n(3)
        array([[-1., -1., -1.],
               [ 1., -1., -1.],
               [-1.,  1., -1.],
               [ 1.,  1., -1.],
               [-1., -1.,  1.],
               [ 1., -1.,  1.],
               [-1.,  1.,  1.],
               [ 1.,  1.,  1.]])

    """
    return 2 * samFullFact([2] * n) - 1


################################################################################

def samFracFact(gen):
    """
    Create a 2-level fractional-factorial design with a generator string.

    Parameters
    ----------
    gen : str
        A string, consisting of lowercase, uppercase letters or operators "-"
        and "+", indicating the factors of the experiment

    Returns
    -------
    H : 2d-array
        A m-by-n matrix, the fractional factorial design. m is 2^k, where k
        is the number of letters in ``gen``, and n is the total number of
        entries in ``gen``.

    Notes
    -----
    In ``gen`` we define the main factors of the experiment and the factors
    whose levels are the products of the main factors. For example, if

        gen = "a b ab"

    then "a" and "b" are the main factors, while the 3rd factor is the product
    of the first two. If we input uppercase letters in ``gen``, we get the same
    result. We can also use the operators "+" and "-" in ``gen``.

    For example, if

        gen = "a b -ab"

    then the 3rd factor is the opposite of the product of "a" and "b".

    The output matrix includes the two level full factorial design, built by
    the main factors of ``gen``, and the products of the main factors. The
    columns of ``H`` follow the sequence of ``gen``.

    For example, if

        gen = "a b ab c"

    then columns H[:, 0], H[:, 1], and H[:, 3] include the two level full
    factorial design and H[:, 2] includes the products of the main factors.

    Examples
    --------
    ::

        >>> samFracFact("a b ab")
        array([[-1., -1.,  1.],
               [ 1., -1., -1.],
               [-1.,  1., -1.],
               [ 1.,  1.,  1.]])

        >>> samFracFact("A B AB")
        array([[-1., -1.,  1.],
               [ 1., -1., -1.],
               [-1.,  1., -1.],
               [ 1.,  1.,  1.]])

        >>> samFracFact("a b -ab c +abc")
        array([[-1., -1., -1., -1., -1.],
               [ 1., -1.,  1., -1.,  1.],
               [-1.,  1.,  1., -1.,  1.],
               [ 1.,  1., -1., -1., -1.],
               [-1., -1., -1.,  1.,  1.],
               [ 1., -1.,  1.,  1., -1.],
               [-1.,  1.,  1.,  1., -1.],
               [ 1.,  1., -1.,  1.,  1.]])

    """
    # Recognize letters and combinations
    A = [item for item in re.split('\-?\s?\+?', gen) if item]  # remove empty strings
    C = [len(item) for item in A]

    # Indices of single letters (main factors)
    I = [i for i, item in enumerate(C) if item == 1]

    # Indices of letter combinations (we need them to fill out H2 properly).
    J = [i for i, item in enumerate(C) if item != 1]

    # Check if there are "-" or "+" operators in gen
    U = [item for item in gen.split(' ') if item]  # remove empty strings

    # If R1 is either None or not, the result is not changed, since it is a
    # multiplication of 1.
    R1 = _grep(U, '+')
    R2 = _grep(U, '-')

    # Fill in design with two level factorial design
    H1 = samFF2n(len(I))
    H = np.zeros((H1.shape[0], len(C)))
    H[:, I] = H1

    # Recognize combinations and fill in the rest of matrix H2 with the proper
    # products
    for k in J:
        # For lowercase letters
        xx = np.array([ord(c) for c in A[k]]) - 97

        # For uppercase letters
        if np.any(xx < 0):
            xx = np.array([ord(c) for c in A[k]]) - 65

        H[:, k] = np.prod(H1[:, xx], axis=1)

    # Update design if gen includes "-" operator
    if R2:
        H[:, R2] *= -1

    # Return the fractional factorial design
    return H


def _grep(haystack, needle):
    try:
        haystack[0]
    except (TypeError, AttributeError):
        return [0] if needle in haystack else []
    else:
        locs = []
        for idx, item in enumerate(haystack):
            if needle in item:
                locs += [idx]
        return locs
# Copyright 2016 <NAME> # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # #    http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Author: <NAME> <<EMAIL>> # License: Apache License, Version 2.0 # Create: 2016-12-02 Create a general full-factorial design      :param levels: An array of integers that indicate the number of levels of each input            design factor.     :returns: The design matrix with coded levels 0 to k-1 for a k-level factor      This code was originally published by the following individuals for use with     Scilab:      - Copyright (C) 2012 - 2013 - <NAME>     - Copyright (C) 2012 - <NAME>     - Copyright (C) 2010 - 2011 - INRIA - <NAME>     - Copyright (C) 2009 - <NAME>     - Copyright (C) 2009 - CEA - <NAME>      website: forge.scilab.org/index.php/p/scidoe/sourcetree/master/macros      Much thanks goes to these individuals. It has been converted to Python by     Abraham Lee.      :Example:      >>> samFullFact([2, 4, 3])     array([[ 0.,  0.,  0.],            [ 1.,  0.,  0.],            [ 0.,  1.,  0.],            [ 1.,  1.,  0.],            [ 0.,  2.,  0.],            [ 1.,  2.,  0.],            [ 0.,  3.,  0.],            [ 1.,  3.,  0.],            [ 0.,  0.,  1.],            [ 1.,  0.,  1.],            [ 0.,  1.,  1.],            [ 1.,  1.,  1.],            [ 0.,  2.,  1.],            [ 1.,  2.,  1.],            [ 0.,  3.,  1.],            [ 1.,  3.,  1.],            [ 0.,  0.,  2.],            [ 1.,  0.,  2.],            [ 0.,  1.,  2.],            [ 1.,  1.,  2.],            [ 0.,  2.,  2.],            [ 1.,  2.,  2.],            [ 0.,  3.,  2.],            [ 1.,  3.,  2.]]) # number of factors # number of trial conditions ################################################################################ Create a 2-Level full-factorial design      Parameters     ----------     n : int         The number of factors in the design.      Returns     -------     mat : 2d-array         The design matrix with coded levels -1 and 1      Example     -------     ::          >>> samFF2n(3)         array([[-1., -1., -1.],                [ 1., -1., -1.],                [-1.,  1., -1.],                [ 1.,  1., -1.],                [-1., -1.,  1.],                [ 1., -1.,  1.],                [-1.,  1.,  1.],                [ 1.,  1.,  1.]]) ################################################################################ Create a 2-level fractional-factorial design with a generator string.      Parameters     ----------     gen : str         A string, consisting of lowercase, uppercase letters or operators "-"         and "+", indicating the factors of the experiment      Returns     -------     H : 2d-array         A m-by-n matrix, the fractional factorial design. m is 2^k, where k         is the number of letters in ``gen``, and n is the total number of         entries in ``gen``.      Notes     -----     In ``gen`` we define the main factors of the experiment and the factors     whose levels are the products of the main factors. For example, if          gen = "a b ab"      then "a" and "b" are the main factors, while the 3rd factor is the product     of the first two. If we input uppercase letters in ``gen``, we get the same     result. We can also use the operators "+" and "-" in ``gen``.      For example, if          gen = "a b -ab"      then the 3rd factor is the opposite of the product of "a" and "b".      The output matrix includes the two level full factorial design, built by     the main factors of ``gen``, and the products of the main factors. The     columns of ``H`` follow the sequence of ``gen``.      For example, if          gen = "a b ab c"      then columns H[:, 0], H[:, 1], and H[:, 3] include the two level full     factorial design and H[:, 2] includes the products of the main factors.      Examples     --------     ::          >>> samFracFact("a b ab")         array([[-1., -1.,  1.],                [ 1., -1., -1.],                [-1.,  1., -1.],                [ 1.,  1.,  1.]])          >>> samFracFact("A B AB")         array([[-1., -1.,  1.],                [ 1., -1., -1.],                [-1.,  1., -1.],                [ 1.,  1.,  1.]])          >>> samFracFact("a b -ab c +abc")         array([[-1., -1., -1., -1., -1.],                [ 1., -1.,  1., -1.,  1.],                [-1.,  1.,  1., -1.,  1.],                [ 1.,  1., -1., -1., -1.],                [-1., -1., -1.,  1.,  1.],                [ 1., -1.,  1.,  1., -1.],                [-1.,  1.,  1.,  1., -1.],                [ 1.,  1., -1.,  1.,  1.]]) # Recognize letters and combinations # remove empty strings # Indices of single letters (main factors) # Indices of letter combinations (we need them to fill out H2 properly). # Check if there are "-" or "+" operators in gen # remove empty strings # If R1 is either None or not, the result is not changed, since it is a # multiplication of 1. # Fill in design with two level factorial design # Recognize combinations and fill in the rest of matrix H2 with the proper # products # For lowercase letters # For uppercase letters # Update design if gen includes "-" operator # Return the fractional factorial design