2020-01-29 13:38:50 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
import sys, os
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
import numpy as np
|
|
|
|
from time import sleep
|
|
|
|
import argparse
|
2020-01-29 22:55:11 +01:00
|
|
|
from math import * # pour utliser les fonctions mathématiques dans l'option -f
|
2020-01-29 13:38:50 +01:00
|
|
|
|
|
|
|
"""
|
|
|
|
MNI TP3 - 27 janvier 2020
|
|
|
|
Dylan Voisin - M1 Informatique Luminy
|
|
|
|
Franck Palacios - M1 Informatique Luminy
|
2020-02-02 21:42:13 +01:00
|
|
|
Cyril Colin - M1 Informatique Luminy
|
2020-01-29 13:38:50 +01:00
|
|
|
|
|
|
|
Notes:
|
|
|
|
- u1 et u2 ne semblent pas orthogonaux, ceci est dû à la non utilisation
|
|
|
|
d'une échelle 1:1 pour l'affichage des points
|
|
|
|
- u1 et u2 ne sont pas proportionnels par rapport à lambda mais à la
|
2020-02-02 21:42:13 +01:00
|
|
|
distance entre le barycentre et la limite de la fenêtre
|
2020-01-29 13:38:50 +01:00
|
|
|
- u1 est orienté «vers la droite» et u2 «vers le haut» dans ce programme
|
2020-02-02 21:42:13 +01:00
|
|
|
- u1 et u2 apparaîssent comment des traits plutôt que des flèches car
|
|
|
|
matplotlib gère mal l'affichage de vecteurs, et plt.arrow peut dans
|
|
|
|
certains cas donner des têtes de flèche assez étranges selon le rapport
|
|
|
|
y:x du nuage de point
|
|
|
|
- le code n'a pas besoin d'être touché pour changer le comportement lors
|
|
|
|
du lancement, exemples:
|
|
|
|
|
|
|
|
* afficher un nuage de point selon cos(x) entre 0 et 1 (pas de 0.1 par
|
|
|
|
défaut et inchangeable sauf dans le code):
|
|
|
|
> python3 TP3_voisin.py -f "cos(x)" -m 1 -s
|
2020-01-29 13:38:50 +01:00
|
|
|
|
2020-02-02 21:42:13 +01:00
|
|
|
* idem mais avec un bruit de 0.5 (-g applique le bruit -n BRUIT dès la génération)
|
|
|
|
> python3 TP3_voisin.py -f "cos(x)" -m 1 -g -n 0.5 -s
|
|
|
|
|
|
|
|
* afficher 10 boucles (avec bruit, défaut=10) avec la fonction sqrt(x)
|
|
|
|
> python3 TP3_voisin.py -f "sqrt(x)" -m 10 -n 2 -l 10
|
|
|
|
le délai entre chaque itération se fait avec le paramètre -d SECONDES (ex -d 2.5)
|
|
|
|
|
|
|
|
* afficher les points contenus dans le fichier exemple.txt (format plus bas)
|
|
|
|
> python3 TP3_voisin.py -i exemple.txt -s
|
2020-01-29 13:38:50 +01:00
|
|
|
|
2020-02-02 21:42:13 +01:00
|
|
|
* les boucles, génération de bruit à l'initialisation sont également possible pour
|
|
|
|
la lecture d'un fichier
|
|
|
|
|
|
|
|
* on peut afficher l'aide via
|
|
|
|
> python3 TP3_voisin.py -h
|
|
|
|
|
|
|
|
Format du fichier d'entrée:
|
|
|
|
x0 y0
|
|
|
|
x1 y1
|
|
|
|
…
|
|
|
|
xn yn
|
|
|
|
"""
|
2020-01-29 13:38:50 +01:00
|
|
|
|
|
|
|
def calc_barycentre(l_pts):
|
|
|
|
x = sum(l_pts[:,0]) / len(l_pts)
|
|
|
|
y = sum(l_pts[:,1]) / len(l_pts)
|
|
|
|
return x, y
|
|
|
|
|
|
|
|
def centrer_points(l_pts, barycentre):
|
|
|
|
return l_pts - barycentre
|
|
|
|
|
|
|
|
def calc_matrice_corrélation(l_pts):
|
|
|
|
c_pts = centrer_points(l_pts, calc_barycentre(l_pts))
|
|
|
|
A = np.array([
|
|
|
|
[sum(pt[0] ** 2 for pt in c_pts), sum(pt[0] * pt[1] for pt in c_pts)],
|
|
|
|
[sum(pt[0] * pt[1] for pt in c_pts), sum(pt[1] ** 2 for pt in c_pts)]
|
|
|
|
])
|
|
|
|
return A
|
|
|
|
|
|
|
|
def diagonaliser_matrice22(m):
|
|
|
|
a, b, c, d = m[0][0], m[0][1], m[1][0], m[1][1]
|
|
|
|
discr = (-d - a) ** 2 - 4 * (a * d - b**2)
|
|
|
|
lambda1 = ((d + a) + discr ** .5) / 2
|
|
|
|
lambda2 = ((d + a) - discr ** .5) / 2
|
|
|
|
if lambda1 != 0:
|
|
|
|
if a - lambda1 != 0 and b != 0:
|
|
|
|
u1 = np.array([-b, (a-lambda1)])
|
|
|
|
u1 *= -1 if b > 0 else 1
|
|
|
|
else:
|
|
|
|
u1 = np.array([lambda1, 0])
|
|
|
|
u2 = np.array([-u1[1], u1[0]])
|
|
|
|
else:
|
|
|
|
if a - lambda2 != 0 and b != 0:
|
|
|
|
u2 = np.array([-b, (a-lambda2)])
|
|
|
|
u2 *= -1 if b < 0 else 1
|
|
|
|
else:
|
|
|
|
u2 = np.array([lambda2, 0])
|
|
|
|
u1 = np.array([u2[1], -u2[0]])
|
|
|
|
return u1, u2, lambda1, lambda2
|
|
|
|
|
2020-01-29 22:55:11 +01:00
|
|
|
def get_u1_u2_affichage(u1, u2, bary, dist_x, dist_y, pb, pg):
|
|
|
|
"""renvoie u1 et u2 prêt pour l'affichage en fonction du barycentre,
|
|
|
|
de la distance x et y sur l'ensemble des points, le point le plus bas et
|
|
|
|
le point le plus à gauche"""
|
|
|
|
if np.linalg.norm(u1) == np.linalg.norm(u2) == 0:
|
|
|
|
# un seul point
|
|
|
|
u1 = np.array([1, 0]) / 500
|
|
|
|
u2 = np.array([0, 1]) / 500
|
|
|
|
return u1, u2
|
|
|
|
bary = np.array((bary[0] - pg[0], bary[1] - pb[1]))
|
|
|
|
if u1[0] != 0 and u1[1] != 0:
|
|
|
|
if u1[0] > 0: # vers la droite
|
|
|
|
t1x = (dist_x - bary[0]) / u1[0]
|
|
|
|
else:
|
|
|
|
t1x = -bary[0] / u1[0]
|
|
|
|
if u1[1] > 0: # vers le haut
|
|
|
|
t1y = (dist_y - bary[1]) / u1[1]
|
|
|
|
else:
|
|
|
|
t1y = -bary[1] / u1[1]
|
|
|
|
t1 = min(t1x, t1y)
|
|
|
|
elif u1[0] != 0:
|
|
|
|
if u1[0] > 0: # vers la droite
|
|
|
|
t1 = (dist_x - bary[0]) / u1[0]
|
|
|
|
else:
|
|
|
|
t1 = -bary[0] / u1[0]
|
|
|
|
else:
|
|
|
|
if u1[1] > 0: # vers le haut
|
|
|
|
t1 = (dist_y - bary[1]) / u1[1]
|
|
|
|
else:
|
|
|
|
t1 = -bary[1] / u1[1]
|
|
|
|
if u2[0] != 0 and u2[1] != 0:
|
|
|
|
if u2[0] > 0: # vers la droite
|
|
|
|
t2x = (dist_x - bary[0]) / u2[0]
|
|
|
|
else:
|
|
|
|
t2x = -bary[0] / u2[0]
|
|
|
|
if u2[1] > 0: # vers le haut
|
|
|
|
t2y = (dist_y - bary[1]) / u2[1]
|
|
|
|
else:
|
|
|
|
t2y = -bary[1] / u2[1]
|
|
|
|
t2 = min(t2x, t2y)
|
|
|
|
elif u2[0] != 0:
|
|
|
|
if u2[0] > 0: # vers la droite
|
|
|
|
t2 = (dist_x - bary[0]) / u2[0]
|
|
|
|
else:
|
|
|
|
t2 = -bary[0] / u2[0]
|
|
|
|
else:
|
|
|
|
if u2[1] > 0: # vers le haut
|
|
|
|
t2 = (dist_y - bary[1]) / u2[1]
|
|
|
|
else:
|
|
|
|
t2 = -bary[1] / u2[1]
|
|
|
|
u1 = u1 * t1 * 2/3
|
|
|
|
u2 = u2 * t2 * 1/3
|
|
|
|
return u1, u2
|
|
|
|
|
2020-01-29 13:38:50 +01:00
|
|
|
def get_info_affichage(l_pts):
|
|
|
|
"""renvoie les vecteurs u1 et u2 pour l'affichage ainsi que xmin, xmax, ymin, ymax"""
|
|
|
|
bary = calc_barycentre(l_pts)
|
|
|
|
u1, u2 = diagonaliser_matrice22(calc_matrice_corrélation(l_pts))[:2]
|
2020-01-29 22:55:11 +01:00
|
|
|
pb = min(l_pts, key = lambda x: x[1])
|
|
|
|
pg = min(l_pts, key = lambda x: x[0])
|
2020-01-29 13:38:50 +01:00
|
|
|
dist = min(max(l_pts[:,0]) - bary[0], max(l_pts[:,1]) - bary[1])
|
|
|
|
dist_x = max(l_pts[:,0]) - min(l_pts[:,0])
|
|
|
|
dist_y = max(l_pts[:,1]) - min(l_pts[:,1])
|
|
|
|
x_range = min(l_pts[:,0]) - 0.1 * dist_x, max(l_pts[:,0]) + 0.1 * dist_x
|
|
|
|
y_range = min(l_pts[:,1]) - 0.1 * dist_y, max(l_pts[:,1]) + 0.1 * dist_y
|
2020-01-29 22:55:11 +01:00
|
|
|
u1, u2 = get_u1_u2_affichage(u1, u2, bary, dist_x, dist_y, pb, pg)
|
|
|
|
return u1, u2, x_range, y_range
|
2020-01-29 13:38:50 +01:00
|
|
|
|
|
|
|
|
|
|
|
def visualiser_nuage(l_pts):
|
|
|
|
bary = calc_barycentre(l_pts)
|
2020-01-29 22:55:11 +01:00
|
|
|
u1, u2 = get_info_affichage(l_pts)[:2]
|
2020-01-29 13:38:50 +01:00
|
|
|
ax = plt.axes()
|
|
|
|
# plot du nuage
|
|
|
|
plt.scatter(l_pts[:,0], l_pts[:,1])
|
2020-01-29 22:55:11 +01:00
|
|
|
ax.arrow(*bary, *u1, ec="red", fc="white")
|
|
|
|
ax.arrow(*bary, *u2, ec="orange", fc="white")
|
2020-01-29 13:38:50 +01:00
|
|
|
plt.show()
|
|
|
|
|
|
|
|
def animation_nuage(l_pts, loops=10, time_sleep=2, delta_bruit=3):
|
|
|
|
plt.ion()
|
|
|
|
fig = plt.figure()
|
|
|
|
ax = fig.add_subplot(111)
|
|
|
|
fig.canvas.draw()
|
|
|
|
# nuage courant (pas de do while en Python)
|
|
|
|
bary = calc_barycentre(l_pts)
|
2020-01-29 22:55:11 +01:00
|
|
|
u1, u2 = get_info_affichage(l_pts)[:2]
|
|
|
|
u1l = np.linalg.norm(u1)
|
2020-01-29 13:38:50 +01:00
|
|
|
sc = ax.scatter(l_pts[:,0], l_pts[:,1])
|
2020-01-29 22:55:11 +01:00
|
|
|
arr1 = ax.arrow(*bary, *u1, ec="red", fc="white")
|
|
|
|
arr2 = ax.arrow(*bary, *u2, ec="orange", fc="white")
|
2020-01-29 13:38:50 +01:00
|
|
|
plt.title("itération 0")
|
|
|
|
fig.canvas.flush_events()
|
|
|
|
fig.canvas.draw()
|
|
|
|
fig.canvas.flush_events()
|
|
|
|
sleep(time_sleep) # attente avant animation suivante
|
|
|
|
for i in range(loops):
|
|
|
|
plt.title("itération " + str(i+1))
|
|
|
|
# aléa
|
|
|
|
ran = np.random.random(l_pts.shape) * 2 * delta_bruit - delta_bruit
|
|
|
|
l_pts += ran
|
|
|
|
# recalcul des données
|
|
|
|
bary = calc_barycentre(l_pts)
|
2020-01-29 22:55:11 +01:00
|
|
|
u1, u2, x_range, y_range = get_info_affichage(l_pts)
|
|
|
|
u1l = np.linalg.norm(u1)
|
2020-01-29 13:38:50 +01:00
|
|
|
ax.set_xlim(*x_range)
|
|
|
|
ax.set_ylim(*y_range)
|
|
|
|
# plot
|
|
|
|
sc.set_offsets(l_pts) # repositionnement des points
|
|
|
|
arr1.remove() # on enlève les vecteurs
|
|
|
|
arr2.remove()
|
2020-01-29 22:55:11 +01:00
|
|
|
arr1 = ax.arrow(*bary, *u1, ec="red", fc="white")
|
|
|
|
arr2 = ax.arrow(*bary, *u2, ec="orange", fc="white")
|
2020-01-29 13:38:50 +01:00
|
|
|
fig.canvas.flush_events()
|
|
|
|
fig.canvas.draw() # on redessine
|
|
|
|
fig.canvas.flush_events()
|
|
|
|
sleep(time_sleep) # attente avant animation suivante
|
|
|
|
|
|
|
|
|
|
|
|
def main(argv):
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
gps = parser.add_mutually_exclusive_group(required=True)
|
2020-01-29 13:48:50 +01:00
|
|
|
gpi = parser.add_mutually_exclusive_group(required=True)
|
|
|
|
gpi.add_argument("-i", "--input",
|
2020-01-29 13:38:50 +01:00
|
|
|
help="input file for dots - if none, generate dots")
|
2020-01-29 13:48:50 +01:00
|
|
|
gpi.add_argument("-f", "--function",
|
2020-01-29 22:55:11 +01:00
|
|
|
help="function to use to generate dots, ex -f \"3*x**2+cos(x)\"")
|
|
|
|
parser.add_argument("-m", "--max-dot-xval", type=float, default=10,
|
|
|
|
help="x range of generated dots (from 0 to MAX_DOT_VAL, step=0.1) from function")
|
2020-01-29 13:38:50 +01:00
|
|
|
gps.add_argument("-s", "--show", action="store_true",
|
|
|
|
help="show cloud of dots with u1 and u2")
|
2020-01-29 22:55:11 +01:00
|
|
|
gps.add_argument("-l", "--loop", type=int,
|
2020-01-29 13:38:50 +01:00
|
|
|
help="number of loop to show/perform on randomized dots from originals ones")
|
2020-01-29 22:55:11 +01:00
|
|
|
parser.add_argument("-g", "--generate-with-noise", action="store_true",
|
|
|
|
help="apply noise on dots generation")
|
2020-01-29 13:38:50 +01:00
|
|
|
parser.add_argument("-d", "--delay", type=float, default=2,
|
|
|
|
help="amount of time between each loop")
|
|
|
|
parser.add_argument("-n", "--noise", type = float, default=10,
|
2020-01-29 22:55:11 +01:00
|
|
|
help="maximum value of the noise applied between each step and on generation if option -g and -f called")
|
2020-01-29 13:38:50 +01:00
|
|
|
if not argv:
|
|
|
|
parser.print_usage()
|
|
|
|
sys.exit(0)
|
|
|
|
args = parser.parse_args(argv)
|
|
|
|
if args.input:
|
|
|
|
if not os.path.isfile(args.input):
|
|
|
|
print("File", args.input, "not found.", file=sys.stderr)
|
|
|
|
sys.exit(1)
|
|
|
|
else:
|
2020-02-02 21:42:13 +01:00
|
|
|
l_pts = np.loadtxt(args.input)
|
2020-01-29 13:38:50 +01:00
|
|
|
else:
|
2020-01-29 22:55:11 +01:00
|
|
|
n = args.max_dot_xval
|
2020-01-29 13:48:50 +01:00
|
|
|
f = lambda x: eval(args.function) # fonction génératrice de la ditribution
|
2020-02-02 21:42:13 +01:00
|
|
|
l_pts = np.array([[i, f(i)] for i in np.arange(0, n, 0.1)])
|
|
|
|
if args.generate_with_noise:
|
2020-01-29 22:55:11 +01:00
|
|
|
delta_bruit = args.noise # bruit généré, 0 si on en veut aucun
|
2020-02-02 21:42:13 +01:00
|
|
|
bruit = np.random.random(len(l_pts)) * 2 * delta_bruit - delta_bruit
|
|
|
|
l_pts[:,1] += bruit # application du bruit
|
2020-01-29 13:38:50 +01:00
|
|
|
if args.show:
|
|
|
|
visualiser_nuage(l_pts)
|
|
|
|
elif args.loop:
|
|
|
|
animation_nuage(l_pts, args.loop, args.delay, args.noise)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main(sys.argv[1:])
|