def test_four_fac_linear(self): """Tests a 4 factor optimal design with a linear model. This should select 5 corners of the hypercube. """ optimal_data = build_optimal(4, order=ModelOrder.linear) model = make_model(optimal_data.columns, ModelOrder.linear, True) X = dmatrix(model, optimal_data) XtXi = np.linalg.inv(np.dot(np.transpose(X), X)) d = np.linalg.det(XtXi) self.assertAlmostEqual(d, 4.34028E-4, delta=1e-3)
def test_optimal(self): """Tests a simple 2 factor optimal design This is a 6 run design using a quadratic model, from the Meyer and Nachtsheim 1995 paper. """ optimal_data = build_optimal(2, order=ModelOrder.quadratic) model = make_model(optimal_data.columns, ModelOrder.quadratic, True) X = dmatrix(model, optimal_data) XtXi = np.linalg.inv(np.dot(np.transpose(X), X)) d = np.linalg.det(XtXi) self.assertAlmostEqual(d, 3.73506e-3, delta=1e-3)
def test_linear(cls): """Checks the optimality of linear simplex lattice designs.""" answer_d = [ 1 ] * 29 actual_d = [] order = ModelOrder.linear for i in range(2, 31): design = build_simplex_lattice(i, order) model = "-1 + " + make_model(design.columns, order, include_powers=False) x_matrix = patsy.dmatrix(model, design, return_type="dataframe") actual_d.append(det_xtxi(x_matrix, use_log=False)) np.testing.assert_allclose(answer_d, actual_d, rtol=1e-5)
def test_cubic(cls): """Checks the optimality of cubic simplex lattice designs.""" answer_d = [ 2.2096470973347775, 13.228395211331954, 39.6547285196038, 88.08632361788068, 165.1201784728033, 277.35271609131553, 431.37986869540055, 633.7971466915958, 891.1996956551948, 1210.1823437751189, 1597.3396416379908, 2059.265895808832, ] actual_d = [] order = ModelOrder.cubic for i in range(2, 14): design = build_simplex_lattice(i, order) model = "-1 + " + make_model(design.columns, order, include_powers=False) x_matrix = patsy.dmatrix(model, design, return_type="dataframe") actual_d.append(det_xtxi(x_matrix, use_log=True)) np.testing.assert_allclose(answer_d, actual_d, rtol=1e-4)
def build_optimal(factor_count, **kwargs): r"""Builds an optimal design. This uses the Coordinate-Exchange algorithm from Meyer and Nachtsheim 1995 :cite:`MeyerNachtsheim1995`. :param factor_count: The number of factors to build for. :type factor_count: integer :Keyword Arguments: * **order** (:class:`ModelOrder <dexpy.model.ModelOrder>`) -- \ Builds a design for this order model. \ Mutually exclusive with the **model** parameter. * **model** (`patsy formula <https://patsy.readthedocs.io>`_) -- \ Builds a design for this model formula. \ Mutually exclusive with the **order** parameter. * **run_count** (`integer`) -- \ The number of runs to use in the design. This must be equal\ to or greater than the rank of the model. """ factor_names = dexpy.design.get_factor_names(factor_count) model = kwargs.get('model', None) if model is None: order = kwargs.get('order', ModelOrder.quadratic) model = make_model(factor_names, order, True) run_count = kwargs.get('run_count', 0) # first generate a valid starting design (design, X) = bootstrap(factor_names, model, run_count) functions = [] for _, subterms in X.design_info.term_codings.items(): sub_funcs = [] for subterm in subterms: for factor in subterm.factors: eval_code = X.design_info.factor_infos[factor].state[ 'eval_code'] if eval_code[0] == 'I': eval_code = eval_code[1:] sub_funcs.append(eval_code) if not sub_funcs: functions.append("1") # intercept else: functions.append("*".join(sub_funcs)) full_func = "[" + ",".join(functions) + "]" code = compile(full_func, "<string>", "eval") steps = 12 low = -1 high = 1 XtXi = np.linalg.inv(np.dot(np.transpose(X), X)) (_, d_optimality) = np.linalg.slogdet(XtXi) design_improved = True swaps = 0 evals = 0 min_change = 1.0 + np.finfo(float).eps while design_improved: design_improved = False for i in range(0, len(design)): design_point = design.iloc[i] for f in range(0, factor_count): original_value = design_point[f] original_expanded = X[i] best_step = -1 best_point = [] best_change = min_change for s in range(0, steps): design_point[f] = low + ((high - low) / (steps - 1)) * s new_point = expand_point(design_point, code) change_in_d = delta(X, XtXi, i, new_point) evals += 1 if change_in_d - best_change > np.finfo(float).eps: best_point = new_point best_step = s best_change = change_in_d if best_step >= 0: # update X with the best point design_point[f] = low + ((high - low) / (steps - 1)) * best_step XtXi = update(XtXi, best_point, X[i]) X[i] = best_point d_optimality -= math.log(best_change) design_improved = True swaps += 1 else: # restore the original design point value design_point[f] = original_value X[i] = original_expanded logging.info("{} swaps evaluated, {} executed ({:.2f}%)".format( evals, swaps, 100 * (swaps / evals))) return design
from patsy import dmatrix import numpy as np import seaborn as sns import matplotlib.pyplot as plt reaction_design = dexpy.optimal.build_optimal(2, order=ModelOrder.quadratic) column_names = ['time', 'temp'] actual_lows = {'time': 40, 'temp': 80} actual_highs = {'time': 50, 'temp': 90} reaction_design.columns = column_names print(coded_to_actual(reaction_design, actual_lows, actual_highs)) quad_model = make_model(reaction_design.columns, ModelOrder.quadratic) X = dmatrix(quad_model, reaction_design) XtX = np.dot(np.transpose(X), X) d = np.linalg.det(XtX) print("|(X'X)| for quadratic 2 factor optimal design: {}".format(d)) fg = sns.lmplot('time', 'temp', data=reaction_design, fit_reg=False) ax = fg.axes[0, 0] ax.set_xticks([-1, 0, 1]) ax.set_xticklabels(['40 min', '45 min', '50 min']) ax.set_yticks([-1, 0, 1]) ax.set_yticklabels(['80C', '85C', '90C']) plt.show() reaction_design = dexpy.optimal.build_optimal(2,