File size: 4,910 Bytes
34a568c
dff68e1
 
 
 
 
 
 
 
f41b181
dff68e1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34a568c
dff68e1
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import streamlit as st
import networkx as nx
import matplotlib.pyplot as plt
import pandas as pd
import random

# Configuración de la página
st.set_page_config(page_title="Visualizador de PageRank", layout="wide")


st.title("🕸️ Visualizador Interactivo de PageRank")
st.markdown("""
Esta herramienta permite visualizar cómo fluye la importancia (PageRank) a través de los nodos de una red.
El **tamaño del nodo** representa su PageRank actual.
""")

# --- BARRA LATERAL: Configuración ---
with st.sidebar:
    st.header("Configuración del Grafo")
    
    num_nodes = st.slider("Número de Nodos", min_value=3, max_value=20, value=8)
    prob_link = st.slider("Probabilidad de enlace (Densidad)", min_value=0.1, max_value=1.0, value=0.3)
    damping_factor = st.slider("Factor de Amortiguación (Damping)", min_value=0.5, max_value=0.99, value=0.85)
    
    st.markdown("---")
    
    if st.button("🔄 Generar Nuevo Grafo"):
        st.session_state.clear() # Limpia todo para reiniciar

# --- LÓGICA DE SESSION STATE ---
# Necesitamos mantener el estado del grafo y los valores entre interacciones

if 'graph' not in st.session_state:
    # Crear grafo dirigido aleatorio
    G = nx.gnp_random_graph(num_nodes, prob_link, directed=True, seed=random.randint(1, 1000))
    st.session_state.graph = G
    
    # Calcular layout fijo para que los nodos no bailen en cada paso
    st.session_state.pos = nx.spring_layout(G, k=0.8, iterations=50)
    
    # Inicializar PageRank: todos empiezan con 1/N
    initial_pr = {node: 1.0/num_nodes for node in G.nodes()}
    st.session_state.pagerank = initial_pr
    st.session_state.iteration = 0
    st.session_state.history = [initial_pr.copy()]

# Función para dar un paso del algoritmo
def step_pagerank():
    G = st.session_state.graph
    current_pr = st.session_state.pagerank
    d = damping_factor
    N = len(G.nodes)
    
    new_pr = {}
    
    # Fórmula básica de PageRank iterativo
    for i in G.nodes():
        incoming_sum = 0
        # Buscar nodos que apuntan a 'i'
        for node_j in G.predecessors(i):
            # Out-degree del nodo j
            out_degree = G.out_degree(node_j)
            if out_degree > 0:
                incoming_sum += current_pr[node_j] / out_degree
        
        # Aplicar fórmula: (1-d)/N + d * sum(PR(j)/L(j))
        val = ((1 - d) / N) + (d * incoming_sum)
        new_pr[i] = val
    
    # Manejo básico de sumideros (nodos sin salidas) para mantener la suma ~1
    # En implementaciones complejas se redistribuye la masa perdida
    total_mass = sum(new_pr.values())
    for k in new_pr:
        new_pr[k] /= total_mass
        
    st.session_state.pagerank = new_pr
    st.session_state.iteration += 1
    st.session_state.history.append(new_pr.copy())

# --- INTERFAZ PRINCIPAL ---

col1, col2 = st.columns([3, 1])

with col1:
    st.subheader(f"Grafo - Iteración: {st.session_state.iteration}")
    
    # Dibujar Grafo
    fig, ax = plt.subplots(figsize=(8, 6))
    
    G = st.session_state.graph
    pos = st.session_state.pos
    pr_values = st.session_state.pagerank
    
    # Escalar tamaño de nodos basado en PR (multiplicador estético)
    node_sizes = [v * 5000 for v in pr_values.values()]
    
    # Colores basados en importancia
    node_colors = list(pr_values.values())
    
    nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color=node_colors, cmap=plt.cm.viridis, alpha=0.9, ax=ax)
    nx.draw_networkx_edges(G, pos, arrowstyle='->', arrowsize=20, edge_color='gray', width=1, alpha=0.5, ax=ax, connectionstyle="arc3,rad=0.1")
    nx.draw_networkx_labels(G, pos, font_size=10, font_color="white", font_weight="bold", ax=ax)
    
    ax.axis('off')
    st.pyplot(fig)

    # Botón grande para iterar
    if st.button("▶️ Siguiente Iteración", use_container_width=True):
        step_pagerank()
        st.rerun()

with col2:
    st.subheader("Datos")
    
    # Crear DataFrame para mostrar valores
    df = pd.DataFrame.from_dict(st.session_state.pagerank, orient='index', columns=['PageRank'])
    df = df.sort_values(by='PageRank', ascending=False)
    
    st.dataframe(df.style.background_gradient(cmap='viridis'), height=400)
    
    # Métrica de convergencia (cambio respecto al paso anterior)
    if st.session_state.iteration > 0:
        prev = st.session_state.history[-2]
        curr = st.session_state.history[-1]
        diff = sum([abs(curr[n] - prev[n]) for n in curr])
        st.metric("Cambio Total (Delta)", f"{diff:.4f}")
        
        if diff < 0.001:
            st.success("¡El algoritmo ha convergido!")

# --- GRÁFICO DE EVOLUCIÓN ---
st.divider()
st.subheader("Evolución de Pesos por Nodo")

if st.session_state.iteration > 0:
    history_df = pd.DataFrame(st.session_state.history)
    st.line_chart(history_df)
else:
    st.info("Presiona 'Siguiente Iteración' para ver cómo evolucionan los pesos.")