# -*- coding: utf-8 -*-
"""Untitled6.ipynb

Automatically generated by Colab.

Original file is located at
    https://colab.research.google.com/drive/118hHMXEMuIIAlWR6YFdrkXsLvqKgA5TL
"""

# -*- coding: utf-8 -*-
"""interpolacao_linear.ipynb

Automatically generated by Colab.

Original file is located at
    https://colab.research.google.com/drive/1SK3m5gHieartoYFtbLYprPY-6p6Z2IXg
"""

# ============================================================
# Aula prÃ¡tica: InterpolaÃ§Ã£o Linear entre Pontos
# CÃ¡lculo NumÃ©rico
# ============================================================

import math
import pandas as pd
import matplotlib.pyplot as plt


# ------------------------------------------------------------
# 1. Pontos originais
# ------------------------------------------------------------

pontos = [
    (1.56, 1.56),  # P1
    (3.75, 0.58),  # P2
    (5.99, 8.66),  # P3
    (7.32, 6.01),  # P4
    (9.51, 7.08)   # P5
]

nomes_pontos = ["P1", "P2", "P3", "P4", "P5"]

limite = 2  # distÃ¢ncia mÃ¡xima desejada entre pontos consecutivos


# ------------------------------------------------------------
# 2. FunÃ§Ã£o para calcular a distÃ¢ncia euclidiana
# ------------------------------------------------------------

def distancia(p1, p2):
    """
    Calcula a distÃ¢ncia euclidiana entre dois pontos no plano.

    p1 = (x1, y1)
    p2 = (x2, y2)
    """
    x1, y1 = p1
    x2, y2 = p2

    dx = x2 - x1
    dy = y2 - y1

    return math.sqrt(dx**2 + dy**2)


# ------------------------------------------------------------
# 3. FunÃ§Ã£o de interpolaÃ§Ã£o linear paramÃ©trica
# ------------------------------------------------------------

def interpolar(p1, p2, t):
    """
    Gera um ponto intermediÃ¡rio entre p1 e p2 usando interpolaÃ§Ã£o linear.

    t = 0 retorna p1
    t = 1 retorna p2
    0 < t < 1 retorna um ponto intermediÃ¡rio
    """
    x1, y1 = p1
    x2, y2 = p2

    x = (1 - t) * x1 + t * x2
    y = (1 - t) * y1 + t * y2

    return (x, y)


# ------------------------------------------------------------
# 4. FunÃ§Ã£o para gerar pontos intermediÃ¡rios entre dois pontos
# ------------------------------------------------------------

def pontos_intermediarios(p1, p2, limite=2):
    """
    Gera pontos intermediÃ¡rios entre p1 e p2 para que
    cada segmento tenha comprimento mÃ¡ximo igual ao limite.

    Retorna:
    - lista de pontos intermediÃ¡rios
    - distÃ¢ncia original entre p1 e p2
    - nÃºmero de segmentos gerados
    """
    d = distancia(p1, p2)

    # n Ã© a quantidade de segmentos necessÃ¡rios
    n = math.ceil(d / limite)

    novos = []

    # Se n = 1, nÃ£o hÃ¡ ponto intermediÃ¡rio.
    # Se n = 2, cria 1 ponto.
    # Se n = 5, cria 4 pontos.
    for k in range(1, n):
        t = k / n
        ponto = interpolar(p1, p2, t)
        novos.append((ponto, t))

    return novos, d, n


# ------------------------------------------------------------
# 5. Mostrar os pontos originais
# ------------------------------------------------------------

print("PONTOS ORIGINAIS")
print("-" * 50)

df_originais = pd.DataFrame({
    "Ponto": nomes_pontos,
    "x": [p[0] for p in pontos],
    "y": [p[1] for p in pontos]
})

display(df_originais)


# ------------------------------------------------------------
# 6. Calcular distÃ¢ncias e gerar pontos interpolados
# ------------------------------------------------------------

resultado = []
registros_distancias = []
registros_interpolados = []

contador_interpolado = 1

