Quero fazer gráficos de contorno das funções com os pontos alcançados pelos métodos: import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize
from sympy import symbols, diff, Matrix
from mpl_toolkits.mplot3d import Axes3D
import sympy as sp
from sympy import lambdify
import plotly.graph_objects as go
from scipy.optimize import minimize
from numpy.linalg import eigvals
import pandas as pd 
import seaborn as sns
import time
import copy
import json
import re
def func1(x):
    return sum(100 * (x[i+1] - x[i]**2)**2 + (1 - x[i])**2 for i in range(len(x) - 1))

def func2(x):
    return sum(x_i**4 - 16 * x_i**2 + 5 * x_i for x_i in x)

def func3(x):
    x1, x2 = x[0], x[1]
    return (x1**2 + x2 - 11)**2 + (x1 + x2**2 - 7)**2

def calcular_gradiente_hessiana_simbolico(func_symbolic, num_vars):
    # Definir as variáveis simbólicas
    vars = sp.symbols(f'x0:{num_vars}')
    
    # Calcular o gradiente
    gradiente = [sp.diff(func_symbolic(vars), var) for var in vars]
    
    # Calcular a matriz Hessiana
    hessiana = [[sp.diff(grad, var) for var in vars] for grad in gradiente]
    
    return gradiente, hessiana, vars

# Armazenar gradientes e Hessianas simbólicos
gradientes_simbolicos = {}
hessianas_simbolicas = {}
vars_simbolicas = {}

# Calcular e armazenar gradiente e Hessiana para cada função
gradientes_simbolicos['func1'], hessianas_simbolicas['func1'], vars_simbolicas['func1'] = calcular_gradiente_hessiana_simbolico(func1, 7)
gradientes_simbolicos['func2'], hessianas_simbolicas['func2'], vars_simbolicas['func2'] = calcular_gradiente_hessiana_simbolico(func2, 100)
gradientes_simbolicos['func3'], hessianas_simbolicas['func3'], vars_simbolicas['func3'] = calcular_gradiente_hessiana_simbolico(func3, 2)

gradiente_f1_lambdified = lambdify(vars_simbolicas['func1'], gradientes_simbolicos['func1'], modules='numpy')
gradiente_f2_lambdified = lambdify(vars_simbolicas['func2'], gradientes_simbolicos['func2'], modules='numpy')
gradiente_f3_lambdified = lambdify(vars_simbolicas['func3'], gradientes_simbolicos['func3'], modules='numpy')

hessiana_f1_lambdified = lambdify(vars_simbolicas['func1'], hessianas_simbolicas['func1'], modules='numpy')
hessiana_f2_lambdified = lambdify(vars_simbolicas['func2'], hessianas_simbolicas['func2'], modules='numpy')
hessiana_f3_lambdified = lambdify(vars_simbolicas['func3'], hessianas_simbolicas['func3'], modules='numpy')
# Lambdified gradient and Hessian

# Function to evaluate convexity at a point using the Hessian
def check_convexity_at_point(hessian_func, point):
    hessian_matrix = np.array(hessian_func(*point))
    eigenvalues = eigvals(hessian_matrix)
    if np.all(eigenvalues > 0):
        return "Convex at this point"
    elif np.all(eigenvalues < 0):
        return "Concave at this point"
    else:
        return "Saddle point or indefinite at this point"

# Multi-start optimization with DataFrame storage
def find_multiple_critical_points(func, func_name, hessian, num_vars, num_starts=10, tol=1e-5):
    critical_points_data = []  # List to store data for DataFrame
    for i in range(num_starts):
        initial_guess = np.random.uniform(-100, 100, num_vars)
        result = minimize(func, initial_guess, method='CG', tol=tol)

        if result.success:
            point = result.x
            is_new_point = all(np.linalg.norm(point - cp) > tol for cp in [data[0] for data in critical_points_data])  # Check against existing points
            if is_new_point:
                convexity = check_convexity_at_point(hessian, point)
                func_value = func(point)  # Calculate function value
                critical_points_data.append([point, convexity, func_name, func_value]) # Changed to add all the point attributes

    # Create DataFrame
    df = pd.DataFrame(critical_points_data, columns=['Critical Point', 'Convexity', 'Function Name', 'Function Value'])
    return df

