def wrapper_minimization(HM, starting_pars, parameterIndex=0, problem='hill_min', list_of_constraints=None):
    # this function takes as input a Hill Model, initial parameter values and the index of the parameter and maximises
    # the hysteresis parameters
    # INPUTS
    # HM        Hill model
    # starting_pars     starting parameters for minimization
    # parameterIndex=1  which parameter is the spacial one in the problem
    # problem='hysteresis'  what problem is to solve
    # list_of_constraints   are there any special constraints? Default = 'hysteresis' and 'norm_param'

    if list_of_constraints is None:
        list_of_constraints = ['saddle_node', 'norm_param']
    SN = SaddleNode(HM)

    # find starting point
    jSearchNodes = np.linspace(starting_pars[parameterIndex] / 10, 10 * starting_pars[parameterIndex], 25)
    starting_values = SN.find_saddle_node(parameterIndex, starting_pars, freeParameterValues=jSearchNodes,
                                          flag_return=1)

    # restructuring starting values in the right format
    # the right format: [lambda1, x1, v1, lambda2, x2, v2, other_pars]
    if starting_values.shape[0] > 1:
        raise Exception("We found more than 1 saddle node: " + str(starting_values.shape[0]))
    elif starting_values.shape[0] < 1:
        raise Exception("We couldn't find 1 saddle node")
    non_special_starting_pars = ezcat(starting_pars[:parameterIndex], starting_pars[parameterIndex + 1:])
    all_starting_values = ezcat(starting_values[0, :], starting_values[1, :], non_special_starting_pars)

    # create constraints
    all_constraints = list()
    if 'saddle_node' in list_of_constraints + [problem]:
        all_constraints = all_constraints + saddle_node_constraint(SN, parameterIndex)
    # if 'norm_param' in list_of_constraints:
    #    all_constraints = all_constraints + parameter_norm_constraint(all_starting_values)

    # create minimizing function
    if problem is 'saddle_node':
        min_function, jac_func, hessian_func = hill_coefficient_min()
    else:
        raise Exception("Not coded yet = only hysteresis considered")
    print(all_starting_values)
    results_min = minimize(min_function, all_starting_values, method='trust-constr', jac=jac_func,
                           constraints=all_constraints, hess=hessian_func)
    return results_min
        if orthogonal:
            orthogParm_hess = np.zeros([13, 13])
            orthogParm_hess[np.ix_(np.arange(5, 9), np.arange(9, 13))] = np.eye(4)
            orthogParm_hess[np.ix_(np.arange(9, 13), np.arange(5, 9))] = np.eye(4)
            return np.einsum('ijk, i', D2g, h[:-1]) + h[-1] * orthogParm_hess
        else:
            return np.einsum('ijk, i', D2g, h)

    return saddle_node_problem, saddle_node_jac, saddle_node_hess


decay = np.array([np.nan, np.nan], dtype=float)  # gamma
p1 = np.array([np.nan, np.nan, np.nan], dtype=float)  # (ell_1, delta_1, theta_1)
p2 = np.array([np.nan, np.nan, np.nan], dtype=float)  # (ell_2, delta_2, theta_2)
f = ToggleSwitch(decay, [p1, p2])
SN = SaddleNode(f)

# set up the minimization problem
loss, lossJacobian, lossHessian = minimize_hill(SN, punish_degenerate=True)  # minimization problem
xBounds = [[0, 10], [0, 10]]  # phase space bounds for saddle node equilibria points
vBounds = [[-5, 5], [-5, 5]]  # tangent vector coordinate bounds
hillBounds = [[1, 10]]  # hill coefficient bounds
parameterBounds = [[0, 10] for i in range(8)]  # bounds on remaining variable parameters
bounds = Bounds(*list(zip(*(xBounds + vBounds + hillBounds + parameterBounds))))
constraintFunction, constraintJacobian, constraintHessian = saddle_node_constraint(SN, orthogonal=False)
saddleNodeConstraint = NonlinearConstraint(constraintFunction, 0, 0, jac=constraintJacobian, hess=constraintHessian)
# saddleNodeConstraint = NonlinearConstraint(constraintFunction, 0, 0, jac=constraintJacobian, hess=BFGS())


def find_min_hill(hill, parameter):
    """Gradient descent of the loss function using SciPy optimize"""
import numpy as np
import matplotlib.pyplot as plt
import time
from hill_model import *
from saddle_node import SaddleNode
from models import ToggleSwitch

# set some parameters to test using MATLAB toggle switch for ground truth
decay = np.array([np.nan, np.nan], dtype=float)  # gamma
p1 = np.array([np.nan, np.nan, np.nan],
              dtype=float)  # (ell_1, delta_1, theta_1)
p2 = np.array([np.nan, np.nan, np.nan],
              dtype=float)  # (ell_2, delta_2, theta_2)

f = ToggleSwitch(decay, [p1, p2])
SN = SaddleNode(f)

# # =========== NON-DIMENSIONALIZED UNIFORM:  23.36 percent, 5,545 max Hill, 3.393 mean Hill
# saveFile = 'tsDataCenter'
# hillInitialData = np.linspace(2, 20, 10)
# hill = 3
#
#
# def sample_center():
#     """Return a sample point in the DSGRN central region"""
#     theta_1 = theta_2 = gamma_1 = 1.  # set fixed parameters for from non-dimensionalization
#     ell_1 = np.random.random_sample()  # sample in (0, 1)
#     u_1 = 1. + np.random.random_sample()  # sample ell_1 + delta_1 in (1,2)
#     delta_1 = u_1 - ell_1
#
#     gamma_2 = 0.1 + 9.9 * np.random.random_sample()  # set gamma_2 in (0.1, 10)
decay = np.array([np.nan, np.nan], dtype=float)  # gamma
p1 = np.array([np.nan, np.nan, np.nan],
              dtype=float)  # (ell_1, delta_1, theta_1)
p2 = np.array([np.nan, np.nan, np.nan],
              dtype=float)  # (ell_2, delta_2, theta_2)

f = ToggleSwitch(decay, [p1, p2])
f1 = f.coordinates[0]
f2 = f.coordinates[1]
H1 = f1.components[0]
H2 = f2.components[0]

p0 = np.array(
    [1, 1, 5, 3, 1, 1, 6, 3], dtype=float
)  # (gamma_1, ell_1, delta_1, theta_1, gamma_2, ell_2, delta_2, theta_2)
SN = SaddleNode(f)

# ==== find saddle node for a parameter choice
rho = 4.1
p = np.array([1, 1, 5, 3, 1, 1, 6, 3], dtype=float)

# x0Sol, v0Sol, rhoSol = [u0Sol.x[idx] for idx in [[0, 1], [2, 3], [4]]]
# # compare to rhoSol = [ 4.55637172,  2.25827744,  0.82199933, -0.56948846,  3.17447061]

# plot nullclines and equilibria
plt.close('all')
plt.figure()
f.plot_nullcline(rho, p)
plt.title('Initial parameters: \n' + np.array2string(ezcat(rho, p)))

fig = plt.figure(tight_layout=True, figsize=(15., 9.))