for i in range(len(pontos) - 1):
    p_atual = pontos[i]
    p_proximo = pontos[i + 1]

    nome_atual = nomes_pontos[i]
    nome_proximo = nomes_pontos[i + 1]

    # Adiciona o ponto original atual ao resultado final
    resultado.append({
        "nome": nome_atual,
        "tipo": "Original",
        "x": p_atual[0],
        "y": p_atual[1]
    })

    # Calcula os pontos intermediÃ¡rios
    novos, d, n = pontos_intermediarios(p_atual, p_proximo, limite)

    registros_distancias.append({
        "Trecho": f"{nome_atual} -> {nome_proximo}",
        "DistÃ¢ncia original": d,
        "Limite": limite,
        "n segmentos": n,
        "Pontos internos criados": n - 1
    })

    # Adiciona os pontos interpolados ao resultado final
    for ponto, t in novos:
        nome_interpolado = f"I{contador_interpolado}"

        resultado.append({
            "nome": nome_interpolado,
            "tipo": "Interpolado",
            "x": ponto[0],
            "y": ponto[1]
        })

        registros_interpolados.append({
            "Ponto": nome_interpolado,
            "Entre": f"{nome_atual} -> {nome_proximo}",
            "t": t,
            "x": ponto[0],
            "y": ponto[1]
        })

        contador_interpolado += 1

# Adiciona o Ãºltimo ponto original
resultado.append({
    "nome": nomes_pontos[-1],
    "tipo": "Original",
    "x": pontos[-1][0],
    "y": pontos[-1][1]
})


# ------------------------------------------------------------
# 7. Exibir tabela de distÃ¢ncias
# ------------------------------------------------------------

print("\nDISTÃ‚NCIAS ENTRE PONTOS CONSECUTIVOS")
print("-" * 50)

df_distancias = pd.DataFrame(registros_distancias)

df_distancias["DistÃ¢ncia original"] = df_distancias["DistÃ¢ncia original"].round(3)

display(df_distancias)


# ------------------------------------------------------------
# 8. Exibir tabela dos pontos interpolados
# ------------------------------------------------------------

print("\nPONTOS INTERPOLADOS")
print("-" * 50)

df_interpolados = pd.DataFrame(registros_interpolados)

df_interpolados["t"] = df_interpolados["t"].round(3)
df_interpolados["x"] = df_interpolados["x"].round(3)
df_interpolados["y"] = df_interpolados["y"].round(3)

display(df_interpolados)


# ------------------------------------------------------------
# 9. Exibir tabela final com pontos originais + interpolados
# ------------------------------------------------------------

print("\nPONTOS FINAIS: ORIGINAIS + INTERPOLADOS")
print("-" * 50)

df_resultado = pd.DataFrame(resultado)

df_resultado["x"] = df_resultado["x"].round(3)
df_resultado["y"] = df_resultado["y"].round(3)

display(df_resultado)


# ------------------------------------------------------------
# 10. Verificar distÃ¢ncia entre os pontos finais consecutivos
# ------------------------------------------------------------

print("\nDISTÃ‚NCIA ENTRE OS PONTOS FINAIS CONSECUTIVOS")
print("-" * 50)

verificacao = []

for i in range(len(resultado) - 1):
    p1 = (resultado[i]["x"], resultado[i]["y"])
    p2 = (resultado[i + 1]["x"], resultado[i + 1]["y"])

    d = distancia(p1, p2)

    verificacao.append({
        "Trecho": f'{resultado[i]["nome"]} -> {resultado[i + 1]["nome"]}',
        "DistÃ¢ncia": d,
        "Respeita limite?": "Sim" if d <= limite else "NÃ£o"
    })

df_verificacao = pd.DataFrame(verificacao)
df_verificacao["DistÃ¢ncia"] = df_verificacao["DistÃ¢ncia"].round(3)

display(df_verificacao)


# ------------------------------------------------------------
# 11. Separar coordenadas para os grÃ¡ficos
# ------------------------------------------------------------

x_original = [p[0] for p in pontos]
y_original = [p[1] for p in pontos]

x_interp = [p["x"] for p in resultado]
y_interp = [p["y"] for p in resultado]


# ------------------------------------------------------------
# 12. GrÃ¡fico 1: pontos originais
# ------------------------------------------------------------

plt.figure(figsize=(8, 5))

plt.plot(x_original, y_original, 'o', markersize=8, label='Pontos originais')

for nome, x, y in zip(nomes_pontos, x_original, y_original):
    plt.text(x + 0.08, y + 0.08, nome)

plt.title("Pontos originais")
plt.xlabel("x")
plt.ylabel("y")
plt.grid(True)
plt.legend()
plt.show()


# ------------------------------------------------------------
# 13. GrÃ¡fico 2: interpolaÃ§Ã£o linear
# ------------------------------------------------------------