# Example usage
num_vars_1 = 7
num_vars_2 = 100
num_vars_3 = 2

critical_points_f1_df = find_multiple_critical_points(lambda x: func1(x), 'f1', hessiana_f1_lambdified, num_vars_1)
critical_points_f2_df = find_multiple_critical_points(lambda x: func2(x), 'f2', hessiana_f2_lambdified, num_vars_2)
critical_points_f3_df = find_multiple_critical_points(lambda x: func3(x), 'f3', hessiana_f3_lambdified, num_vars_3)
def armijo_line_search(f, grad_f, x, d, gamma=0.8, eta=0.25):
    t = 1
    total_iterations = 1
    while total_iterations < 1e4 and f(x + t * d) > f(x) + eta * t * np.dot(grad_f(x), d):
        t *= gamma
        total_iterations += 1
    return t, total_iterations
def GradientSearch(f, grad_f, x, eps, max_iter, time_limit, stop):
    """
    Performs Gradient Descent optimization with Armijo line search.

    Parameters:
    - f: The objective function.
    - grad_f: The gradient of the objective function.
    - x: Initial point as a NumPy array.
    - eps: Tolerance for the gradient norm.
    - max_iter: Maximum number of iterations.
    - time_limit: Time limit for the optimization (in seconds).
    - stop: Stopping criterion ("time" or "max_iter").

    Returns:
    - x: The optimized variable.
    - k: Number of iterations performed.
    - total_armijo: Total Armijo iterations.
    - f_current: Final function value.
    - f_list: List of function values per iteration.
    - error_ratios: List of error ratios per iteration.
    - total_time: Total time taken for optimization.
    """
    k = 0
    total_armijo = 0
    f_current = f(x)
    gradient = grad_f(x)
    f_list = [f_current]
    error_ratios = []
    f_prev = f(x)
    start_time = time.time()

    # Initialize stop_condition correctly
    if stop == "time" or stop == "max_iter":
        stop_condition = True  # Placeholder; actual condition checked inside loop
    else:
        raise ValueError("Unsupported stopping criterion. Use 'time' or 'max_iter'.")

    while True:
        # Update stop_condition based on the chosen stopping criterion
        if stop == "time":
            stop_condition = (time.time() - start_time) < time_limit
        elif stop == "max_iter":
            stop_condition = k < max_iter

        # Check stopping conditions
        if not stop_condition or np.linalg.norm(gradient) <= eps:
            break

        # Compute search direction
        d = -gradient

        # Perform Armijo line search
        step, armijo_iter = armijo_line_search(f, grad_f, x, d)
        total_armijo += armijo_iter

        # Update position
        x = x + d * step
        k += 1

        # Compute new gradient and function value
        gradient = grad_f(x)
        f_current = f(x)
        f_list.append(f_current)

        # Calculate error ratio
        if k > 1:
            if abs(f_prev) > 1e-12:  # Avoid division by zero with a small threshold
                error_ratio = abs(f_current - f_prev) / abs(f_prev)
            else:
                error_ratio = np.nan
            error_ratios.append(error_ratio)
        else:
            error_ratios.append(np.nan)  # First iteration has no previous value to compare

        f_prev = f_current

    return x, k, total_armijo, f(x), f_list, error_ratios, time.time() - start_time
