Skip to content

Python in knjižnice

Zdaj, ko znamo graditi enostavne vsebnike, je čas, da pripravimo vsebnike, ki jih bomo zaganjali na računskih vozliščih superračunalnika. Pogledali si bomo, kako v vsebniku zaženemo:

  • program, ki se izvaja na jedrih centralne procesne enote,
  • program, ki uporablja grafično procesno enoto in
  • program, ki se hkrati izvaja na več vozliščih.

Program, ki ga bomo uporabljali na delavnici, računa Mandelbrotovo množico. Napisan je v programskem jeziku Python. Osnovi program bomo po potrebi sproti nadgrajevali s podporo za izbrano tehnologijo in z njim preverjali delovanje vsebnika.

Mandelbrotova množica

Mandelbrotovo množico sestavlja množica točk c v kompleksni ravnini, za katere zaporedje kompleksnih vrednosti z(i+1) = z(i) * z(i) + c, z začetno vrednostjo z(0) = 0, ne divergira.

Mandelbrotovo množico rišemo s črno barvo, točke zunaj množice pa pobarvamo glede na število iteracij, po katerih smo zaznali divergenco zaporedja.

Mandelbrotva množica Mandelbrotva množica - detail-1 Mandelbrotva množica - detail-2 Mandelbrotva množica - detail-3

Opazimo samopodobnost in zapleten rob, ki se ob povečavi ne poenostavi. Ti lastnosti Mandelbrotovo množico uvrščata med fraktale.

Program za izris Mandelbrotove množice

Osnovna verzija programa in nekaj konfiguracijskih datotek:

Koda programa mandelbrot-seq.py

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env python3


####################################################################################
#                                                                                  #
#  single-core implementation of the Mandelbrot set                                 #
#                                                                                  #
####################################################################################


# import required libraries
import time, os
import argparse, configparser
import numpy as np
from PIL import Image
from numba import jit, prange


# mandelbrot_colors computes pixel colors in the image
#   real_min, real_max, imag_min, imag_max: bounds of the complex plane
#   iters_max: the maximum number of iterations
#   image: a reference to a memory location of the image
@jit(nopython = True, parallel = False, cache = True)
def mandelbrot_colors(real_min, real_max, imag_min, imag_max, iters_max, image):

    # image size in pixels
    width = image.shape[1]
    height = image.shape[0]

    # pixel size in complex plane
    real_step = (real_max - real_min) / width
    imag_step = (imag_max - imag_min) / height

    # check convergence of each pixel in the image
    for y in prange(0, height):
        for x in prange(0, width):              

            # a point in a complex plane corrsponding to the pixel (x, y)
            real = real_min + real_step * x
            imag = imag_min + imag_step * y
            c = complex(real, imag)

            # check for convergence
            z = complex(0, 0)
            iters = 0
            while abs(z) <= 2 and iters < iters_max:
                z = z*z + c
                iters += 1

            # color pixel in HSV scheme
            image[y, x] = (iters % 256, 255, 255 * (iters < iters_max))

# end mandelbrot_colors


# mandelbrot creates an image array of the Mandelbrot set
#   real_min, real_max, imag_min, imag_max: bounds of the complex plane
#   iters_max: the maximum number of iterations
#   width, height: size of the final image
def mandelbrot(real_min, real_max, imag_min, imag_max, iters_max, width, height):

    # allocate image array
    image = np.zeros((height, width, 3), dtype = np.uint8)

    # process image
    mandelbrot_colors(real_min, real_max, imag_min, imag_max, iters_max, image)

    # return image array
    return image

# end mandelbrot


# main routine
def main():

    # parse arguments
    ap = argparse.ArgumentParser()
    ap.add_argument('--config', type = str, default = '', help = 'config file')
    args = vars(ap.parse_args())
    config_file = args['config']

    # parse config file
    config = configparser.ConfigParser()
    if os.path.isfile(config_file):
        config.read(config_file)
    real_min = config.getfloat('AREA', 'real_min', fallback = -2.5)
    real_max = config.getfloat('AREA', 'real_max', fallback = +1.5)
    imag_min = config.getfloat('AREA', 'imag_min', fallback = -1.125)
    imag_max = config.getfloat('AREA', 'imag_max', fallback = +1.125)
    iters_max = config.getint('ITERATIONS', 'max', fallback = 256)
    width = config.getint('IMAGE', 'width', fallback = 3840)
    height = config.getint('IMAGE', 'height', fallback = 2160)
    name = config.get('IMAGE', 'name', fallback = 'mandelbrot.jpg')

    # main processing
    t = time.time()
    image = mandelbrot(real_min, real_max, imag_min, imag_max, iters_max, width, height)
    t = time.time() - t

    # save image
    Image.fromarray(image, mode='HSV').convert('RGB').save(name)    

    # printout
    print('SEQ: size:', (width, height), 'iterations:', iters_max, 'time:', round(t, 3), "s")    

# end main


