AGT: W2 · Test Web Page

AGT: W2

Dato da fare la scorsa volta:

Scrivere un notebook python/sagemath (dopo averlo correttamente installato?) che dato un grafo (assegnato come complesso simpliciale astratto) calcola i due gruppi di omologia \(H_0\) e \(H_1\). Opzionale: assegnare (in qualche modo razionale) delle coordinate ai vertici, trasformandolo in grafo possibilmente euclideo, e visualizzare quel che viene.

# define a function for visualization:
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
import numpy as np
mpl.rcParams['figure.figsize'] = (8.0, 8.0)
pi=np.pi
def view(X,show_labels=False):
    vertices,edges=X # assign names...
    fig=plt.figure()
    ax=fig.gca()
    ax.set_aspect('equal')
    ax.set_axis_off()
    fig.set_facecolor('white')
    
    lc = LineCollection(edges, color="black", lw=2)
    
    ax.add_collection(lc)
    for P in vertices:
        ax.add_patch(plt.Circle(P, 0.005*N/2, edgecolor='black',
                        linewidth=2, rasterized=False, antialiased=True,facecolor='w',
                        zorder=1000) )
        
    if show_labels:
          for j in range(len(vertices)):
                Px,Py=vertices[j]
                ax.annotate("$%s$" % j, vertices[j] , (Px-0.06,Py+0.01),fontsize=24, color='blue')
    ax.plot()   #Causes an autoscale update.
    plt.show()
# Primo problema: come **codificare** un grafo: elenco di vertici (euclidei?) e di spigoli.
N=3 #numero di case/pozzi
import random

A=[(random.random(),random.random()) for x in range(N)]  # complesso simplicial astratto o euclideo
B=[ (random.random(), random.random()) for y in range(N)]  # (niente cooordinate o coordinate?)

vertices = A + B # concatenation of lists (overloading degli operatori)
edges = [ [x, y] for x in A for y in B[1:] ] # math notation
# un grafo quindi è la coppia:
X = (vertices,edges) # come complesso simpliciale mancherebbero gli 0-simplessi, però...

# proviamo a scrivere una funzione per calcolare l'omologia: h_0 e h_1: prima 
# di tutto dovremmo capire se è connesso oppure no... 

view(X,show_labels=True)
png
# now: come calcolare le componenti connesse? e l'omologia? come fare? 
# Classico: https://en.wikipedia.org/wiki/Breadth-first_search
from scipy.sparse import csr_matrix
from scipy.sparse.csgraph import connected_components


# quindi: h_0 è il numero delle componenti connesse:

def h0(X):
    vertices,edges = X 
    incidence_matrix = np.zeros((len(vertices),len(vertices)))
    for s in edges:
        x=vertices.index(s[0])
        y=vertices.index(s[1])
    
        incidence_matrix[x,y] = 1
        incidence_matrix[y,x] = 1
    graph = csr_matrix(incidence_matrix)
    n_components = connected_components(csgraph=graph, directed=False, return_labels=False)
    return n_components

print(h0(X))
    
2
# ora calcoliamo h_1: teniamo conto che h_0 - h_1 = c_0 - c_1 :
# segue che h_1 = h0 - c_0 + c_1 
def h1(X):
    vertices, edges = X 
    c0 = len(vertices)
    c1 = len(edges)
    return h0(X) - c0 + c1

print(h1(X))
2
# e per dare delle coordinate migliori di quelle random?
import scipy.optimize
elastic_constant=1.0

def sq_euc_norm(P):
    result=0.0
    for j in range(len(P)):
        result += P[j] * P[j]
    return result

def euc_norm(P):
    sq_result = sq_euc_norm(P)
    return sq_result ** (0.5)

def distance(P, Q):
    result = euc_norm(P-Q)
    return result

def vector_product(P,Q):
    vector_product=np.zeros(3)
    vector_product[0] = P[1] * Q[2] - Q[1] * P[2]
    vector_product[1] = Q[0] * P[2] - P[0] * Q[2]
    vector_product[2] = P[0] * Q[1] - Q[0] * P[1]
    return vector_product

def dot_product(P,Q):
    return np.dot(P,Q)

def Uij(P,Q,are_endpoints=False):
    """interaction of P and Q: edges elastic attract, all repulsive"""
    if are_endpoints:
        return elastic_constant * sq_euc_norm(P-Q) + 1. / euc_norm(P-Q)
    else:
        return 1. / euc_norm(P-Q)