def NewtonSearch(f, grad_f, hess_f, x, eps, max_iter, time_limit, stop):
    """
    Performs Newton's method optimization with Armijo line search.

    Parameters:
    - Same as your original definition.

    Returns:
    - Same as your original definition.
    """
    k = 0
    total_armijo = 0
    gradient = grad_f(x)
    f_current = f(x)
    f_list = [f_current] 
    f_prev = f_current
    error_ratios = []
    start_time = time.time()

    # Initialize stop_condition correctly
    if stop == "time":
        stop_condition = True  # Placeholder; actual condition checked inside loop
    elif stop == "max_iter":
        stop_condition = True  # Placeholder; actual condition checked inside loop
    else:
        raise ValueError("Unsupported stopping criterion. Use 'time' or 'max_iter'.")

    while True:
        # Update stop_condition based on the chosen stopping criterion
        if stop == "time":
            stop_condition = (time.time() - start_time) < time_limit
        elif stop == "max_iter":
            stop_condition = k < max_iter

        # Check stopping conditions
        if not stop_condition or np.linalg.norm(gradient) <= eps:
            break

        try:
            # Compute search direction
            hessian = hess_f(x)
            # Ensure Hessian is not singular
            d = -np.linalg.solve(hessian, gradient)
        except np.linalg.LinAlgError:
            print("Hessian is singular or ill-conditioned at iteration", k)
            break

        # Perform Armijo line search
        step, armijo_iter = armijo_line_search(f, grad_f, x, d)
        total_armijo += armijo_iter

        # Update position
        x = x + d * step
        k += 1

        # Compute new gradient and function value
        gradient = grad_f(x)
        f_current = f(x)
        f_list.append(f_current)  # Append the new function value

        # Calculate error ratio
        if k > 0:
            if abs(f_prev) > 1e-12:  # Avoid division by zero with a small threshold
                error_ratio = abs(f_current - f_prev) / abs(f_prev)
            else:
                error_ratio = np.nan
            error_ratios.append(error_ratio)

        f_prev = f_current

    total_time = time.time() - start_time
    return x, k, total_armijo, f(x), f_list, error_ratios, time.time() - start_time
def BFGS(f, grad_f, x0, tol=1e-6, max_iter=100, time_limit=100, stop="time", H0=None):

    k = 0
    n = len(x0)
    x_k = x0
    f_list = [f(x0)]
    f_prev = f(x0)
    error_ratios = []
    
    H_k = np.eye(n) if H0 is None else H0
    start_time = time.time()

    total_armijo = 0
    grad_k = grad_f(x0)

    while True:
        # Update stop_condition based on chosen stopping criterion
        if stop == "time":
            stop_condition = (time.time() - start_time) < time_limit
        elif stop == "max_iter":
            stop_condition = k < max_iter
        else:
            raise ValueError("Unsupported stopping criterion")

        # Check stopping conditions
        if not stop_condition or np.linalg.norm(grad_k) <= tol:
            break

        # Compute search direction
        d_k = -H_k @ grad_k

        # Perform line search
        t_k, armijo_iter = armijo_line_search(f, grad_f, x_k, d_k)
        total_armijo += armijo_iter

        # Update position
        x_next = x_k + t_k * d_k
        p_k = x_next - x_k
        grad_next = grad_f(x_next)
        q_k = grad_next - grad_k

        p_q = np.dot(p_k, q_k)
        H_q = H_k @ q_k

        if abs(p_q) > 1e-10:  # Use a small threshold instead of exact zero
            H_k = (H_k + np.outer(p_k, p_k) / p_q
                   - np.outer(H_q, H_q) / np.dot(q_k, H_q))

        x_k = x_next
        grad_k = grad_next
        k += 1

        f_current = f(x_k)
        f_list.append(f_current)
        if k > 0:
            error_ratio = abs(f_current - f_prev) / abs(f_prev) if abs(f_prev) > 0 else np.nan
            error_ratios.append(error_ratio)
        f_prev = f_current

    return x_k, k, total_armijo, f(x_k), f_list, error_ratios, time.time() - start_time