# invoke the main routine, when this is the main script
if __name__ == "__main__":
   main()
Izvorna datoteka: mandelbrot-seq.py

Konfiguracijska datoteka default.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[AREA]
real_min = -2.5
real_max = +1.5
imag_min = -1.125
imag_max = +1.125

[ITERATIONS]
max = 256

[IMAGE]
width  = 3840
height = 2160
name   = mandelbrot.jpg
Izvorna datoteka: default.conf

Konfiguracijska datoteka detail-1.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[AREA]
real_min = -1.14183
real_max = -0.60999
imag_min = 0.05506
imag_max = 0.35422

[ITERATIONS]
max = 256

[IMAGE]
width  = 3840
height = 2160
name   = mandelbrot-1.jpg
Izvorna datoteka: detail-1.conf

Konfiguracijska datoteka detail-2.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[AREA]
real_min = -0.7856455
real_max = -0.7340665
imag_min = -0.140053594
imag_max = -0.111040406

[ITERATIONS]
max = 256

[IMAGE]
width  = 3840
height = 2160
name   = mandelbrot-2.jpg
Izvorna datoteka: detail-2.conf

Konfiguracijska datoteka detail-3.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[AREA]
real_min = -0.751085
real_max = -0.734975
imag_min = 0.121902063
imag_max = 0.130963938

[ITERATIONS]
max = 256

[IMAGE]
width  = 3840
height = 2160
name   = mandelbrot-3.jpg
Izvorna datoteka: detail-3.conf

Osnovni program vključuje tri funkcije:

  • V funkciji main (vrstice od 74 do 107) iz konfiguracijske datoteke preberemo parametre izrisa. Če konfiguracijske datoteke ne podamo (stikalo --config), pripravimo sliko Mandelbrotove množice s privzetimi parametri.
  • Funkcija mandelbrot (vrstice 56 do 71) pripravi prazno sliko, ki jo funkcija mandelbrot_colors pobarva.
  • Računanje Mandelbrotove množice izvaja funkcija mandelbrot_colors (vrstice 19 do 53), ki se sprehodi čez vse slikovne točke in za vsako nastavi barvo, odvisno od konvergence zaporedja.

Bolj, kot podrobnosti programa, je za gradnjo vsebnika pomembno, da

  • rabimo python3 verzije 3.6 ali novejšega in
  • da moramo dodatno namestiti pakete:

    • numpy za lažje delo z dvodimenzionalnimi polji, ki sestavljajo sliko,
    • PIL (Pillow) za delo s slikami in
    • numba, ki nam s sprotnim prevajanjem lahko precej pohitri izračune. Za knjižnico numba dodatno namestimo še knjižnico za večnitno izvajanje programov TBB (angl. Thread Building Blocks).

V funkciji mandelbrot_color uporabimo dve funkciji iz paketa numba: s prange zahtevamo vzporedno računanje iteracij v podanem obsegu, z navodilom @jit(nopython=True, parallel = False, cache = True) pa sprotno prevajanje za eno jedro in shranjevanje prevedene kode za naslednjo uporabo.

Vsebnik za python z nekaj paketi

Pripravimo vsebnik za python s potrebnimi paketi. Za osnovno bomo vzeli vsebnik ubuntu:20.04 - najnovejšo različico z dolgotrajno podporo LTS (angl. Long Term Support) - in vanjo namestili:

  • osnovna orodja za prevajanje, build-essential,
  • knjižnico TBB za večnitenje, libtbb-dev,
  • programski jezik python, python3-dev,
  • program pip za nameščanje paketov v python, python3-pip, in
  • pakete: numpy, Pillow, tbb in numba.
Recept python-scilib.def

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Bootstrap: docker
From: ubuntu:20.04
IncludeCmd: yes

%setup

%files

%environment
    export LC_ALL=C    

%post
    export LC_ALL=C    
    export DEBIAN_FRONTEND=noninteractive

    apt -y update

    apt -y install build-essential
    apt -y install libtbb-dev
    apt -y install python3-dev
    apt -y install python3-pip

    pip3 install numpy
    pip3 install Pillow
    pip3 install tbb
    pip3 install numba

%runscript

%test
    python3 --version
    python3 -c "import numpy; print('numpy', numpy.__version__)"
    python3 -c "import PIL; print('PIL', PIL.__version__)"
    python3 -c "import numba; print('numba', numba.__version__)"

%labels
    Author      uros (dot) lotric (at) fri (dot) uni (dash) lj (dot) si
    Container   firbec with terminal viewer viu
    Version     1.0
    Description Workshop advanced supercomputing (Superračunalištvo bolj zares)

%help
    Python container with libraries:
        numpy: computation
        PIL: image manipulation
        tbb: thread building blocks
        numba: parallel processing with just in time compilation 
Izvorna datoteka: python-scilib.def.

Preveri svoje znanje

Vaja

Za pripravo recepta in gradnjo vsebnika sledimo navodilom: Vaja 04.