def grad_Uij(P,Q,are_endpoints=False):
    """grad of interaction of P and Q, wrt P"""
    if are_endpoints:
        return 2.0 * elastic_constant * (P-Q) - (P-Q) * euc_norm(P-Q)**(-3.)
    else:
        return - (P-Q) * euc_norm(P-Q)**(-3.)

def potential(conf,is_edge):
    energy = 0.0
    number_of_vertices,eucl_dim=conf.shape
    # numba.prange requires parallel=True flag to compile.
    # It causes the loop to run in parallel in multiple threads.
    for i in range(number_of_vertices-1):
        for j in range(i + 1, number_of_vertices ):
            if is_edge[i,j]:
                energy += Uij( conf[i,:],conf[j,:], are_endpoints=True )
            else:
                energy += Uij( conf[i,:],conf[j,:], are_endpoints=False)
    return energy

def grad_potential(conf,is_edge):
    number_of_vertices,eucl_dim=conf.shape
    grad = np.zeros((number_of_vertices,eucl_dim))
    for i in range(number_of_vertices-1):
        for j in range(i + 1, number_of_vertices ):
            if is_edge[i,j]:
                grad[i,:] += grad_Uij( conf[i,:],conf[j,:], are_endpoints=True )
                grad[j,:] += grad_Uij( conf[j,:],conf[i,:], are_endpoints=True )
            else:
                grad[i,:] += grad_Uij( conf[i,:],conf[j,:], are_endpoints=False )
                grad[j,:] += grad_Uij( conf[j,:],conf[i,:], are_endpoints=False )
    return grad

def euclidean_embedding(vertices,edges,dim=2):
    number_of_vertices=len(vertices)
    eucl_dim=dim

    is_edge=np.zeros( (number_of_vertices,)*2 , dtype=bool)
    for (i,j) in edges:
        is_edge[i,j]=True

    def opt_fun(x):
        conf=x.reshape(number_of_vertices,eucl_dim)
        result = potential(conf,is_edge)
        return result
    
    def opt_jac(x):
        conf=x.reshape(number_of_vertices,eucl_dim)
        result = grad_potential(conf,is_edge)
        return result.reshape(number_of_vertices*eucl_dim)
    X0=np.random.rand( number_of_vertices,eucl_dim )
    result = scipy.optimize.minimize(opt_fun,X0,method='CG',jac=opt_jac)
    
    outconf=result.x.reshape(number_of_vertices,eucl_dim)
    howsol=euc_norm(result.jac)
    print("howsol: {}".format(howsol))
    # print(outconf)
    eucl_vertices= [ tuple( outconf[i,:] )  for i in range(number_of_vertices) ]
    eucl_edges= [ (eucl_vertices[i],eucl_vertices[j]) for i,j in edges ]
    return (eucl_vertices,eucl_edges)
N=5 #numero di case/pozzi
A=[x for x in range(N)]  # complesso simplicial astratto o euclideo
B=[ y for y in  range(N,N+N)]  # (niente cooordinate o coordinate?)
vertices = A + B # concatenation of lists (overloading degli operatori)
edges = [ [x, y] for x in A for y in B ] # math notation
print(vertices,edges)
eucl_S=euclidean_embedding(vertices,edges)
view(eucl_S,show_labels=True)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [[0, 5], [0, 6], [0, 7], [0, 8], [0, 9], [1, 5], [1, 6], [1, 7], [1, 8], [1, 9], [2, 5], [2, 6], [2, 7], [2, 8], [2, 9], [3, 5], [3, 6], [3, 7], [3, 8], [3, 9], [4, 5], [4, 6], [4, 7], [4, 8], [4, 9]]
howsol: 1.4563980367849192e-05
png
N=7
vertices = [ x for x in range(N)]
edges = [ sorted( [x, (x+1) % N] ) for x in range(N)] + \
 [ sorted( [x,(x+3) %N ]) for x in range(N) ]
print(vertices,edges)
eucl_S=euclidean_embedding(vertices,edges)
view(eucl_S,show_labels=True)
[0, 1, 2, 3, 4, 5, 6] [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [0, 6], [0, 3], [1, 4], [2, 5], [3, 6], [0, 4], [1, 5], [2, 6]]
howsol: 3.9716886522722156e-06
png

Quindi da fare…

Provare a fare eseguire i pezzi sopra di codice (copia-e-incolla), modificandoli qua e là. Quale potenziale è meglio per rappresentare visualmente i grafi? Qualcosa in 3d? E se ciascuno riuscisse a portare su e-learning l’immagine di un grafo di cui si è calcolato l’omologia? Fare piccola una galleria?