Source code for hybparsimony.lhs.base.geneticLHS

# -*- coding: utf-8 -*-

import numpy as np
from hybparsimony.lhs import randomLHS_int
from hybparsimony.lhs.util import calculateSOptimal, calculateDistance, runifint, runif_std, findorder_zero

[docs]def geneticLHS(n, k, pop=100, gen=4, pMut=0.1, criterium="S", seed=None): r"""Latin Hypercube Sampling with a Genetic Algorithm Draws a Latin Hypercube Sample from a set of uniform distributions for use in creating a LatinHypercube Design. This function attempts to optimize the sample with respect to the S optimalitycriterion through a genetic type algorithm. Parameters ---------- n : int The number of rows or samples. k : int The number of columns or parameters/variables. pop : int, optional The number of designs in the initial population. Default `100`. gen : int, optional The number of generations over which the algorithm is applied. Default `4`. pMut : float, optional The probability with which a mutation occurs in a column of the progeny. Default `0.1`. criterium : str, {'S', 'Maximin'}, optional The optimality criterium of the algorithm. Default is `'S'`. Maximin is also supported. seed : int, optional Random seed. Default `None`. Returns ------- numpy.array A `numpy.array` of `float` with shape `(n, k)`. """ if n < 1 or k < 1: raise Exception("nsamples are less than 1 (n) or nparameters less than 1 (k)") m_n = n m_k = k result = np.empty((m_n, m_k)).astype(np.double) if gen < 1 or pop < 1: raise Exception("pop, and gen should be integers greater than 0") m_pop = pop m_gen = gen if pMut <= 0 or pMut >= 1: raise Exception("pMut should be between 0 and 1") if m_pop % 2 != 0: raise Exception("pop should be an even number") if seed: np.random.seed(seed) A = np.empty(m_pop).astype(object) for i in range(m_pop): # // fill A with random hypercubes A[i] = randomLHS_int(m_n, m_k) for _ in range(m_gen): B = np.empty(m_pop).astype(np.double) for i in range(m_pop): if criterium == "S": B[i] = int(calculateSOptimal(A[i])) elif criterium == "Maximin": dist = calculateDistance(A[i]).astype(np.int32)[0] # // we want to find the minimum distance element, but there are zeros in the dist matrix distnonzero = [] for mit in dist: if mit > 0.0: distnonzero.append(mit) distnonzero = np.array(distnonzero) it = np.min(distnonzero) if distnonzero.shape[0]>0 else 0.0 B[i] = it else: raise Exception(f"Criterium not recognized: S and Maximin are available: {criterium} was provided.") # // H is used as an index on vector of matrices, A, so it should be using zero based order H = findorder_zero(B) posit = int(np.max(B) - B[0]) J = np.empty(m_pop).astype(object) # // the first half of the next population gets the best hypercube from the first population for i in range(int(m_pop / 2)): J[i] = A[posit] if m_pop / 2 == 1: break # // the second half of the next population gets the decreasingly best hypercubes from the first population for i in range(int(m_pop / 2)): J[int(i + m_pop / 2)] = A[int(H[i])] # // skip the first best hypercube in the next generation # // in the others in the first half of the population, randomly permute a column from the second half into the first half for i in range(1, int(m_pop / 2)): temp1 = runifint(0, m_k-1).astype(np.int32) temp2 = runifint(0, m_k-1).astype(np.int32) for irow in range(m_n): J[i][irow, temp1] = J[int(i + m_pop / 2)][irow, temp2] # // for the second half of the population, randomly permute a column from the best hypercube for i in range(int(m_pop / 2), m_pop): temp1 = runifint(0, m_k-1).astype(np.int32) temp2 = runifint(0, m_k-1).astype(np.int32) for irow in range(m_n): J[i][irow, temp1] = A[posit][irow, temp2] # // randomly exchange two numbers in pMut percent of columns for i in range(1, m_pop): y = runif_std(m_k) for j in range(m_k): if y[j] <= pMut: z = runifint(0, m_n-1, 2).astype(np.int32) a = J[i][z[0], j] b = J[i][z[1], j] J[i][z[0], j] = b J[i][z[1], j] = a # // put all of J back into A to start the next round A = J eps = runif_std(m_n * m_k) count = 0 for j in range(m_k): for i in range(m_n): result[i,j] = (np.double(J[0][i,j]) - 1.0 + eps[count]) / np.double(m_n) count += 1 return result