CentraleSupélec LMF, UMR CNRS 9021
Département informatique Laboratoire Méthodes Formelles
Bât Breguet, 3 rue Joliot-Curie Bât 650 Ada Lovelace, Université Paris Sud
91190 Gif-sur-Yvette, France Rue Noetzlin, 91190 Gif-sur-Yvette, France
TD n°16 et 17 SIP 2019

Éléments de corrigé

Première partie - questions 1 à 11

# Introduction to visualization
# Question 1
import matplotlib.pyplot as plt

def first_test() :
  plt.plot([1,2,3,4])  # Single array: x coord is 0..n
  plt.show()

  # Two plots on the same figure
  plt.plot([1,2,3,4], [1,4,9,16])
  plt.plot([1,2,3,4], [1,8,27,64])
  plt.show()

  # Two plots on the same figure, alternative version
  plt.plot([1,2,3,4], [1,4,9,16], [1,2,3,4], [1,8,27,64])
  plt.show()

# first_test()

# Title and so on
import numpy as np

def example() :
  city=['Delhi','Beijing','Washington','Tokyo','Moscow']
  Gender=['Male','Female']
  pos = np.arange(len(city))
  bar_width = 0.35
  Happiness_Index_Male=[60,40,70,65,85]
  Happiness_Index_Female=[30,60,70,55,75]
  plt.bar(pos, Happiness_Index_Male, bar_width, color='blue', edgecolor='black')
  plt.bar(pos+bar_width, Happiness_Index_Female, bar_width, color='pink', edgecolor='black')
  plt.xticks(pos, city)
  plt.xlabel('City', fontsize=16)
  plt.ylabel('Happiness_Index', fontsize=16)
  plt.title('Group Barchart - Happiness index across cities By Gender', fontsize=18)
  plt.legend(Gender,loc=2)
  plt.show()

# example()

# Visualizing functions
# Question 2
import math

def center_axes(axes):
  # Move left y-axis and bottom x-axis to centre, passing through (0,0)
  axes.spines['left'].set_position('zero')
  axes.spines['bottom'].set_position('zero')
  # Eliminate upper and right axes
  axes.spines['right'].set_color('none')
  axes.spines['top'].set_color('none')
  # Show ticks in the left and lower axes only
  axes.xaxis.set_ticks_position('bottom')
  axes.yaxis.set_ticks_position('left')

# Without numpy
def plt_sine() :
  x = [xx/100 for xx in range(-200, 201, 2)]
  fy = [math.sin(math.pi * xx) for xx in x]
  gy = [math.sin(2 * math.pi * xx) for xx in x]
  #
  center_axes(plt.axes())
  plt.plot(x, fy, x, gy)
  plt.show()

# Without numpy
def plt_sine_np() :
  x = np.linspace(-2, 2, 200)
  fy = np.sin(math.pi * x)
  gy = np.sin(2 * math.pi * x)
  #
  center_axes(plt.axes())
  plt.plot(x, fy, x, gy)
  plt.show()

# plt_sine()
# plt_sine_np()

# Question 3
def two_figures() :
  x = np.linspace(-2, 2, 200)
  plt.subplot(211) # 2 rows, 1 column, figure #1
  plt.plot(x, np.sin(math.pi * x))
  plt.subplot(212) # 2 rows, 1 column, figure #2
  plt.plot(x, np.sin(2 * math.pi * x))
  plt.show()

# two_figures()

# Question 4
def two_figures_style() :
  x = np.linspace(-2, 2, 200)
  plt.subplot(211) # 2 rows, 1 column, figure #1
  plt.plot(x, np.sin(math.pi * x), 'rv')
  plt.subplot(212) # 2 rows, 1 column, figure #2
  plt.plot(x, np.sin(2 * math.pi * x), 'b+')
  plt.show()

# two_figures_style()

# Digits of pi
# Question 5

def plain_red_square() :
  n = 40
  img = [[[128, 29, 58] for j in range(n)] for i in range(n)]
  plt.imshow(img)
  plt.axis('off') # Do not display the axes
  plt.show()

# plain_red_square()