def DFP(f, func_grad, x0, tol=1e-6, max_iter=10000, time_limit=100, stop="time", H0=None):
    """
    Performs the DFP (Davidon-Fletcher-Powell) optimization algorithm with Armijo line search.

    Parameters:
    - f: objective function.
    - func_grad: gradient function.
    - x0: initial point as a NumPy array.
    - tol: tolerance for the gradient norm.
    - max_iter: maximum number of iterations.
    - time_limit: time limit for optimization (in seconds).
    - stop: stopping criterion ("time" or "max_iter").
    - H0: initial Hessian approximation matrix. Defaults to identity matrix.

    Returns:
    - x_k: optimized variable.
    - k: number of iterations performed.
    - total_armijo: total Armijo iterations.
    - f_current: final function value.
    - f_list: list of function values per iteration.
    - error_ratios: list of error ratios per iteration.
    - total_time: total time taken for optimization.
    """
    k = 0
    x_k = copy.deepcopy(x0)
    f_current = f(x_k)
    f_list = [f_current]  # Initialize with the initial function value
    f_prev = f_current
    error_ratios = []
    
    n = len(x0)
    H_k = np.eye(n) if H0 is None else H0
    start_time = time.time()
    
    total_armijo = 0
    grad_k = func_grad(x_k)
    epsilon = 1e-10  # Small value to prevent division by zero
    
    while True:
        # Check stopping conditions
        elapsed_time = time.time() - start_time
        if stop == "time" and elapsed_time >= time_limit:
            break
        if stop == "max_iter" and k >= max_iter:
            break
        if np.linalg.norm(grad_k) <= tol:
            break
        
        # Compute search direction
        d_k = -H_k @ grad_k
        
        # Perform Armijo line search
        step, armijo_iter = armijo_line_search(f, func_grad, x_k, d_k)
        total_armijo += armijo_iter
        
        # Update position
        x_next = x_k + step * d_k
        p_k = x_next - x_k
        grad_next = func_grad(x_next)
        q_k = grad_next - grad_k

        p_q = np.dot(p_k, q_k)
        if abs(p_q) < 1e-12:  # p^T q is too close to zero
            H_k = np.eye(n)
            continue
        
        # Update Hessian approximation using the corrected DFP formula
        H_k = H_k + np.outer(p_k, p_k) / p_q \
              - (H_k @ np.outer(q_k, p_k) + np.outer(p_k, q_k) @ H_k) / p_q
        
        # Optionally, ensure H_k remains symmetric
        H_k = (H_k + H_k.T) / 2
        
        # Update variables for next iteration
        x_k = x_next
        grad_k = grad_next
        k += 1
        
        # Compute new function value
        f_current = f(x_k)
        f_list.append(f_current)
        
        # Calculate error ratio
        if k > 1:
            if abs(f_prev) > 1e-12:
                error_ratio = abs(f_current - f_prev) / abs(f_prev)
            else:
                error_ratio = np.nan
            error_ratios.append(error_ratio)
        else:
            error_ratios.append(np.nan)
        
        f_prev = f_current
    
    total_time = time.time() - start_time
    return x_k, k, total_armijo, f_current, f_list, error_ratios, total_time
df_results = pd.DataFrame(columns=[
    "Função utilizada", "Ponto inicial", "Método", "# de iterações",
    "# total de iterações da busca de Armijo", "Ponto ótimo encontrado",
    "Valor da função no ponto ótimo encontrado", "Erro de aproximação",
    "Lista de f", "Razão de erros", 'Tempo de execução'])

df_armijo = pd.DataFrame(columns=[
    "Método", "Total Armijo", "Média Armijo por Iteração"
])

initial_points_f1 = []
initial_points_f2 = []
initial_points_f3 = []

for _ in range(5):
    initial_points_f1.append(np.random.uniform(-100, 100, 7))
    initial_points_f2.append(np.random.uniform(-100, 100, 100))
    initial_points_f3.append(np.random.uniform(-100, 100, 2))