plt.figure(figsize=(8, 5))

plt.plot(x_original, y_original, 'o', markersize=8, label='Pontos originais')
plt.plot(x_interp, y_interp, '-o', markersize=5, label='Pontos apÃ³s interpolaÃ§Ã£o')

for p in resultado:
    plt.text(p["x"] + 0.08, p["y"] + 0.08, p["nome"], fontsize=9)

plt.title("InterpolaÃ§Ã£o linear entre pontos consecutivos")
plt.xlabel("x")
plt.ylabel("y")
plt.grid(True)
plt.legend()
plt.show()


# ------------------------------------------------------------
# 14. GrÃ¡fico 3: comparaÃ§Ã£o antes e depois
# ------------------------------------------------------------

plt.figure(figsize=(9, 5))

plt.plot(x_original, y_original, 'o--', markersize=8, label='Antes: pontos originais')
plt.plot(x_interp, y_interp, '-o', markersize=5, label='Depois: pontos interpolados')

plt.title("ComparaÃ§Ã£o: antes e depois da interpolaÃ§Ã£o")
plt.xlabel("x")
plt.ylabel("y")
plt.grid(True)
plt.legend()
plt.show()


# ------------------------------------------------------------
# 15. Teste com outros limites
# ------------------------------------------------------------

def gerar_resultado_com_limite(pontos, nomes_pontos, limite):
    """
    Gera a lista final de pontos para um determinado limite de distÃ¢ncia.
    """
    resultado_temp = []
    contador = 1

    for i in range(len(pontos) - 1):
        p_atual = pontos[i]
        p_proximo = pontos[i + 1]

        resultado_temp.append({
            "nome": nomes_pontos[i],
            "tipo": "Original",
            "x": p_atual[0],
            "y": p_atual[1]
        })

        novos, d, n = pontos_intermediarios(p_atual, p_proximo, limite)

        for ponto, t in novos:
            resultado_temp.append({
                "nome": f"I{contador}",
                "tipo": "Interpolado",
                "x": ponto[0],
                "y": ponto[1]
            })
            contador += 1

    resultado_temp.append({
        "nome": nomes_pontos[-1],
        "tipo": "Original",
        "x": pontos[-1][0],
        "y": pontos[-1][1]
    })

    return resultado_temp


# Testar diferentes valores de limite
limites = [1, 2, 3]

for lim in limites:
    res = gerar_resultado_com_limite(pontos, nomes_pontos, lim)

    x_res = [p["x"] for p in res]
    y_res = [p["y"] for p in res]

    plt.figure(figsize=(8, 5))
    plt.plot(x_original, y_original, 'o--', markersize=8, label='Originais')
    plt.plot(x_res, y_res, '-o', markersize=4, label=f'Interpolados - limite={lim}')

    plt.title(f"InterpolaÃ§Ã£o com distÃ¢ncia mÃ¡xima = {lim}")
    plt.xlabel("x")
    plt.ylabel("y")
    plt.grid(True)
    plt.legend()
    plt.show()

    print(f"Limite = {lim}")
    print(f"Quantidade total de pontos: {len(res)}")
    print(f"Pontos novos criados: {len(res) - len(pontos)}")
    print("-" * 50)

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from scipy.interpolate import lagrange, CubicSpline


# =========================================================
# 1. Pontos originais do exercício
# =========================================================

x_original = np.array([1, 4, 6, 9], dtype=float)
y_original = np.array([2, 1, 7, 4], dtype=float)

pontos = list(zip(x_original, y_original))


# =========================================================
# 2. Função para calcular distância euclidiana
# =========================================================

def distancia(p1, p2):
    x1, y1 = p1
    x2, y2 = p2

    return np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)


# =========================================================
# 3. Subdivisão dos segmentos
# =========================================================

distancia_maxima = 2

x_novo = []
y_linear = []

tabela_segmentos = []

for i in range(len(pontos) - 1):
    p_atual = pontos[i]
    p_proximo = pontos[i + 1]

    x1, y1 = p_atual
    x2, y2 = p_proximo

    d = distancia(p_atual, p_proximo)

    # Número de partes
    m = int(np.ceil(d / distancia_maxima))

    tabela_segmentos.append({
        "Segmento": f"Q{i+1}Q{i+2}",
        "Distância": d,
        "Partes": m,
        "Distância por parte": d / m
    })

    # Gera os pontos do segmento
    # Para evitar repetir o ponto inicial de cada novo segmento,
    # usamos k=0 apenas no primeiro segmento.
    inicio = 0 if i == 0 else 1

    for k in range(inicio, m + 1):
        xk = x1 + k * (x2 - x1) / m
        yk = y1 + k * (y2 - y1) / m

        x_novo.append(xk)
        y_linear.append(yk)

