def minimize_fixture(self): """ Set up the parameters, model, constraints for the minimization fits. These tests used to purposefully use unamed variable, however this has been changed as this feature is now being depricated in a future version of Symfit. """ x = Parameter('x', value=-1.0) y = Parameter('y', value=1.0) self.x = x self.y = y self.model = Model(2 * x * y + 2 * x - x ** 2 - 2 * y ** 2) self.constraints = [ Ge(y - 1, 0), # y - 1 >= 0, Eq(x**3 - y, 0), # x**3 - y == 0, ] self.cons = ( {'type': 'eq', 'fun': lambda x: np.array([x[0]**3 - x[1]]), 'jac': lambda x: np.array([3.0 * (x[0]**2.0), -1.0])}, {'type': 'ineq', 'fun': lambda x: np.array([x[1] - 1]), 'jac': lambda x: np.array([0.0, 1.0])} )
def test_minimize(): """ Tests maximizing a function with and without constraints, taken from the scipy `minimize` tutorial. Compare the symfit result with the scipy result. https://docs.scipy.org/doc/scipy-0.18.1/reference/tutorial/optimize.html#constrained-minimization-of-multivariate-scalar-functions-minimize """ x = Parameter(value=-1.0) y = Parameter(value=1.0) # Use an unnamed Variable on purpose to test the auto-generation of names. model = Model(2 * x * y + 2 * x - x ** 2 - 2 * y ** 2) constraints = [ Ge(y - 1, 0), # y - 1 >= 0, Eq(x**3 - y, 0), # x**3 - y == 0, ] def func(x, sign=1.0): """ Objective function """ return sign*(2*x[0]*x[1] + 2*x[0] - x[0]**2 - 2*x[1]**2) def func_deriv(x, sign=1.0): """ Derivative of objective function """ dfdx0 = sign*(-2*x[0] + 2*x[1] + 2) dfdx1 = sign*(2*x[0] - 4*x[1]) return np.array([dfdx0, dfdx1]) cons = ( {'type': 'eq', 'fun': lambda x: np.array([x[0]**3 - x[1]]), 'jac': lambda x: np.array([3.0*(x[0]**2.0), -1.0])}, {'type': 'ineq', 'fun': lambda x: np.array([x[1] - 1]), 'jac': lambda x: np.array([0.0, 1.0])} ) # Unconstrained fit res = minimize(func, [-1.0, 1.0], args=(-1.0,), jac=func_deriv, method='BFGS', options={'disp': False}) fit = Fit(model=-model) assert isinstance(fit.objective, MinimizeModel) assert isinstance(fit.minimizer, BFGS) fit_result = fit.execute() assert fit_result.value(x) == pytest.approx(res.x[0], 1e-6) assert fit_result.value(y) == pytest.approx(res.x[1], 1e-6) # Same test, but with constraints in place. res = minimize(func, [-1.0, 1.0], args=(-1.0,), jac=func_deriv, constraints=cons, method='SLSQP', options={'disp': False}) fit = Fit(-model, constraints=constraints) assert fit.constraints[0].constraint_type == Ge assert fit.constraints[1].constraint_type == Eq fit_result = fit.execute() assert fit_result.value(x) == pytest.approx(res.x[0], 1e-6) assert fit_result.value(y) == pytest.approx(res.x[1], 1e-6)
def test_constraint_types(): x = Parameter('x', value=-1.0) y = Parameter('y', value=1.0) z = Variable('z') model = Model({z: 2*x*y + 2*x - x**2 - 2*y**2}) # These types are not allowed constraints. for relation in [Lt, Gt, Ne]: with pytest.raises(ModelError): Fit(model, constraints=[relation(x, y)]) # Should execute without problems. for relation in [Eq, Ge, Le]: Fit(model, constraints=[relation(x, y)]) fit = Fit(model, constraints=[Le(x, y)]) # Le should be transformed to Ge assert fit.constraints[0].constraint_type is Ge # Redo the standard test as a Le constraints = [ Le(- y + 1, 0), # y - 1 >= 0, Eq(x**3 - y, 0), # x**3 - y == 0, ] std_constraints = [ Ge(y - 1, 0), # y - 1 >= 0, Eq(x**3 - y, 0), # x**3 - y == 0, ] fit = Fit(-model, constraints=constraints) std_fit = Fit(-model, constraints=std_constraints) assert fit.constraints[0].constraint_type == Ge assert fit.constraints[1].constraint_type == Eq assert fit.constraints[0].params == [x, y] assert fit.constraints[1].params == [x, y] assert fit.constraints[0].jacobian_model.params == [x, y] assert fit.constraints[1].jacobian_model.params == [x, y] assert fit.constraints[0].hessian_model.params == [x, y] assert fit.constraints[1].hessian_model.params == [x, y] assert fit.constraints[0].__signature__ == fit.constraints[1].__signature__ fit_result = fit.execute() std_result = std_fit.execute() assert fit_result.value(x) == pytest.approx(std_result.value(x)) assert fit_result.value(y) == pytest.approx(std_result.value(y))
def test_constrainedminimizers(): """ Compare the different constrained minimizers, to make sure all support constraints, and converge to the same answer. """ minimizers = list(subclasses(ScipyConstrainedMinimize)) x = Parameter('x', value=-1.0) y = Parameter('y', value=1.0) z = Variable('z') model = Model({z: 2 * x * y + 2 * x - x**2 - 2 * y**2}) # First we try an unconstrained fit results = [] for minimizer in minimizers: fit = Fit(-model, minimizer=minimizer) assert isinstance(fit.objective, MinimizeModel) fit_result = fit.execute(tol=1e-15) results.append(fit_result) # Compare the parameter values. for r1, r2 in zip(results[:-1], results[1:]): assert r1.value(x) == pytest.approx(r2.value(x), 1e-6) assert r1.value(y) == pytest.approx(r2.value(y), 1e-6) assert r1.covariance_matrix == pytest.approx(r2.covariance_matrix) constraints = [ Ge(y - 1, 0), # y - 1 >= 0, Eq(x**3 - y, 0), # x**3 - y == 0, ] # Constrained fit. results = [] for minimizer in minimizers: if minimizer is COBYLA: # COBYLA only supports inequality. continue fit = Fit(-model, constraints=constraints, minimizer=minimizer) fit_result = fit.execute(tol=1e-15) results.append(fit_result) for r1, r2 in zip(results[:-1], results[1:]): assert r1.value(x) == pytest.approx(r2.value(x), 1e-6) assert r1.value(y) == pytest.approx(r2.value(y), 1e-6) assert r1.covariance_matrix == pytest.approx(r2.covariance_matrix)
def test_constraint_types(self): x = Parameter(-1.0) y = Parameter(1.0) z = Variable() model = {z: 2 * x * y + 2 * x - x**2 - 2 * y**2} # These types are not allowed constraints. for relation in [Lt, Gt, Ne]: with self.assertRaises(ModelError): Maximize(model, constraints=[relation(x, y)]) # Should execute without problems. for relation in [Eq, Ge, Le]: Maximize(model, constraints=[relation(x, y)]) fit = Maximize(model, constraints=[Le(x, y)]) # Le should be transformed to Ge self.assertIs(fit.constraints[0].constraint_type, Ge) # Redo the standard test as a Le constraints = [ Le(-y + 1, 0), # y - 1 >= 0, Eq(x**3 - y, 0), # x**3 - y == 0, ] std_constraints = [ Ge(y - 1, 0), # y - 1 >= 0, Eq(x**3 - y, 0), # x**3 - y == 0, ] fit = Maximize(model, constraints=constraints) std_fit = Maximize(model, constraints=std_constraints) self.assertEqual(fit.constraints[0].constraint_type, Ge) self.assertEqual(fit.constraints[1].constraint_type, Eq) fit_result = fit.execute() std_result = std_fit.execute() self.assertAlmostEqual(fit_result.value(x), std_result.value(x)) self.assertAlmostEqual(fit_result.value(y), std_result.value(y))
def test_pickle(): """ Test the picklability of the different minimizers. """ # Create test data xdata = np.linspace(0, 100, 100) # From 0 to 100 in 100 steps a_vec = np.random.normal(15.0, scale=2.0, size=xdata.shape) b_vec = np.random.normal(100, scale=2.0, size=xdata.shape) ydata = a_vec * xdata + b_vec # Point scattered around the line 5 * x + 105 # Normal symbolic fit a = Parameter('a', value=0, min=0.0, max=1000) b = Parameter('b', value=0, min=0.0, max=1000) x, y = variables('x, y') # Make a set of all ScipyMinimizers, and add a chained minimizer. scipy_minimizers = list(subclasses(ScipyMinimize)) chained_minimizer = (DifferentialEvolution, BFGS) scipy_minimizers.append(chained_minimizer) constrained_minimizers = subclasses(ScipyConstrainedMinimize) # Test for all of them if they can be pickled. for minimizer in scipy_minimizers: if minimizer in constrained_minimizers: constraints = [Ge(b, a)] else: constraints = [] model = CallableNumericalModel({y: f}, independent_vars=[x], params=[a, b]) fit = Fit(model, x=xdata, y=ydata, minimizer=minimizer, constraints=constraints) if minimizer is not MINPACK: assert isinstance(fit.objective, LeastSquares) assert isinstance(fit.minimizer.objective, LeastSquares) else: assert isinstance(fit.objective, VectorLeastSquares) assert isinstance(fit.minimizer.objective, VectorLeastSquares) fit = fit.minimizer # Just check if the minimizer pickles dump = pickle.dumps(fit) pickled_fit = pickle.loads(dump) problematic_attr = [ 'objective', '_pickle_kwargs', 'wrapped_objective', 'constraints', 'wrapped_constraints', 'local_minimizer', 'minimizers' ] for key, value in fit.__dict__.items(): new_value = pickled_fit.__dict__[key] try: assert value == new_value except AssertionError as err: if key not in problematic_attr: raise err # These attr are new instances, and therefore do not # pass an equality test. All we can do is see if they # are at least the same type. if isinstance(value, (list, tuple)): for val1, val2 in zip(value, new_value): assert isinstance(val1, val2.__class__) if key == 'constraints': assert val1.model.constraint_type == val2.model.constraint_type assert list( val1.model.model_dict.values())[0] == list( val2.model.model_dict.values())[0] assert val1.model.independent_vars == val2.model.independent_vars assert val1.model.params == val2.model.params assert val1.model.__signature__ == val2.model.__signature__ elif key == 'wrapped_constraints': if isinstance(val1, dict): assert val1['type'] == val2['type'] assert set(val1.keys()) == set(val2.keys()) elif isinstance(val1, NonlinearConstraint): # For trust-ncg we manually check if # their dicts are equal, because no # __eq__ is implemented on # NonLinearConstraint assert len(val1.__dict__) == len(val2.__dict__) for key in val1.__dict__: try: assert val1.__dict__[ key] == val2.__dict__[key] except AssertionError: assert isinstance( val1.__dict__[key], val2.__dict__[key].__class__) else: raise NotImplementedError( 'No such constraint type is known.') elif key == '_pickle_kwargs': FitResults._array_safe_dict_eq(value, new_value) else: assert isinstance(new_value, value.__class__) assert set(fit.__dict__.keys()) == set(pickled_fit.__dict__.keys()) # Test if we converge to the same result. np.random.seed(2) res_before = fit.execute() np.random.seed(2) res_after = pickled_fit.execute() assert FitResults._array_safe_dict_eq(res_before.__dict__, res_after.__dict__)
def test_minimize(self): """ Tests maximizing a function with and without constraints, taken from the scipy `minimize` tutorial. Compare the symfit result with the scipy result. https://docs.scipy.org/doc/scipy-0.18.1/reference/tutorial/optimize.html#constrained-minimization-of-multivariate-scalar-functions-minimize """ x = Parameter(-1.0) y = Parameter(1.0) z = Variable() model = {z: 2 * x * y + 2 * x - x**2 - 2 * y**2} constraints = [ Ge(y - 1, 0), # y - 1 >= 0, Eq(x**3 - y, 0), # x**3 - y == 0, ] def func(x, sign=1.0): """ Objective function """ return sign * (2 * x[0] * x[1] + 2 * x[0] - x[0]**2 - 2 * x[1]**2) def func_deriv(x, sign=1.0): """ Derivative of objective function """ dfdx0 = sign * (-2 * x[0] + 2 * x[1] + 2) dfdx1 = sign * (2 * x[0] - 4 * x[1]) return np.array([dfdx0, dfdx1]) cons = ({ 'type': 'eq', 'fun': lambda x: np.array([x[0]**3 - x[1]]), 'jac': lambda x: np.array([3.0 * (x[0]**2.0), -1.0]) }, { 'type': 'ineq', 'fun': lambda x: np.array([x[1] - 1]), 'jac': lambda x: np.array([0.0, 1.0]) }) # Unconstrained fit res = minimize(func, [-1.0, 1.0], args=(-1.0, ), jac=func_deriv, method='SLSQP', options={'disp': False}) fit = Maximize(model) fit_result = fit.execute() self.assertAlmostEqual(fit_result.value(x), res.x[0]) self.assertAlmostEqual(fit_result.value(y), res.x[1]) # Same test, but with constraints in place. res = minimize(func, [-1.0, 1.0], args=(-1.0, ), jac=func_deriv, constraints=cons, method='SLSQP', options={'disp': False}) fit = Maximize(model, constraints=constraints) self.assertEqual(fit.constraints[0].constraint_type, Ge) self.assertEqual(fit.constraints[1].constraint_type, Eq) fit_result = fit.execute() self.assertAlmostEqual(fit_result.value(x), res.x[0]) self.assertAlmostEqual(fit_result.value(y), res.x[1])