for i in range(5):
    gradientX, gradientIterations, gradientArmijoIterations, gradientF, gradientFList, gradientErrors, GradientExecTime = GradientSearch(func1, avaliar_gradiente_f1, initial_points_f1[i], 1e-5, 100, 1, "max_iter")
    newtonX, newtonIterations, newtonArmijoIterations, newtonF, newtonFList, newtonErrors, newtonExecTime = NewtonSearch(func1, avaliar_gradiente_f1, avaliar_hessiana_f1, initial_points_f1[i], 1e-5, 100, 1, "max_iter")
    bfgsX, bfgsIterations, bfgsArmijoIterations, bfgsF, bfgsFList, bfgsErrors, bfgsExecTime = BFGS(func1, avaliar_gradiente_f1, initial_points_f1[i], 1e-5, 100, 1, "max_iter")
    dfpX, dfpIterations, dfpArmijoIterations, dfpF, dfpFList, dfpErrors, dfpExecTime = DFP(func1, avaliar_gradiente_f1, initial_points_f1[i], 1e-5, 100, 1, "max_iter")

    gradient_armijo = {
    "Método": "Gradiente",
    "Total Armijo": gradientArmijoIterations,
    "Média Armijo por Iteração": [
        gradientArmijoIterations / gradientIterations,
    ]
}

    newton_armijo = {
    "Método": "Newton",
    "Total Armijo": newtonArmijoIterations,
    "Média Armijo por Iteração": [
        newtonArmijoIterations / newtonIterations,
    ]
}

    bfgs_armijo = {
    "Método": "BFGS",
    "Total Armijo": bfgsArmijoIterations,
    "Média Armijo por Iteração": [
        bfgsArmijoIterations / bfgsIterations,
    ]
}
    
    dfp_armijo = {
    "Método": "DFP",
    "Total Armijo": dfpArmijoIterations,
    "Média Armijo por Iteração": [
        dfpArmijoIterations / dfpIterations,
    ]
}

    df_armijo = pd.concat([df_armijo, pd.DataFrame([gradient_armijo])], ignore_index=True)
    df_armijo = pd.concat([df_armijo, pd.DataFrame([newton_armijo])], ignore_index=True)
    df_armijo = pd.concat([df_armijo, pd.DataFrame([bfgs_armijo])], ignore_index=True)
    df_armijo = pd.concat([df_armijo, pd.DataFrame([dfp_armijo])], ignore_index=True)

    gradient_row = {
        "Função utilizada": "func1",
        "Ponto inicial": initial_points_f1[i],
        "Método": "Gradiente",
        "# de iterações": gradientIterations,
        "# total de iterações da busca de Armijo": gradientArmijoIterations,
        "Ponto ótimo encontrado": gradientX.tolist(),
        "Valor da função no ponto ótimo encontrado": gradientF,
        "Erro de aproximação": np.linalg.norm(avaliar_gradiente_f1(gradientX)), 
        "Lista de f": gradientFList,
        "Razão de erros": gradientErrors,
        "Tempo de execução": GradientExecTime
    }

    newton_row = {
        "Função utilizada": "func1",
        "Ponto inicial": initial_points_f1[i],
        "Método": "Newton",
        "# de iterações": newtonIterations,
        "# total de iterações da busca de Armijo": newtonArmijoIterations,
        "Ponto ótimo encontrado": newtonX.tolist(),
        "Valor da função no ponto ótimo encontrado": newtonF,
        "Erro de aproximação": np.linalg.norm(avaliar_gradiente_f1(newtonX)), 
        "Lista de f": newtonFList,
        "Razão de erros": newtonErrors,
        "Tempo de execução": newtonExecTime
    }

    bfgs_row = {
        "Função utilizada": "func1",
        "Ponto inicial": initial_points_f1[i],
        "Método": "BFGS",
        "# de iterações": bfgsIterations,
        "# total de iterações da busca de Armijo": bfgsArmijoIterations,
        "Ponto ótimo encontrado": bfgsX.tolist(),
        "Valor da função no ponto ótimo encontrado":bfgsF,
        "Erro de aproximação": np.linalg.norm(avaliar_gradiente_f1(bfgsX)),  
        "Lista de f": bfgsFList,
        "Razão de erros": bfgsErrors,
        "Tempo de execução": bfgsExecTime
    }

    dfp_row = {
        "Função utilizada": "func1",
        "Ponto inicial": initial_points_f1[i],
        "Método": "DFP",
        "# de iterações": dfpIterations,
        "# total de iterações da busca de Armijo": dfpArmijoIterations,
        "Ponto ótimo encontrado": dfpX,
        "Valor da função no ponto ótimo encontrado":dfpF,
        "Erro de aproximação": np.linalg.norm(avaliar_gradiente_f1(dfpX)),  
        "Lista de f": dfpFList,
        "Razão de erros": dfpErrors,
        "Tempo de execução": dfpExecTime
    }

    df_results = pd.concat([df_results, pd.DataFrame([gradient_row])], ignore_index=True)
    df_results = pd.concat([df_results, pd.DataFrame([newton_row])], ignore_index=True)
    df_results = pd.concat([df_results, pd.DataFrame([bfgs_row])], ignore_index=True)
    df_results = pd.concat([df_results, pd.DataFrame([dfp_row])], ignore_index=True)