# Question 6
colors = [
  [230 ,230 ,230],  # white
  [255 ,65 ,54],    # red
  [255 ,53 ,27],    # orange
  [255 ,220 ,0],    # yellow
  [1 ,255 ,112],    # light green
  [61 ,153 ,112],   # dark green
  [57 ,204 ,204],   # light blue
  [0 ,116 ,217],    # dark blue
  [177 ,13 ,201],   # purple
  [240 ,18 ,190]    # pink
]

def color_square_digits(dig, n):
  img=[[[0,0,0] for i in range(n)] for j in range(n)]
  i = 0
  j = 0
  for k in range(len(dig)):
    img[i][j] = colors[int(dig[k])]
    j = (j + 1) % n
    if j == 0:
      i = (i + 1) % n
      if i == 0 :
        break
  plt.imshow(img)
  plt.axis('off')
  plt.show()

def frac(a, b, n) :
  """
  return the n first digits of the fractional part of a/b
  """
  digits = ""
  for d in range(n) :
    a *= 10
    digits += str((a // b) % 10)
  return digits

# color_square_digits(frac(1,7,1600), 40)

# Question 7
big_pi = """\
31415926535897932384626433832795028841971693993751058209749445923078164062862\
089986280348253421170679821480865132823066470938446095505822317253594081284811\
174502841027019385211055596446229489549303819644288109756659334461284756482337\
867831652712019091456485669234603486104543266482133936072602491412737245870066\
063155881748815209209628292540917153643678925903600113305305488204665213841469\
519415116094330572703657595919530921861173819326117931051185480744623799627495\
673518857527248912279381830119491298336733624406566430860213949463952247371907\
021798609437027705392171762931767523846748184676694051320005681271452635608277\
857713427577896091736371787214684409012249534301465495853710507922796892589235\
420199561121290219608640344181598136297747713099605187072113499999983729780499\
510597317328160963185950244594553469083026425223082533446850352619311881710100\
031378387528865875332083814206171776691473035982534904287554687311595628638823\
537875937519577818577805321712268066130019278766111959092164201989380952572010\
654858632788659361533818279682303019520353018529689957736225994138912497217752\
834791315155748572424541506959508295331168617278558890750983817546374649393192\
550604009277016711390098488240128583616035637076601047101819429555961989467678\
374494482553797747268471040475346462080466842590694912933136770289891521047521\
620569660240580381501935112533824300355876402474964732639141992726042699227967\
823547816360093417216412199245863150302861829745557067498385054945885869269956\
909272107975093029553211653449872027559602364806654991198818347977535663698074\
265425278625518184175746728909777727938000816470600161452491921732172147723501\
414419735685481613611573525521334757418494684385233239073941433345477624168625\
189835694855620992192221842725502542568876717904946016534668049886272327917860\
857843838279679766814541009538837863609506800642251252051173929848960841284886\
269456042419652850222106611863067442786220391949450471237137869609563643719172\
"""

# color_square_digits(big_pi, 40)
# color_square_digits(str(math.pi).replace('.', ''), 40)


# Koch snowflake
import cmath  # complex numbers

# Question 8 to 11
def iso_triangle() :
  a = complex(0,0)
  b = complex(1,0)
  ac = (b-a) * cmath.rect(1, math.pi/3)
  c = a + ac
  return ([a.real, c.real, b.real, a.real],
          [a.imag, c.imag, b.imag, a.imag])

# plt.axes().set_aspect('equal')
# isot = iso_triangle()
# plt.plot(isot[0], isot[1])
# plt.show()

def koch_seg(segx, segy, pos = 0) :
  # We use complex numbers to represent points
  a = complex(segx[pos], segy[pos])         # get point A
  b = complex(segx[pos + 1], segy[pos + 1]) # and point B
  ab = b - a      # Compute vector AB
  ac = ab / 3     # Cut the segment at 1/3 of its length
  c = a + ac      # to find point C
  # The next point is at 1/3 of AB, turned by 60° (pi/3), relative to C
  cd = ac * cmath.rect(1, math.pi/3)  # Can also be written ac * complex(math.cos(math.pi/3), math.sin(math.pi/3))
  d = c + cd
  # The last point is at 2/3 of AB
  ae = 2 * ab / 3
  e = a + ae
  return ([a.real, c.real, d.real, e.real, b.real],
          [a.imag, c.imag, d.imag, e.imag, b.imag])

def koch_iter(segsx, segsy) :
  finalx = []
  finaly = []
  for pos in range(len(segsx) - 1) :
    nsegs = koch_seg(segsx, segsy, pos)
    finalx = finalx + nsegs[0]
    finaly = finaly + nsegs[1]
  return (finalx, finaly)

def vonKoch(n) :
  k = iso_triangle()
  for i in range(n) :
    k = koch_iter(k[0], k[1])
  plt.axes().set_aspect('equal')
  plt.plot(k[0], k[1])
  plt.show()

# vonKoch(3)
# vonKoch(5)

Deuxième partie - questions 12 à 27

# Pi

# Question 12
import random

def approx_pi(n) :
  inside = 0
  total = 0
  while total < n :
    x = random.uniform(-1, 1)
    y = random.uniform(-1, 1)
    total += 1
    if math.hypot(x, y) < 1 :
      inside += 1
  almost_pi = 4 * (inside / total)
  rel_error = math.fabs(almost_pi - math.pi) / math.pi
  return (almost_pi, rel_error)

# print(approx_pi(1000))
# print(approx_pi(100000))

# Questions 13 and 14
import matplotlib.patches as patches

def plot_monte_carlo() :
  fig = plt.figure()     # Create a figure
  fig.patch.set_facecolor('white') # Set its background to white
  axes = plt.axes()      # Create the axes of the figure
  axes.set_aspect('equal')   # Set their aspect ratio to 1
  axes.set_axis_off()    # Do not display the axes
  axes.set_xlim(left=-1, right=1.1)  # The .1 is for avoiding to clip the rectangle
  axes.set_ylim(bottom=-1.1, top=1)
  # Add the rectangle and the circle
  axes.add_patch(patches.Circle((0, 0), 1, linewidth=1, fill=False, edgecolor='blue'))
  axes.add_patch(patches.Rectangle((-1, -1), 2, 2, linewidth=1, fill=False))
  return (fig, axes)

def approx_pi_plot(n) :
  (fig, axes) = plot_monte_carlo()
  inside = 0
  total = 0
  while total < n :
    x = random.uniform(-1, 1)
    y = random.uniform(-1, 1)
    total += 1
    if math.hypot(x, y) < 1 :
      inside += 1
      plt.plot(x, y, 'ro', markersize=2)
    else:
      plt.plot(x, y, 'go', markersize=2)
    plt.pause(0.01)  # Pause for animation
  almost_pi = 4 * (inside / total)
  rel_error = math.fabs(almost_pi - math.pi) / math.pi
  axes.set_title("n = {0}  π = {1:f}  error = {2:.2%}".format(n, almost_pi, rel_error))
  plt.pause(0.01)  # To force the update of the title
  plt.show()
  return (almost_pi, rel_error)

# print(approx_pi_plot(500))

##
# Mandelbrot set
##

# Question 15
def module_real_img(z) :
  return math.hypot(z.real, z.imag)

def module_conj(z) :
  return math.sqrt((z.conjugate()*z).real)

# Question 16
def mandelfunc(c, z) :
  return z*z + c

def mandelbrot(c, n_max) :
  z = 0
  n = 0
  while n < n_max and abs(z) < 2 :
    n += 1
    z = mandelfunc(c, z)
  return n

# Question 17
def mandelbrot_set(xmin, xmax, ymin, ymax, xnum, ynum, n_max) :
  x = [xmin + i * (xmax - xmin)/xnum for i in range(xnum+1)]
  y = [ymin + i * (ymax - ymin)/ynum for i in range(ynum+1)]
  z = [[mandelbrot(complex(x[i], y[j]), n_max) for i in range(xnum)] for j in range(ynum)]
  return z

# Version numpy (much slower than the raw Python version)
import numpy as np

def mandelbrot_set_np(xmin, xmax, ymin, ymax, xnum, ynum, n_max) :
  x = np.linspace(xmin, xmax, xnum)
  y = np.linspace(ymin, ymax, ynum)
  z = np.empty((ynum, xnum))
  for i in range(xnum) :
    for k in range(ynum) :
      z[k,i] = mandelbrot(x[i] + 1j * y[k], n_max)
  return z


# Question 18
import matplotlib.colors as colors

def plot_mandelbrot(xmin, xmax, ymin, ymax, xnum, ynum, n_max) :
  z = mandelbrot_set(xmin, xmax, ymin, ymax, xnum, ynum, n_max)
  mycolors = [(1,0,0), (0,1,0), (0,0,1), (0,0,0)]
  colmap = colors.LinearSegmentedColormap.from_list("mandelbrot", mycolors, 16)
  plt.imshow(z, cmap=colmap, interpolation='bicubic')
  plt.show()

# plot_mandelbrot(-2.25, 0.75, -1.25, 1.25, 1500, 1250, 64)

# Ranking the web

# Question 19
def subdiv_one(n):
  elems = [random.random() for _  in range(n)]
  norm = sum(elems)
  return [elem / norm for elem in elems]

# Question 20
def create_transition_matrix(size) :
  # First, we create a matrix whose lines sum up to 1
  m = np.matrix([subdiv_one(size) for i in range(size)])
  # and we transpose it to have columns that sum up to 1
  return m.transpose()

# Questions 21 and 22
# https://en.wikipedia.org/wiki/Matrix_norm
import numpy.linalg as la

def page_rank(size, epsilon) :
  m = create_transition_matrix(size)
  s0 = [[1/size] for _ in range(size)]  # 1 column matrix
  rank = 0
  norm = la.norm((m ** (rank + 1) - m ** rank), np.inf)
  while (norm/size > epsilon) :
    rank += 1
    norm = la.norm((m ** (rank + 1) - m ** rank), np.inf)
  return (m ** rank) * s0

# More efficient version, which does not recompute m**k at
# each iteration. It also compare the norm directly to epsilon,
# without dividing by the number of pages
def page_rank_lowcost(size, epsilon) :
  m = create_transition_matrix(size)
  mk = m
  s0 = [[1/size] for _ in range(size)]
  rank = 0
  norm = la.norm(m, np.inf)
  while (norm > epsilon) :
    rank += 1
    mk2 = mk * m
    norm = la.norm((mk2 - mk), np.inf)
    mk = mk2
  return mk * s0

# Prey-predator

# Question 24
# The function that gives the derivative from the current values
def f(x, y, a, b) :
  return x * (a - b * y)

# Question 25
# The function that gives the next value of z (which is either x or y)
# from the current value of z, of the other variable (t), the parameters
# of the problem (a and b) and the integration step (Euler explicit scheme).
def next_step(zn, tn, a, b, step) :
  return zn + step * f(zn, tn, a, b)

# Question 26
def populations(x0, y0, alpha, beta, delta, gamma, step, n) :
  x, y = x0, y0
  lx, ly = [x0], [y0]
  for _ in range(n) :
    # We have dx/dt =  alpha*x - beta*x*y
    # and     dy/dt = -gamma*y + delta*y*x
    # hence the parameters of next_step
    x, y = next_step(x, y, alpha, beta, step), \
           next_step(y, x, -gamma, -delta, step)
    lx.append(x)
    ly.append(y)
  return lx, ly

# Question 27
def display_populations() :
  # Initial populations
  x0, y0 = 0.9, 0.9
  # Parameters
  alpha, beta, delta, gamma = 2/3, 4/3, 1, 1
  # Temporal resolution
  resolution = 1000
  # Compute until that time
  time_max = 20
  # Time step size
  step = 1 / resolution
  # Total number of steps
  n = time_max * resolution
  ## Compute the populations
  prey, pred = populations(x0, y0, alpha, beta, delta, gamma, step, n)
  # Plot the result
  time = [step * i for i in range(n+1)]
  plt.subplot(121)
  # First subplot for the evolution of populations through time
  plt.title("Population versus time")
  plt.plot(time, prey, label="Preys")
  plt.plot(time, pred, label="Predators")
  plt.xlabel("Time")
  plt.ylabel("Population")
  plt.legend(loc='upper left')
  # Second subplot for the phase diagram
  plt.subplot(122)
  plt.title("Predators versus preys")
  plt.plot(prey, pred)
  plt.xlabel("Preys")
  plt.ylabel("Predators")
  plt.show()

# display_populations()