AGT: W2
2020-11-24 ( 2020-11-24)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
'figure.figsize'] = (8.0, 8.0)
mpl.rcParams[=np.pi
pidef view(X,show_labels=False):
=X # assign names...
vertices,edges=plt.figure()
fig=fig.gca()
ax'equal')
ax.set_aspect(
ax.set_axis_off()'white')
fig.set_facecolor(
= LineCollection(edges, color="black", lw=2)
lc
ax.add_collection(lc)for P in vertices:
0.005*N/2, edgecolor='black',
ax.add_patch(plt.Circle(P, =2, rasterized=False, antialiased=True,facecolor='w',
linewidth=1000) )
zorder
if show_labels:
for j in range(len(vertices)):
=vertices[j]
Px,Py"$%s$" % j, vertices[j] , (Px-0.06,Py+0.01),fontsize=24, color='blue')
ax.annotate(#Causes an autoscale update.
ax.plot() plt.show()
# Primo problema: come **codificare** un grafo: elenco di vertici (euclidei?) e di spigoli.
=3 #numero di case/pozzi
Nimport random
=[(random.random(),random.random()) for x in range(N)] # complesso simplicial astratto o euclideo
A=[ (random.random(), random.random()) for y in range(N)] # (niente cooordinate o coordinate?)
B
= A + B # concatenation of lists (overloading degli operatori)
vertices = [ [x, y] for x in A for y in B[1:] ] # math notation
edges # un grafo quindi è la coppia:
= (vertices,edges) # come complesso simpliciale mancherebbero gli 0-simplessi, però...
X
# proviamo a scrivere una funzione per calcolare l'omologia: h_0 e h_1: prima
# di tutto dovremmo capire se è connesso oppure no...
=True) view(X,show_labels

# 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):
= X
vertices,edges = np.zeros((len(vertices),len(vertices)))
incidence_matrix for s in edges:
=vertices.index(s[0])
x=vertices.index(s[1])
y
= 1
incidence_matrix[x,y] = 1
incidence_matrix[y,x] = csr_matrix(incidence_matrix)
graph = connected_components(csgraph=graph, directed=False, return_labels=False)
n_components 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):
= X
vertices, edges = len(vertices)
c0 = len(edges)
c1 return h0(X) - c0 + c1
print(h1(X))
2
# e per dare delle coordinate migliori di quelle random?
import scipy.optimize
=1.0
elastic_constant
def sq_euc_norm(P):
=0.0
resultfor j in range(len(P)):
+= P[j] * P[j]
result return result
def euc_norm(P):
= sq_euc_norm(P)
sq_result return sq_result ** (0.5)
def distance(P, Q):
= euc_norm(P-Q)
result return result
def vector_product(P,Q):
=np.zeros(3)
vector_product0] = 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]
vector_product[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):
= 0.0
energy =conf.shape
number_of_vertices,eucl_dim# 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]:
+= Uij( conf[i,:],conf[j,:], are_endpoints=True )
energy else:
+= Uij( conf[i,:],conf[j,:], are_endpoints=False)
energy return energy
def grad_potential(conf,is_edge):
=conf.shape
number_of_vertices,eucl_dim= np.zeros((number_of_vertices,eucl_dim))
grad for i in range(number_of_vertices-1):
for j in range(i + 1, number_of_vertices ):
if is_edge[i,j]:
+= grad_Uij( conf[i,:],conf[j,:], are_endpoints=True )
grad[i,:] += grad_Uij( conf[j,:],conf[i,:], are_endpoints=True )
grad[j,:] else:
+= grad_Uij( conf[i,:],conf[j,:], are_endpoints=False )
grad[i,:] += grad_Uij( conf[j,:],conf[i,:], are_endpoints=False )
grad[j,:] return grad
def euclidean_embedding(vertices,edges,dim=2):
=len(vertices)
number_of_vertices=dim
eucl_dim
=np.zeros( (number_of_vertices,)*2 , dtype=bool)
is_edgefor (i,j) in edges:
=True
is_edge[i,j]
def opt_fun(x):
=x.reshape(number_of_vertices,eucl_dim)
conf= potential(conf,is_edge)
result return result
def opt_jac(x):
=x.reshape(number_of_vertices,eucl_dim)
conf= grad_potential(conf,is_edge)
result return result.reshape(number_of_vertices*eucl_dim)
=np.random.rand( number_of_vertices,eucl_dim )
X0= scipy.optimize.minimize(opt_fun,X0,method='CG',jac=opt_jac)
result
=result.x.reshape(number_of_vertices,eucl_dim)
outconf=euc_norm(result.jac)
howsolprint("howsol: {}".format(howsol))
# print(outconf)
= [ tuple( outconf[i,:] ) for i in range(number_of_vertices) ]
eucl_vertices= [ (eucl_vertices[i],eucl_vertices[j]) for i,j in edges ]
eucl_edgesreturn (eucl_vertices,eucl_edges)
=5 #numero di case/pozzi
N=[x for x in range(N)] # complesso simplicial astratto o euclideo
A=[ y for y in range(N,N+N)] # (niente cooordinate o coordinate?)
B= A + B # concatenation of lists (overloading degli operatori)
vertices = [ [x, y] for x in A for y in B ] # math notation
edges print(vertices,edges)
=euclidean_embedding(vertices,edges)
eucl_S=True) view(eucl_S,show_labels
[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

=7
N= [ x for x in range(N)]
vertices = [ sorted( [x, (x+1) % N] ) for x in range(N)] + \
edges sorted( [x,(x+3) %N ]) for x in range(N) ]
[ print(vertices,edges)
=euclidean_embedding(vertices,edges)
eucl_S=True) view(eucl_S,show_labels
[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

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?