for i in range(5):
    gradientX, gradientIterations, gradientArmijoIterations, gradientF, gradientFList, gradientErrors, GradientExecTime = GradientSearch(func2, avaliar_gradiente_f2, initial_points_f2[i], 1e-5, 100, 1, "max_iter")
    newtonX, newtonIterations, newtonArmijoIterations, newtonF, newtonFList, newtonErrors, newtonExecTime = NewtonSearch(func2, avaliar_gradiente_f2, avaliar_hessiana_f2, initial_points_f2[i], 1e-5, 100, 1, "max_iter")
    bfgsX, bfgsIterations, bfgsArmijoIterations, bfgsF, bfgsFList, bfgsErrors, bfgsExecTime = BFGS(func2, avaliar_gradiente_f2, initial_points_f2[i], 1e-5, 100, 1, "max_iter")
    dfpX, dfpIterations, dfpArmijoIterations, dfpF, dfpFList, dfpErrors, dfpExecTime = DFP(func2, avaliar_gradiente_f2, initial_points_f2[i], 1e-5, 100, 1, "max_iter")


    gradient_armijo = {
    "Método": "Gradiente",
    "Total Armijo": gradientArmijoIterations,
    "Média Armijo por Iteração": [
        gradientArmijoIterations / gradientIterations,
    ]
}

    newton_armijo = {
    "Método": "Newton",
    "Total Armijo": newtonArmijoIterations,
    "Média Armijo por Iteração": [
        newtonArmijoIterations / newtonIterations,
    ]
}

    bfgs_armijo = {
    "Método": "BFGS",
    "Total Armijo": bfgsArmijoIterations,
    "Média Armijo por Iteração": [
        bfgsArmijoIterations / bfgsIterations,
    ]
}
    
    dfp_armijo = {
    "Método": "DFP",
    "Total Armijo": dfpArmijoIterations,
    "Média Armijo por Iteração": [
        dfpArmijoIterations / dfpIterations,
    ]
}

    df_armijo = pd.concat([df_armijo, pd.DataFrame([gradient_armijo])], ignore_index=True)
    df_armijo = pd.concat([df_armijo, pd.DataFrame([newton_armijo])], ignore_index=True)
    df_armijo = pd.concat([df_armijo, pd.DataFrame([bfgs_armijo])], ignore_index=True)
    df_armijo = pd.concat([df_armijo, pd.DataFrame([dfp_armijo])], ignore_index=True)
    
    gradient_row = {
        "Função utilizada": "func2",
        "Ponto inicial": initial_points_f2[i],
        "Método": "Gradiente",
        "# de iterações": gradientIterations,
        "# total de iterações da busca de Armijo": gradientArmijoIterations,
        "Ponto ótimo encontrado": gradientX,
        "Valor da função no ponto ótimo encontrado": gradientF,
        "Erro de aproximação": np.linalg.norm(avaliar_gradiente_f2(gradientX)), 
        "Lista de f": gradientFList,
        "Razão de erros": gradientErrors,
        "Tempo de execução": GradientExecTime
    }

    newton_row = {
        "Função utilizada": "func2",
        "Ponto inicial": initial_points_f2[i],
        "Método": "Newton",
        "# de iterações": newtonIterations,
        "# total de iterações da busca de Armijo": newtonArmijoIterations,
        "Ponto ótimo encontrado": newtonX,
        "Valor da função no ponto ótimo encontrado": newtonF,
        "Erro de aproximação": np.linalg.norm(avaliar_gradiente_f2(newtonX)), 
        "Lista de f": newtonFList,
        "Razão de erros": newtonErrors,
        "Tempo de execução": newtonExecTime
    }

    bfgs_row = {
        "Função utilizada": "func2",
        "Ponto inicial": initial_points_f2[i],
        "Método": "BFGS",
        "# de iterações": bfgsIterations,
        "# total de iterações da busca de Armijo": bfgsArmijoIterations,
        "Ponto ótimo encontrado": bfgsX,
        "Valor da função no ponto ótimo encontrado":bfgsF,
        "Erro de aproximação": np.linalg.norm(avaliar_gradiente_f2(bfgsX)),  
        "Lista de f": bfgsFList,
        "Razão de erros": bfgsErrors,
        "Tempo de execução": bfgsExecTime
    }

    dfp_row = {
        "Função utilizada": "func2",
        "Ponto inicial": initial_points_f2[i],
        "Método": "DFP",
        "# de iterações": dfpIterations,
        "# total de iterações da busca de Armijo": dfpArmijoIterations,
        "Ponto ótimo encontrado": dfpX,
        "Valor da função no ponto ótimo encontrado":dfpF,
        "Erro de aproximação": np.linalg.norm(avaliar_gradiente_f2(dfpX)),  
        "Lista de f": dfpFList,
        "Razão de erros": dfpErrors,
        "Tempo de execução": dfpExecTime
    }

    df_results = pd.concat([df_results, pd.DataFrame([gradient_row])], ignore_index=True)
    df_results = pd.concat([df_results, pd.DataFrame([newton_row])], ignore_index=True)
    df_results = pd.concat([df_results, pd.DataFrame([bfgs_row])], ignore_index=True)
    df_results = pd.concat([df_results, pd.DataFrame([dfp_row])], ignore_index=True)
