From 31f5a04bbc6a0fd03f267e9d41a2466f962152b5 Mon Sep 17 00:00:00 2001 From: Martin Delcourt <martin.delcourt@uclouvain.be> Date: Tue, 29 Oct 2019 16:43:53 +0100 Subject: [PATCH] Adding Chapter 6 & 7 --- Chapter_6/csvhisto.py | 53 ++++++++++++ Chapter_6/edosolver.py | 86 +++++++++++++++++++ Chapter_6/histogram.py | 76 +++++++++++++++++ Chapter_6/mplhisto.py | 20 +++++ Chapter_6/playing_with_edo.py | 44 ++++++++++ Chapter_7/Vecteur.py | 133 ++++++++++++++++++++++++++++++ Chapter_7/complexe.py | 102 +++++++++++++++++++++++ Chapter_7/not_implemented_expl.py | 76 +++++++++++++++++ 8 files changed, 590 insertions(+) create mode 100644 Chapter_6/csvhisto.py create mode 100644 Chapter_6/edosolver.py create mode 100644 Chapter_6/histogram.py create mode 100644 Chapter_6/mplhisto.py create mode 100644 Chapter_6/playing_with_edo.py create mode 100644 Chapter_7/Vecteur.py create mode 100644 Chapter_7/complexe.py create mode 100644 Chapter_7/not_implemented_expl.py diff --git a/Chapter_6/csvhisto.py b/Chapter_6/csvhisto.py new file mode 100644 index 0000000..8ba596b --- /dev/null +++ b/Chapter_6/csvhisto.py @@ -0,0 +1,53 @@ +from histogram import Histogram +import csv + +class CSVHisto(Histogram): + + def save(self, file_name): + """Save histogram as CSV file""" + with open(file_name,'w') as file: + writer = csv.writer(file) + writer.writerow(["bin id","bin center", "n entries"]) + bin_size = (self._b - self._a)*1./self._n_bins + for bin_id, bin_content in enumerate(self._data): + writer.writerow([bin_id, self._a+(bin_id+0.5)*bin_size, bin_content]) + + + def load(self, file_name): + """Read histogram from CSV file""" + self._data = [] + line_id = 0 + first_bin_center = 0 + second_bin_center = 0 + with open(file_name,'r') as file: + reader = csv.reader(file) + for row in reader: + line_id += 1 + if line_id == 1: + #Skipping line containing column titles + continue + else: + if line_id == 2: + first_bin_center = float(row[1]) + elif line_id == 3: + second_bin_center = float(row[1]) + + self._data.append(float(row[2])) + bin_width = second_bin_center - first_bin_center + self._a = first_bin_center-0.5*bin_width + self._b = self._a + len(self._data) * bin_width + self._n_bins = len(self._data) + + + +if __name__ == "__main__": + h2 = CSVHisto(100,-5,5) + from random import gauss + for i in range(int(100000)): + h2.fill(gauss(0,1)) + h2.save("test.csv") + + h3 = CSVHisto() + h3.load("test.csv") + h3.draw() + diff --git a/Chapter_6/edosolver.py b/Chapter_6/edosolver.py new file mode 100644 index 0000000..bf3e484 --- /dev/null +++ b/Chapter_6/edosolver.py @@ -0,0 +1,86 @@ +import matplotlib.pyplot as plt + + +class EDOSolver: + def __init__(self, f, y_0, t_0, t_max, dt, store_dt = 1): + self.f = f + self.y_stored = [y_0[:]] + self.time = [t_0] + self.t_max = t_max + self.h = dt + self.store_dt = store_dt + + def solve(self): + raise NotImplemented + + def draw(self): + data = [] + for i in range(len(self.y_stored[0])): + data.append([entry[i] for entry in self.y_stored]) + plt.plot(self.time,data[i]) + + plt.show() + + def draw_diff(self,other_solver): + data = [] + for i in range(len(self.y_stored[0])): + data.append([self.y_stored[j][i] - other_solver.y_stored[j][i] for j in range(len(self.y_stored))]) + plt.plot(self.time,data[i]) + + plt.show() + + +class EulerSolver(EDOSolver): + def solve(self): + t = self.time[0] + y = self.y_stored[0][:] + + while abs(t - self.t_max) >= 0.5*self.h and t < self.t_max: + if abs(t - (self.time[-1] + self.store_dt)) <= 0.5*self.h: + self.time.append(t) + self.y_stored.append(y[:]) + + dy = self.f(t,y) + for i,x in enumerate(dy): + y[i] += self.h*x + t += self.h + self.time.append(t) + self.y_stored.append(y[:]) + + + +class RK4Solver(EDOSolver): + def solve(self): + t = self.time[0] + y = self.y_stored[0][:] + while abs(t - self.t_max) >= 0.5*self.h and t < self.t_max: + if abs(t - (self.time[-1] + self.store_dt)) <= 0.5*self.h: + self.time.append(t) + self.y_stored.append(y[:]) + + dy = self.f(t,y) + k1 = [self.h*x for x in dy] + + + y_k2 = [y[i] + 0.5*k1[i] for i in range(len(k1))] + dy_2 = self.f(t+0.5*self.h,y_k2) + k2 = [self.h*x for x in dy_2] + + + y_k3 = [y[i] + 0.5*k2[i] for i in range(len(k2))] + dy_3 = self.f(t+0.5*self.h, y_k3) + k3 = [self.h*x for x in dy_3] + + y_k4 = [y[i] + k3[i] for i in range(len(k3))] + dy_4 = self.f(t+self.h, y_k4) + k4 = [self.h*x for x in dy_4] + + y = [y[i] + 1./6 * (k1[i] + 2*k2[i] + 2*k3[i] + k4[i]) for i in range(len(y))] + t += self.h + self.time.append(t) + self.y_stored.append(y[:]) + + + + + diff --git a/Chapter_6/histogram.py b/Chapter_6/histogram.py new file mode 100644 index 0000000..4765178 --- /dev/null +++ b/Chapter_6/histogram.py @@ -0,0 +1,76 @@ + + +class Histogram: + def __init__(self, n_bins = 0, a = 0, b = 0): + """Crée un histograme de n_bins réparties uniformément entre a et b""" + self._data = [0 for i in range(n_bins)] + self._n_bins = n_bins + self._a = a + self._b = b + + def fill(self, x): + """Remplis la bin correspondant à la valeur 'x'""" + bin_number = int((x-self._a)*self._n_bins/(self._b-self._a)) + if bin_number >= 0 and bin_number < self._n_bins: + self._data[bin_number] += 1 + return bin_number + else: + print("Warning, {} out of bounds.".format(x)) + return -1 + + def get_maximum(self): + """Retourne le maximum de l'histogramme""" + maximum = self._data[0] + for x in self._data[1:]: + if x > maximum: + maximum = x + return maximum + + + def draw(self): + """Affiche l'histogramme en console""" + y_max = self.get_maximum() + scale = 1. + if y_max > 50: + scale = 50*1./y_max + y_max = 50 + # vertical axis : 50 pxl + y = y_max + 1 + while y > 0: + to_print = "" + if (y == y_max+1): + to_print = "y ^" + else: + to_print = " |" + + for x in range(self._n_bins): + if self._data[x] >= y/scale: + to_print += "#" + else: + to_print += " " + print (to_print) + y-=1 + + print (" -"+"+"+(self._n_bins-1)*"-"+">") + print (" |"+(self._n_bins-1)*" "+"x") + if scale != 1: + print ("y-axis scale : 1 # = {}".format(1./scale)) + + + + +if __name__=="__main__": + h = Histogram(10,0,1) + h.fill(0.5) + h.fill(0.5) + h.fill(0.5) + h.fill(0.1) + h.fill(2) + h.fill(-1) + h.draw() + + h2 = Histogram(100,-5,5) + from random import gauss + for i in range(int(100000)): + h2.fill(gauss(0,1)) + h2.draw() diff --git a/Chapter_6/mplhisto.py b/Chapter_6/mplhisto.py new file mode 100644 index 0000000..98fa1a1 --- /dev/null +++ b/Chapter_6/mplhisto.py @@ -0,0 +1,20 @@ +import numpy as np +import matplotlib.pyplot as plt +from histogram import Histogram + +class MPLHisto(Histogram): + def draw(self): + bin_size = (self._b - self._a)*1./self._n_bins + bins = [self._a + i*bin_size for i in range(len(self._data))] + plt.hist(bins[:],bins,weights = self._data) + plt.show() + +if __name__ == "__main__": + h2 = MPLHisto(100,-5,5) + from random import gauss + for i in range(int(100000)): + h2.fill(gauss(0,1)) + h2.draw() + + + diff --git a/Chapter_6/playing_with_edo.py b/Chapter_6/playing_with_edo.py new file mode 100644 index 0000000..8a647a7 --- /dev/null +++ b/Chapter_6/playing_with_edo.py @@ -0,0 +1,44 @@ +from edosolver import RK4Solver, EulerSolver +from math import pi +def free_fall_acceleration(t,y): + # y = [x,v] + dv = - 9.81 - 0.00315 * abs(y[1]) * y[1] + dx = y[1] + + return [dx,dv] + +def oscillation(t,y): + return[y[1],-y[0]] + + + +start = 0 +stop = 30 +dt = 0.01 +dt_store = 0.1 + + + + +solution_oscillation_euler = EulerSolver(oscillation,[1,0],start,stop,dt,dt_store) +solution_oscillation_euler.solve() +solution_oscillation_euler.draw() + +solution_oscillation_rk4 = RK4Solver(oscillation,[1,0],start,stop,dt,dt_store) +solution_oscillation_rk4.solve() +solution_oscillation_rk4.draw() + +solution_oscillation_rk4.draw_diff(solution_oscillation_euler) + + + +solution_free_fall_euler = EulerSolver(free_fall_acceleration,[0,0], start, stop, dt,dt_store) +solution_free_fall_euler.solve() +solution_free_fall_euler.draw() + + +solution_free_fall_rk4 = RK4Solver(free_fall_acceleration,[0,0], start, stop, dt,dt_store) +solution_free_fall_rk4.solve() +solution_free_fall_rk4.draw() + +solution_free_fall_rk4.draw_diff(solution_free_fall_euler) diff --git a/Chapter_7/Vecteur.py b/Chapter_7/Vecteur.py new file mode 100644 index 0000000..995b327 --- /dev/null +++ b/Chapter_7/Vecteur.py @@ -0,0 +1,133 @@ +import math + +class Vecteur: + """Classe vecteur de nombres de dimension arbitraire.""" + + def __init__(self, *coord): + """Initialise un vecteur de coordonnees "coord" """ + self._coord = [c for c in coord] + + def get_vec(self): + """Retourne un tuple contenant les coordonnees du vecteur""" + return tuple(self._coord) + + def set(self,*coord): + """Donne les coordonnees "coord" au vecteur""" + self._coord = [c for c in coord] + + def set_from_list(self,coord_list): + """Donne les coordonnées venant de la liste 'coord_list'.""" + self._coord = [c for c in coord_list ] + + + def set_component(self, index, value): + """Donne la valeur 'value' a la composante 'index' du vecteur""" + self._coord[index] = value + + def norme(self): + """Retourne la norme du vecteur""" + nn = 0 + for x in self._coord: + nn += x*x + return math.sqrt(nn) + + def prod_scal(self, vec): + """Retourne le produit scalaire du vecteur avec vec""" + if not len(vec._coord) == len(self._coord): + print("Error, vectors of different dimensions...") + return 0 + ps = 0 + for i in range(len(self._coord)): + ps+=self._coord[i] * vec._coord[i] + return ps + + def prod_vec(self,vec): + """Retourne le produit vectoriel du vecteur avec vec""" + if not len(vec._coord) == 3 or not len(self._coord)==3: + raise TypeError("vector product not possible on vectors of dimension != 3 ({} and {} provided)".format(len(self._coord),len(other._coord))) + print("Error, only possible for dim 3 vectors.") + return Vecteur(0,0,0) + return Vecteur( + self._coord[1]*vec._coord[2] - self._coord[2]*vec._coord[1], + -self._coord[0]*vec._coord[2] + self._coord[2]*vec._coord[0], + self._coord[0]*vec._coord[1] - self._coord[1]*vec._coord[0]) + + def __repr__(self): + return str(self._coord) + + def __abs__(self): + return self.norme() + + def __add__(self,other): + try: + addition = Vecteur() + addition.set_from_list([ x+y for (x,y) in zip(self._coord, other._coord) ]) + return addition + except: + return NotImplemented + + def __radd__(self,other): + return self + other + + def __mul__(self,other): + if isinstance(other,Vecteur): + if len(other._coord) != len(self._coord): + raise TypeError("Vector size inconsistent ({} and {})".format(len(self._coord),len(other._coord))) + else: + ps = 0 + for (x,y) in zip(self._coord, other._coord): + ps+= x*y + return ps + else: + try: + multiplied = Vecteur() + multiplied.set_from_list([other*x for x in self._coord]) + return multiplied + except: + return NotImplemented + + def __rmul__(self,other): + return self*other + + def __truediv__(self,other): + try: + divided = Vecteur() + divided.set_from_list([x/other for x in self._coord]) + return divided + except: + return NotImplemented + + + def polar_coordinates(self): + """Returns the polar coordinates (r,theta) of the vector if the dimension is 2""" + if len(self._coord) != 2: + raise TypeError("Unable to return polar coordinates. Dimension should be 2 (was {})".format(len(self._coord))) + if self._coord[0] == 0: + return (abs(self), -math.pi/2 + 2*(self._coord[1] > 0)) + return (abs(self), math.atan(self._coord[1]/self._coord[0])) + + + +if __name__ == '__main__': + print("Debugging...") + u = Vecteur() + u.set(1,2,3) + print("vecteur u = {} et |u| = {}".format(u,u.norme())) + + v = Vecteur(4,5,6) + print("vecteur v = {} et |v| = {}".format(v,v.norme())) + + print("u.v = {}".format(u.prod_scal(v))) + print("v.u = {}".format(v.prod_scal(u))) + w = u.prod_vec(v) + print("w = u x v = {}".format(w)) + print(" v x u = {}".format(v.prod_vec(u))) + print("w.u = {} ; u.w = {}".format(w.prod_scal(u),u.prod_scal(w))) + print("w.v = {} ; v.w = {}".format(w.prod_scal(v),v.prod_scal(w))) + + print(u*3) + print(3*u) + print(u*v) + print(u/3) + + diff --git a/Chapter_7/complexe.py b/Chapter_7/complexe.py new file mode 100644 index 0000000..c1ccf43 --- /dev/null +++ b/Chapter_7/complexe.py @@ -0,0 +1,102 @@ +import math + +class complexe: + def __init__(self,real = 0, imag = 0): + """ Initialise complex number with real and imaginary parts respectively set to real and imag""" + self.real = real + self.imag = imag + + def complexe_conjugue(self): + """ Returns the complex conjugate""" + return complexe(self.real,-self.imag) + + def __repr__(self): + if self.imag >= 0: + return "{}+{}i".format(self.real,self.imag) + else: + return "{}{}i".format(self.real,self.imag) + + def __add__(self,other): + if isinstance(other,complexe): + return complexe(self.real + other.real, self.imag+other.imag) + else: + try: + return complexe(self.real+other, self.imag) + except: + return NotImplemented + + def __radd__(self,other): + return self + other + + def __mul__(self,other): + if isinstance(other,complexe): + return complexe(self.real*other.real - self.imag * other.imag, + self.real*other.imag + self.imag * other.real) + else: + try: + return complexe(other*self.real,other*self.imag) + except: + return NotImplemented + + def __rmul__(self,other): + return self*other + + def __abs__(self): + """Returns the module of the complex number""" + return math.sqrt(self.real**2 + self.imag**2) + + def __truediv__(self,other): + if isinstance(other,complexe): + return self*self.complexe_conjugue()*1./(abs(self)**2) + else: + try: + return complexe(self.real/other,self.imag/other) + except: + return NotImplemented + + def __rtruediv__(self,other): + return self/other + + def __eq__(self,other): + if isinstance(other,complexe): + return self.real == other.real and self.imag == other.imag + else: + try: + return self.real == other and self.imag == 0 + except: + return NotImplemented + def __ne__(self,other): + return not self == other + + def __lt__(self,other): + if isinstance(other,complexe): + return abs(self) < abs(other) + else: + try: + return abs(self) < other + except: + return NotImplemented + def __le__(self,other): + if isinstance(other,complexe): + return abs(self) <= abs(other) + else: + try: + return abs(self) <= other + except: + return NotImplemented + + def __gt__(self,other): + return other < self + + def __ge__(self,other): + return other <= self + + def polar(self): + if self.real == 0: + return (abs(self),math.pi/2) + else: + return (abs(self),math.atan(self.imag/self.real)) + + +if __name__ == "__main__": + print("For explanation on all the 'NotImplemented in the code, please check-out not_implemented_expl.py") diff --git a/Chapter_7/not_implemented_expl.py b/Chapter_7/not_implemented_expl.py new file mode 100644 index 0000000..e44b830 --- /dev/null +++ b/Chapter_7/not_implemented_expl.py @@ -0,0 +1,76 @@ + +print("Imaginons deux programmeurs python différents.") +print("Le premier tente de construire une classe 'vecteur à deux dimension' rudimentaire") +print("Il crée la classe 'first_class' et définit l'addition entre deux vecteurs") +print("Et il a pensé à rajouter le 'NotImplemented' dans ses opérations.") + + + +class first_class: + def __init__(self, x, y): + self.x = x + self.y = y + + def __add__(self,other): + try: + return(first_class(self.x+other.x,self.y+other.y)) + except: + print("Cannot use first_class.__add__ ---> returning NotImplemented") + return NotImplemented + + def __radd__(self,other): + return self+other + + +print("Un second programmeur crée une autre classe, 'second_class' qui a pour seul but de compter le nombre de fois qu'un tel objet aura été dans une addition") +print("On voudrait que cet objet puisse être additionné avec n'importe quel autre objet.") + +class second_class: + def __init__(self): + self.addition_counter = 0 + + def __repr__(self): + return "Was added {} time(s)".format(self.addition_counter) + + def __add__(self,other): + new_addition = second_class() + new_addition.addition_counter = self.addition_counter + 1 + return new_addition + + def __radd__(self,other): + print("second_class.__radd__ called") + return self + other + + +print("En particulier, on voit que ça devrait marcher avec la classe de l'autre programmeur\n\n") +v1 = first_class(0,0) +counter = second_class() +print("v1 + counter va appeler v1.__add__, mais v1 ne sait pas que counter existe !") +print("Il renvoie donc 'NotImplemented' et on appelle counter.__radd__, qui lui fonctionne bien.") +counter = v1 + counter +print(counter) +print("\n\n\n") +print("Maintenant, regardons ce même exemple si le premier programmeur n'avait pas renvoyé 'NotImplemented'") + + +class first_class_without_protection: + def __init__(self, x, y): + self.x = x + self.y = y + + def __add__(self,other): + print("Calling first_class_without_protection.__add__") + return(first_class_without_protection(self.x+other.x,self.y+other.y)) + + def __radd__(self,other): + return self+other + + + +v2 = first_class_without_protection(0,0) +counter2 = second_class() +print("Dans ce cas-ci, la méthode __add__ de la première classe va planter lors de l'addition, sans laisser la possibilité à second_class d'effectuer __radd__") +counter = v2 + counter + + + -- GitLab