x_novo = np.array(x_novo)
y_linear = np.array(y_linear)


# =========================================================
# 4. Interpolador de Lagrange
# =========================================================

polinomio_lagrange = lagrange(x_original, y_original)
y_lagrange = polinomio_lagrange(x_novo)


# =========================================================
# 5. Interpolador de Newton por diferenças divididas
# =========================================================

def coeficientes_newton(x, y):
    """
    Calcula os coeficientes do polinômio de Newton
    usando diferenças divididas.
    """
    n = len(x)
    coef = np.copy(y).astype(float)

    for j in range(1, n):
        coef[j:n] = (coef[j:n] - coef[j - 1:n - 1]) / (x[j:n] - x[0:n - j])

    return coef


def avalia_newton(x_dados, coef, x):
    """
    Avalia o polinômio de Newton nos valores de x.
    """
    n = len(coef)
    resultado = coef[n - 1]

    for k in range(n - 2, -1, -1):
        resultado = resultado * (x - x_dados[k]) + coef[k]

    return resultado


coef_newton = coeficientes_newton(x_original, y_original)
y_newton = avalia_newton(x_original, coef_newton, x_novo)


# =========================================================
# 6. Spline linear
# =========================================================

# Como a spline linear é a interpolação linear por partes,
# podemos usar np.interp.
y_spline_linear = np.interp(x_novo, x_original, y_original)


# =========================================================
# 7. Spline cúbica natural
# =========================================================

spline_cubica = CubicSpline(x_original, y_original, bc_type="natural")
y_spline_cubica = spline_cubica(x_novo)


# =========================================================
# 8. Tabelas de saída
# =========================================================

df_segmentos = pd.DataFrame(tabela_segmentos)

df_comparacao = pd.DataFrame({
    "x": x_novo,
    "Linear": y_linear,
    "Lagrange": y_lagrange,
    "Newton": y_newton,
    "Spline Linear": y_spline_linear,
    "Spline Cúbica Natural": y_spline_cubica
})

print("\nTabela de segmentos:")
print(df_segmentos.round(3))

print("\nTabela comparativa:")
print(df_comparacao.round(3))


# =========================================================
# 9. Gráfico comparativo
# =========================================================

x_plot = np.linspace(min(x_original), max(x_original), 400)

y_plot_lagrange = polinomio_lagrange(x_plot)
y_plot_spline_linear = np.interp(x_plot, x_original, y_original)
y_plot_spline_cubica = spline_cubica(x_plot)

plt.figure(figsize=(10, 6))

# Pontos originais
plt.scatter(
    x_original,
    y_original,
    color="black",
    label="Pontos originais",
    zorder=5
)

# Curva de Lagrange/Newton
plt.plot(
    x_plot,
    y_plot_lagrange,
    color="blue",
    label="Lagrange/Newton"
)

# Spline linear
plt.plot(
    x_plot,
    y_plot_spline_linear,
    color="red",
    label="Spline linear"
)

# Spline cúbica natural
plt.plot(
    x_plot,
    y_plot_spline_cubica,
    color="green",
    label="Spline cúbica natural"
)

# Pontos novos da interpolação linear
plt.scatter(
    x_novo,
    y_linear,
    color="red",
    marker="o",
    label="Novos pontos lineares"
)

# Pontos novos de Lagrange/Newton
plt.scatter(
    x_novo,
    y_lagrange,
    color="blue",
    marker="s",
    label="Novos pontos Lagrange/Newton"
)

# Pontos novos da spline cúbica
plt.scatter(
    x_novo,
    y_spline_cubica,
    color="green",
    marker="^",
    label="Novos pontos spline cúbica"
)

plt.title("Comparação entre interpoladores")
plt.xlabel("x")
plt.ylabel("y")
plt.grid(True)
plt.legend()
plt.show()


# =========================================================
# 10. Polinômios obtidos
# =========================================================

print("\nPolinômio de Lagrange:")
print(polinomio_lagrange)

print("\nCoeficientes de Newton:")
for i, c in enumerate(coef_newton):
    print(f"a{i} = {c:.6f}")