for i in range(5):
    gradientX, gradientIterations, gradientArmijoIterations, gradientF, gradientFList, gradientErrors, GradientExecTime = GradientSearch(func3, avaliar_gradiente_f3, initial_points_f3[i], 1e-5, 100, 1, "max_iter")
    newtonX, newtonIterations, newtonArmijoIterations, newtonF, newtonFList, newtonErrors, newtonExecTime = NewtonSearch(func3, avaliar_gradiente_f3, avaliar_hessiana_f3, initial_points_f3[i], 1e-5, 100, 1, "max_iter")
    bfgsX, bfgsIterations, bfgsArmijoIterations, bfgsF, bfgsFList, bfgsErrors, bfgsExecTime = BFGS(func3, avaliar_gradiente_f3, initial_points_f3[i], 1e-5, 100, 1, "max_iter")
    dfpX, dfpIterations, dfpArmijoIterations, dfpF, dfpFList, dfpErrors, dfpExecTime = DFP(func3, avaliar_gradiente_f3, initial_points_f3[i], 1e-5, 100, 1, "max_iter")

    gradient_armijo = {
    "Método": "Gradiente",
    "Total Armijo": gradientArmijoIterations,
    "Média Armijo por Iteração": [
        gradientArmijoIterations / gradientIterations,
    ]
}

    newton_armijo = {
    "Método": "Newton",
    "Total Armijo": newtonArmijoIterations,
    "Média Armijo por Iteração": [
        newtonArmijoIterations / newtonIterations,
    ]
}

    bfgs_armijo = {
    "Método": "BFGS",
    "Total Armijo": bfgsArmijoIterations,
    "Média Armijo por Iteração": [
        bfgsArmijoIterations / bfgsIterations,
    ]
}
    
    dfp_armijo = {
    "Método": "DFP",
    "Total Armijo": dfpArmijoIterations,
    "Média Armijo por Iteração": [
        dfpArmijoIterations / dfpIterations,
    ]
}

    df_armijo = pd.concat([df_armijo, pd.DataFrame([gradient_armijo])], ignore_index=True)
    df_armijo = pd.concat([df_armijo, pd.DataFrame([newton_armijo])], ignore_index=True)
    df_armijo = pd.concat([df_armijo, pd.DataFrame([bfgs_armijo])], ignore_index=True)
    df_armijo = pd.concat([df_armijo, pd.DataFrame([dfp_armijo])], ignore_index=True)

    gradient_row = {
        "Função utilizada": "func3",
        "Ponto inicial": initial_points_f3[i],
        "Método": "Gradiente",
        "# de iterações": gradientIterations,
        "# total de iterações da busca de Armijo": gradientArmijoIterations,
        "Ponto ótimo encontrado": gradientX,
        "Valor da função no ponto ótimo encontrado": gradientF,
        "Erro de aproximação": np.linalg.norm(avaliar_gradiente_f3(gradientX)), 
        "Lista de f": gradientFList,
        "Razão de erros": gradientErrors,
        "Tempo de execução": GradientExecTime
    }

    newton_row = {
        "Função utilizada": "func3",
        "Ponto inicial": initial_points_f3[i],
        "Método": "Newton",
        "# de iterações": newtonIterations,
        "# total de iterações da busca de Armijo": newtonArmijoIterations,
        "Ponto ótimo encontrado": newtonX,
        "Valor da função no ponto ótimo encontrado": newtonF,
        "Erro de aproximação": np.linalg.norm(avaliar_gradiente_f3(newtonX)), 
        "Lista de f": newtonFList,
        "Razão de erros": newtonErrors,
        "Tempo de execução": newtonExecTime
    }

    bfgs_row = {
        "Função utilizada": "func3",
        "Ponto inicial": initial_points_f3[i],
        "Método": "BFGS",
        "# de iterações": bfgsIterations,
        "# total de iterações da busca de Armijo": bfgsArmijoIterations,
        "Ponto ótimo encontrado": bfgsX,
        "Valor da função no ponto ótimo encontrado":bfgsF,
        "Erro de aproximação": np.linalg.norm(avaliar_gradiente_f3(bfgsX)),  
        "Lista de f": bfgsFList,
        "Razão de erros": bfgsErrors,
        "Tempo de execução": bfgsExecTime
    }

    dfp_row = {
        "Função utilizada": "func3",
        "Ponto inicial": initial_points_f3[i],
        "Método": "DFP",
        "# de iterações": dfpIterations,
        "# total de iterações da busca de Armijo": dfpArmijoIterations,
        "Ponto ótimo encontrado": dfpX,
        "Valor da função no ponto ótimo encontrado":dfpF,
        "Erro de aproximação": np.linalg.norm(avaliar_gradiente_f3(dfpX)),  
        "Lista de f": dfpFList,
        "Razão de erros": dfpErrors,
        "Tempo de execução": dfpExecTime
    }

    df_results = pd.concat([df_results, pd.DataFrame([gradient_row])], ignore_index=True)
    df_results = pd.concat([df_results, pd.DataFrame([newton_row])], ignore_index=True)
    df_results = pd.concat([df_results, pd.DataFrame([bfgs_row])], ignore_index=True)
    df_results = pd.concat([df_results, pd.DataFrame([dfp_row])], ignore_index=True)