Hlavní navigace

Práce s vektory, maticemi a n-dimenzionálními poli v knihovně SymPy

21. 6. 2022
Doba čtení: 24 minut

Sdílet

 Autor: sympy_team, podle licence: Rights Managed
Dnes se budeme zabývat především zpracováním vektorů matic. Některé operace jsou podobné operacím dostupným v knihovně NumPy, další podobné vlastnosti můžeme najít například v knihovně SciPy (řídké matice).

Obsah

1. Práce s vektory, maticemi a n-dimenzionálními poli v knihovně SymPy

2. Co si představit pod termínem „pole“?

3. Čtyři varianty reprezentace matic v knihovně SymPy

4. Konstrukce neměnného pole – matice

5. Tisk obsahu matice funkcí sympy.pprint

6. Konstrukce vektoru a matice s využitím třídy range

7. Počet dimenzí a tvar n-dimenzionálního pole

8. Změna tvaru n-dimenzionálního pole

9. Řídká pole

10. Nemodifikovatelná vs. modifikovatelná pole

11. Vynásobení matice skalárem a součet matic (přetížené operátory)

12. Transpozice matic

13. Symbolická reprezentace matice

14. Ukázka symbolické reprezentace matice

15. Maticový součin v symbolické podobě

16. Transpozice matic v symbolické podobě

17. Výpočet inverzní matice

18. Výpočet inverze maticového součinu

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Práce s vektory, maticemi a n-dimenzionálními poli v knihovně SymPy

Ve čtvrtém článku o knihovně SymPy (předchozí části jsou dostupné v [1], [2] a [3]), která je primárně určena pro provádění symbolických výpočtů v Pythonu, se budeme zabývat především zpracováním vektorů a matic. Některé dále popsané operace jsou podobné operacím dostupným v knihovně NumPy, další podobné vlastnosti můžeme najít například v knihovně SciPy (řídké matice atd.). Ovšem pro SymPy unikátní je podpora pro symbolické výpočty s maticemi.

Dnes se tedy začneme zabývat jednou poměrně rozsáhlou a současně i poněkud specifickou oblastí v informatice. Tou je zpracování vektorů, matic a taktéž vícerozměrných polí – obecně se v tomto kontextu mluví o n-rozměrných polích. S těmito velmi užitečnými datovými strukturami se můžeme setkat v různých (mnohdy zdánlivě i velmi vzdálených) disciplínách, například ve finančnictví, pojišťovnictví, statistice, zpracování numerických dat, simulacích, zpracování 1D a 2D signálů atd. Zapomenout ovšem nesmíme ani na strojové učení (machine learning) a umělou inteligencí (artifical intelligence), protože například datové struktury určené pro uložení neuronových sítí (zejména konvolučních sítí) jsou realizovány n-rozměrnými poli. Současně se jedná i o velmi zajímavou oblast, neboť právě kvůli nutnosti co nejrychlejší práce s velkými maticemi byly vytvořeny speciální výpočetní bloky v některých superpočítačích (příkladem mohou být superpočítače Cray) a došlo tak k důležitému podnětu pro další rozvoj výpočetní techniky (ten nepřímo vedl k vývoji moderních GPU). A pokud zůstaneme u 1D a 2D polí – zde došlo k rozšíření digitálních signálových procesorů orientovaných a optimalizovaných právě na tuto oblast.

Operace s poli jsou buď součástí syntaxe a sémantiky programovacích jazyků nebo jsou realizovány formou knihovny. V ekosystému programovacího jazyka Python se v první řadě jedná o již výše zmíněnou knihovnu NumPy, ovšem operace s vektory a maticemi (obecně s poli) jsou ve skutečnosti podporovány i v knihovně SymPy. A právě tímto tématem se budeme zabývat v dnešním článku.

2. Co si představit pod termínem „pole“?

V programovacích jazycích se termín „pole“ resp. array používá velmi často, ovšem ani zdaleka ne konzistentně. V případě, že se v dokumentaci jazyka bez dalších podrobností termín array použije, je vhodné hledat odpovědi na následující otázky:

  1. Kolik dimenzí může pole mít? Typicky 1 a 2, někdy i více.
  2. Začínají indexy prvků od 0, 1 či je první index volitelný?
  3. Jsou podporována obdélníková pole nebo nepravidelná pole?
  4. Jsou jednotlivé osy na sobě nezávislé? (což vylučuje nepravidelná pole)
  5. Je možné indexy na jednotlivých osách pojmenovat? (a vytvořit tak vlastně datový rámec)
  6. Jedná se o homogenní nebo o heterogenní datovou strukturu? Homogenní struktura může uchovávat prvky jediného (typicky předem definovaného) typu zatímco v heterogenní struktuře mohou být umístěny prvky různých typů.
  7. Je nějakým způsobem omezen datový typ prvků pole? (například jen na celá čísla a čísla reálná).
  8. Lze prvky pole měnit (mutable) nebo je pole neměnitelné (immutable).
  9. Pokud jsou pole heterogenní a měnitelná, může prvek pole obsahovat to samé pole?
  10. Obsahuje pole přímo hodnoty prvků nebo jen reference na prvky?
  11. Jsou prvky v poli uloženy v operační paměti za sebou nebo se jedná o strukturu s ukazateli?
  12. Jsou prvky v 2D poli uloženy po řádcích nebo po sloupcích? (C versus Fortran).
  13. Lze měnit tvar (shape) pole?
  14. Podporuje jazyk operace nad celými poli?
  15. Podporuje jazyk takzvaný broadcasting (aplikaci skaláru na všechny prvky pole atd.)?
  16. Jsou pole plnohodnotným datovým typem nebo speciální strukturou?
  17. Je podporován „literál typu pole“?
Poznámka: jak uvidíme dále, má termín „pole“ resp. „array“ v knihovně SymPy hned čtyři různé významy a používá se typicky pro reprezentaci matic s rozdílnými vlastnostmi.

3. Čtyři varianty reprezentace matic v knihovně SymPy

V knihovně SymPy je možné hodnoty ukládat do čtyř typů polí, jejichž názvy i stručný popis jsou uvedeny v následující tabulce:

Třída Stručný popis Zdrojový kód
sympy.tensor.array.Immuta­bleDenseNDimArray nemodifikovatelné pole link
sympy.tensor.array.Immuta­bleSparseNDimArray nemodifikovatelné řídké pole link
sympy.tensor.array.Mutable­DenseNDimArray modifikovatelné pole link
sympy.tensor.array.MutableS­parseNDimArray modifikovatelné řídké pole link

Vidíme tedy, že ony čtyři varianty N-dimenzionálních polí můžeme rozdělit podle dvou kritérií:

  1. Modifikovatelné vs. nemodifikovatelné, tedy podle toho, zda je možné obsah pole měnit či nikoli (tedy zda lze zapisovat nové hodnoty prvků).
  2. „Husté“ vs. „řídké“ pole, přičemž řídké pole je uloženo takovým způsobem, aby nebylo nutné explicitně ukládat prvky s nejčastější hodnotou (typicky se jedná o nulovou hodnotu).

4. Konstrukce neměnného pole – matice

Pro konstrukci neměnného (immutable) pole, ať již se má jednat o vektor, matici, či o vícedimenzionální pole, se používá konstruktor nazvaný jednoduše Array. Tomuto konstruktoru postačuje předat seznam, n-tici či seznam seznamů s prvky vloženými do budoucího pole.

V dnešním prvním demonstračním příkladu vytvoříme dvojrozměrné pole z prvků předaných v seznamu seznamů:

import sympy as sp
 
a = sp.Array([
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]])
 
print(a)
print(type(a))

Pole se při použití standardní funkce print vypíše tímto způsobem:

[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Přičemž jeho typ je následující:

<class 'sympy.tensor.array.dense_ndim_array.ImmutableDenseNDimArray'>

Naprosto stejným způsobem lze pole zkonstruovat z prvků předaných v n-tici (což je neměnitelná obdoba seznamů):

import sympy as sp
 
a = sp.Array((
        (1, 2, 3),
        (4, 5, 6),
        (7, 8, 9)))
 
print(a)
print(type(a))

Výsledkem bude pole (matice) zcela totožné s polem předchozím:

[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
<class 'sympy.tensor.array.dense_ndim_array.ImmutableDenseNDimArray'>
Poznámka: povšimněte si, že se skutečně jedná o neměnnou (neřídkou) matici – ImmutableDenseNDimArray.

5. Tisk obsahu matice funkcí sympy.pprint

Tisk matice či dokonce vícerozměrného pole ve tvaru:

[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

je poměrně nečitelný. Ovšem jak již víme z předchozích článků, podporuje knihovna SymPy i „pěkný“ tisk realizovaný funkcí sympy.pprint. Její chování si můžeme otestovat velmi snadno:

import sympy as sp
 
a = sp.Array([
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]])
 
sp.pprint(a)

Po spuštění tohoto skriptu by se na terminál měl vypsat následující výsledek:

⎡1  2  3⎤
⎢       ⎥
⎢4  5  6⎥
⎢       ⎥
⎣7  8  9⎦
Poznámka: předpokladem pro korektní tisk matice je to, že terminál podporuje Unicode, což je vlastnost naprosté většiny moderních emulací terminálu.

6. Konstrukce vektoru a matice s využitím třídy range

Konstruktor sympy.Array akceptuje jako svůj první parametr i instanci standardní třídy range. Můžeme tak velmi snadno zkonstruovat například jednorozměrný vektor, a to konkrétně následující konstrukcí:

import sympy as sp
 
a = sp.Array(range(9))
 
sp.pprint(a)

S výsledkem:

[0  1  2  3  4  5  6  7  8]

Pro konstrukci (dvourozměrné) matice je navíc nutné konstruktoru Array předat i tvar (shape) matice; v našem případě konkrétně půjde o matici se čtyřmi řádky a třemi sloupci. Tvar matice se předává n-ticí nebo seznamem:

import sympy as sp
 
a = sp.Array(range(12), (4,3))
 
sp.pprint(a)

Nyní bude výsledek získaný po spuštění skriptu vypadat takto:

⎡0  1   2 ⎤
⎢         ⎥
⎢3  4   5 ⎥
⎢         ⎥
⎢6  7   8 ⎥
⎢         ⎥
⎣9  10  11⎦

7. Počet dimenzí a tvar n-dimenzionálního pole

Počet dimenzí pole je možné zjistit metodou nazvanou rank zatímco takzvaný tvar pole (počet prvků ve směru jednotlivých os) se kupodivu nezjišťuje metodou, ale je uložen v atributu shape. Nejdříve se podívejme na zjištění těchto dvou velmi důležitých vlastností n-dimenzionálních polí pro jednodimenzionální vektor:

import sympy as sp
 
a = sp.Array(range(9))
 
sp.pprint(a)
print(a.rank())
print(a.shape)

Výsledek:

[0  1  2  3  4  5  6  7  8]
1
(9,)
Poznámka: povšimněte si, že tvar (shape) je vrácen formou n-tice, přičemž počet prvků této n-tice odpovídá hodnotě rank().

Podobný příklad, ovšem pro dvourozměrnou matici:

import sympy as sp
 
a = sp.Array(range(12), (4,3))
 
sp.pprint(a)
print(a.rank())
print(a.shape)

S výsledky:

⎡0  1   2 ⎤
⎢         ⎥
⎢3  4   5 ⎥
⎢         ⎥
⎢6  7   8 ⎥
⎢         ⎥
⎣9  10  11⎦
 
2
(4, 3)

8. Změna tvaru n-dimenzionálního pole

Další velmi důležitou operací, s níž se v praxi často setkáme, je operace nazvaná reshape(), která dokáže změnit velikost matice a vhodným způsobem přeorganizovat prvky v původní matici (operace je převzata z jazyka APL – jak jinak). Tato operace je představována metodou nazvanou reshape se předává tvar nového n-dimenzionálního pole. Původní pole přitom zůstane nezměněno.

V dalším demonstračním příkladu nejdříve vytvoříme jednodimenzionální vektor se šestnácti prvky a následně z tohoto vektoru vytvoříme dvoudimenzionální matice i pole se třemi dimenzemi. Povšimněte si, že ve všech případech je zachován počet prvků vytvořeného n-dimenzionálního pole (16):

import sympy as sp
 
a1 = sp.Array(range(16))
sp.pprint(a1)
 
print()
 
a2 = a1.reshape(2,8)
sp.pprint(a2)
 
print()
 
a3 = a1.reshape(4,4)
sp.pprint(a3)
 
print()
 
a4 = a1.reshape(4,2,2)
sp.pprint(a4)
 
print()
 
a5 = a1.reshape(2,4,2)
sp.pprint(a5)
 
print()
 
a6 = a1.reshape(2,2,4)
sp.pprint(a6)
 
print()

Výsledná pole získaná tímto skriptem:

Původní vektor se šestnácti prvky:

[0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15]

Dvourozměrné matice:

⎡0  1  2   3   4   5   6   7 ⎤
⎢                            ⎥
⎣8  9  10  11  12  13  14  15⎦
 
⎡0   1   2   3 ⎤
⎢              ⎥
⎢4   5   6   7 ⎥
⎢              ⎥
⎢8   9   10  11⎥
⎢              ⎥
⎣12  13  14  15⎦

Trojrozměrná pole:

 
⎡⎡0  1⎤  ⎡4  5⎤  ⎡8   9 ⎤  ⎡12  13⎤⎤
⎢⎢    ⎥  ⎢    ⎥  ⎢      ⎥  ⎢      ⎥⎥
⎣⎣2  3⎦  ⎣6  7⎦  ⎣10  11⎦  ⎣14  15⎦⎦
 
⎡⎡0  1⎤  ⎡8   9 ⎤⎤
⎢⎢    ⎥  ⎢      ⎥⎥
⎢⎢2  3⎥  ⎢10  11⎥⎥
⎢⎢    ⎥  ⎢      ⎥⎥
⎢⎢4  5⎥  ⎢12  13⎥⎥
⎢⎢    ⎥  ⎢      ⎥⎥
⎣⎣6  7⎦  ⎣14  15⎦⎦
 
⎡⎡0  1  2  3⎤  ⎡8   9   10  11⎤⎤
⎢⎢          ⎥  ⎢              ⎥⎥
⎣⎣4  5  6  7⎦  ⎣12  13  14  15⎦⎦

Vytvořit lze pochopitelně i pole s větším počtem dimenzí:

import sympy as sp
 
a1 = sp.Array(range(16))
sp.pprint(a1)
print(a1.rank())
print(a1.shape)
 
print()
 
a2 = a1.reshape(2,2,2,2)
sp.pprint(a2)
print(a2.rank())
print(a2.shape)

Výsledkem činnosti tohoto skriptu bude pole 2×2×2×2 prvky:

[0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15]
 
1
(16,)
 
 
⎡ ⎡0  1⎤    ⎡4  5⎤ ⎤
⎢ ⎢    ⎥    ⎢    ⎥ ⎥
⎢ ⎣2  3⎦    ⎣6  7⎦ ⎥
⎢                  ⎥
⎢⎡8   9 ⎤  ⎡12  13⎤⎥
⎢⎢      ⎥  ⎢      ⎥⎥
⎣⎣10  11⎦  ⎣14  15⎦⎦
 
4
(2, 2, 2, 2)

9. Řídká pole

V mnoha oblastech se poměrně často setkáme se situací, kdy je nutné pracovat s obrovskými n-rozměrnými poli (typicky s maticemi), které ovšem převážně obsahují prvky se shodnou hodnotou. Příkladem mohou být takzvané matice incidence používané pro reprezentaci grafů. V případě, že má graf relativně velké množství uzlů, ovšem málo hran, bude matice incidence obsahovat mnoho nulových prvků. A právě v těchto případech je možné využít takzvané řídké matice resp. obecně řídká n-rozměrná pole (sparse array). Podívejme se nyní na rozdílný způsob konstrukce „hustých“ a „řídkých“ polí. Nejdříve zkonstruujeme „hustou“ neměnitelnou matici o rozměrech 5×5 prvků, která bude obsahovat dvacet pět nulových prvků:

import sympy as sp
 
a1 = sp.Array([0]*25)
a2 = a1.reshape(5,5)
 
print(type(a2))
print()
 
sp.pprint(a2)

Tento skript nejdříve vypíše typ pole a posléze i jeho obsah:

<class 'sympy.tensor.array.dense_ndim_array.ImmutableDenseNDimArray'>
 
⎡0  0  0  0  0⎤
⎢             ⎥
⎢0  0  0  0  0⎥
⎢             ⎥
⎢0  0  0  0  0⎥
⎢             ⎥
⎢0  0  0  0  0⎥
⎢             ⎥
⎣0  0  0  0  0⎦

Naproti tomu řídkou matici musíme vytvořit konstruktorem ImmutableSparseNDimArray či MutableSparseNDimArray, a to následujícím způsobem:

import sympy as sp
 
a1 = sp.ImmutableSparseNDimArray([0]*25)
a2 = a1.reshape(5,5)
 
print(type(a2))
print()
 
sp.pprint(a2)

I tento skript nejdříve vypíše typ pole a posléze i jeho obsah:

<class 'sympy.tensor.array.sparse_ndim_array.ImmutableSparseNDimArray'>
 
⎡0  0  0  0  0⎤
⎢             ⎥
⎢0  0  0  0  0⎥
⎢             ⎥
⎢0  0  0  0  0⎥
⎢             ⎥
⎢0  0  0  0  0⎥
⎢             ⎥
⎣0  0  0  0  0⎦

10. Nemodifikovatelná vs. modifikovatelná pole

N-rozměrné pole vytvořené konstruktory Array, ImmutableDenseNDimArray či ImmutableSparseNDimArray je neměnitelné (nemodifikovatelné), což znamená, že ani není možné měnit hodnotu jeho prvků. Opět si to pochopitelně můžeme otestovat na jednoduchém příkladu, v němž se pokusíme změnit hodnotu prostředního prvku v matici (povšimněte si způsobu zápisu „souřadnic“ tohoto prvku s využitím n-tice):

import sympy as sp
 
a1 = sp.ImmutableSparseNDimArray([0]*25)
a2 = a1.reshape(5,5)
 
print(type(a2))
print()
 
sp.pprint(a2)
 
a2[(2,2)] = 42
 
print()
 
sp.pprint(a2)

Tento příklad nejprve vypíše typ pole:

<class 'sympy.tensor.array.sparse_ndim_array.ImmutableSparseNDimArray'>

Následně jeho původní obsah:

⎡0  0  0  0  0⎤
⎢             ⎥
⎢0  0  0  0  0⎥
⎢             ⎥
⎢0  0  0  0  0⎥
⎢             ⎥
⎢0  0  0  0  0⎥
⎢             ⎥
⎣0  0  0  0  0⎦

A po pokusu o modifikaci prvku pole tento skript zhavaruje:

Traceback (most recent call last):
  File "/home/user/sympy69.py", line 11, in <module>
    a2[(2,2)] = 42
  File "/home/user/.local/lib/python3.10/site-packages/sympy/tensor/array/sparse_ndim_array.py", line 132, in __setitem__
    raise TypeError("immutable N-dim array")
TypeError: immutable N-dim array

Situaci napravíme jednoduše, a to použitím konstruktoru MutableDenseNDimArray nebo MutableSparseNDimArray:

import sympy as sp
 
a1 = sp.MutableSparseNDimArray([0]*25)
a2 = a1.reshape(5,5)
 
print(type(a2))
print()
 
sp.pprint(a2)
 
a2[(2,2)] = 42
 
print()
 
sp.pprint(a2)

Chování skriptu – nejdříve vypíše typ pole:

<class 'sympy.tensor.array.sparse_ndim_array.MutableSparseNDimArray'>

Dále se zobrazí původní pole před jeho modifikací:

⎡0  0  0  0  0⎤
⎢             ⎥
⎢0  0  0  0  0⎥
⎢             ⎥
⎢0  0  0  0  0⎥
⎢             ⎥
⎢0  0  0  0  0⎥
⎢             ⎥
⎣0  0  0  0  0⎦

Pole po modifikaci – povšimněte si prvku uprostřed pole (matice):

⎡0  0  0   0  0⎤
⎢              ⎥
⎢0  0  0   0  0⎥
⎢              ⎥
⎢0  0  42  0  0⎥
⎢              ⎥
⎢0  0  0   0  0⎥
⎢              ⎥
⎣0  0  0   0  0⎦

11. Vynásobení matice skalárem a součet matic (přetížené operátory)

Knihovna SymPy podporuje provádění základních operací s maticemi s využitím přetížených (původně většinou aritmetických) operátorů. V následujícím příkladu je ukázáno, jak lze využít přetížené operátory * a + pro vynásobení všech prvků matice skalární hodnotou a následně pro součet matic (které by měly mít stejný počet dimenzí i shodný tvar):

import sympy as sp
 
a1 = sp.ImmutableSparseNDimArray(range(12))
a2 = a1.reshape(3,4)
 
sp.pprint(a2)
 
print()
 
a3 = 2*a2
sp.pprint(a3)
 
print()
 
a4 = a2 + a3
sp.pprint(a4)

Původní matice vypsaná skriptem:

⎡0  1  2   3 ⎤
⎢            ⎥
⎢4  5  6   7 ⎥
⎢            ⎥
⎣8  9  10  11⎦

Nová matice vytvořená vynásobení všech prvků původní matice skalární hodnotou:

⎡0   2   4   6 ⎤
⎢              ⎥
⎢8   10  12  14⎥
⎢              ⎥
⎣16  18  20  22⎦

Další matice, která vznikla součtem obou předchozích matic:

⎡0   3   6   9 ⎤
⎢              ⎥
⎢12  15  18  21⎥
⎢              ⎥
⎣24  27  30  33⎦
Poznámka: předchozí příklad bude funkční se všemi čtyřmi typy matic (resp. polí).

12. Transpozice matic

Knihovna SymPy podporuje i transpozici matic. Pro tento účel se používá metoda nazvaná transpose(), která vrátí novou matici, jež je odvozena z matice zdrojové. Opět se pochopitelně podíváme na příklad:

import sympy as sp
 
a1 = sp.ImmutableSparseNDimArray(range(12))
a2 = a1.reshape(3,4)
 
sp.pprint(a2)
 
print()
 
a3 = a2.transpose()
sp.pprint(a3)

Tento skript nejdříve zobrazí původní matici a posléze matici transponovanou:

⎡0  1  2   3 ⎤
⎢            ⎥
⎢4  5  6   7 ⎥
⎢            ⎥
⎣8  9  10  11⎦
 
⎡0  4  8 ⎤
⎢        ⎥
⎢1  5  9 ⎥
⎢        ⎥
⎢2  6  10⎥
⎢        ⎥
⎣3  7  11⎦

13. Symbolická reprezentace matice

Jméno knihovny SymPy je odvozeno od slova „symbolic“, což znamená, že manipulace s matematickými vztahy (typicky s rovnicemi, ale například i se zápisem transformací typu derivace a integrace) je prováděn v symbolické formě, nikoli numericky. To do jisté míry platí i pro matice. Prozatím jsme s maticemi (a obecně n-dimenzionálními poli) pracovali jako s kontejnery na numerické hodnoty, s nimiž je možné provádět numerické operace (maticový součin atd.), ovšem SymPy umožňuje na matice nahlížet taktéž jako na kontejnery, v nichž má každý prvek určité symbolické jméno, například A₁₂. Takové matice se konstruují s využitím konstruktoru MatrixSymbol, který použijeme v navazujících kapitolách. A operace s takovými maticemi jsou opět prováděny na symbolické úrovni, tj. výsledkem typicky bývají opět „symbolické“ matice.

14. Ukázka symbolické reprezentace matice

V této kapitole si ukážeme, jak se pracuje se symbolickou reprezentací matice. Takovou matici je zapotřebí deklarovat, přičemž namísto nám již známého konstruktoru Symbols použijeme konstruktor MatrixSymbol, jemuž se kromě symbolického jména matice předávají i její rozměry. Matice se třemi řádky a čtyřmi sloupci se zkonstruuje takto:

a = sp.MatrixSymbol("A", 3, 4)

Taková matice se vypíše v symbolické podobě příkazem:

sp.pprint(sp.Matrix(a))

Podívejme se na skript, který tyto operace provádí:

import sympy as sp
 
a = sp.MatrixSymbol("A", 3, 4)
print(a)
 
print()
 
print(sp.Matrix(a))
 
print()
 
sp.pprint(sp.Matrix(a))

Tento skript postupně zobrazí jméno matice (jako symbolu), obsah matice v „řádkové“ formě a obsah matice v „čitelné“ formě:

A
 
Matrix([[A[0, 0], A[0, 1], A[0, 2], A[0, 3]], [A[1, 0], A[1, 1], A[1, 2], A[1, 3]], [A[2, 0], A[2, 1], A[2, 2], A[2, 3]]])
 
⎡A₀₀  A₀₁  A₀₂  A₀₃⎤
⎢                  ⎥
⎢A₁₀  A₁₁  A₁₂  A₁₃⎥
⎢                  ⎥
⎣A₂₀  A₂₁  A₂₂  A₂₃⎦
Poznámka: povšimněte si, že se při výstupu na terminál využívá Unicode pro zápis indexů (nejedná se tedy o původní ASCII číslice).

15. Maticový součin v symbolické podobě

Vyzkoušejme si nyní, jak vlastně bude vypadat maticový součin pro matice zapsané v symbolické podobě. Výsledkem by přitom opět měla být matice v symbolické podobě. Nejdříve obě matice vytvoříme:

a = sp.MatrixSymbol("A", 3, 4)
b = sp.MatrixSymbol("B", 4, 3)
Poznámka: povšimněte si, že matice jsou „kompatibilní“ z hlediska součinu, protože počet sloupců první matice odpovídá počtu řádků matice druhé.

Nyní vypočteme součin a necháme si zobrazit výslednou matici:

c = a*b
sp.pprint(sp.Matrix(c))

Úplný skript provádějící tuto operaci vypadá následovně:

import sympy as sp
 
a = sp.MatrixSymbol("A", 3, 4)
b = sp.MatrixSymbol("B", 4, 3)
 
sp.pprint(sp.Matrix(a))
 
print()
 
sp.pprint(sp.Matrix(b))
 
print()
 
c = a*b
sp.pprint(sp.Matrix(c))

První matice vstupující do součinu:

⎡A₀₀  A₀₁  A₀₂  A₀₃⎤
⎢                  ⎥
⎢A₁₀  A₁₁  A₁₂  A₁₃⎥
⎢                  ⎥
⎣A₂₀  A₂₁  A₂₂  A₂₃⎦

Druhá matice vstupující do součinu:

⎡B₀₀  B₀₁  B₀₂⎤
⎢             ⎥
⎢B₁₀  B₁₁  B₁₂⎥
⎢             ⎥
⎢B₂₀  B₂₁  B₂₂⎥
⎢             ⎥
⎣B₃₀  B₃₁  B₃₂⎦

Symbolicky zapsaný výsledek maticového součinu

⎡A₀₀⋅B₀₀ + A₀₁⋅B₁₀ + A₀₂⋅B₂₀ + A₀₃⋅B₃₀  A₀₀⋅B₀₁ + A₀₁⋅B₁₁ + A₀₂⋅B₂₁ + A₀₃⋅B₃₁  A₀₀⋅B₀₂ + A₀₁⋅B₁₂ + A₀₂⋅B₂₂ + A₀₃⋅B₃₂⎤
⎢                                                                                                                   ⎥
⎢A₁₀⋅B₀₀ + A₁₁⋅B₁₀ + A₁₂⋅B₂₀ + A₁₃⋅B₃₀  A₁₀⋅B₀₁ + A₁₁⋅B₁₁ + A₁₂⋅B₂₁ + A₁₃⋅B₃₁  A₁₀⋅B₀₂ + A₁₁⋅B₁₂ + A₁₂⋅B₂₂ + A₁₃⋅B₃₂⎥
⎢                                                                                                                   ⎥
⎣A₂₀⋅B₀₀ + A₂₁⋅B₁₀ + A₂₂⋅B₂₀ + A₂₃⋅B₃₀  A₂₀⋅B₀₁ + A₂₁⋅B₁₁ + A₂₂⋅B₂₁ + A₂₃⋅B₃₁  A₂₀⋅B₀₂ + A₂₁⋅B₁₂ + A₂₂⋅B₂₂ + A₂₃⋅B₃₂⎦
Poznámka: výsledkem je tedy matice 3×3 prvky.

16. Transpozice matic v symbolické podobě

Pro získání transponované matice v symbolické podobě postačuje (což je zvláštní) přistoupit k atributu T. Je tomu tak z toho důvodu, že se zápis atributu do určité míry přibližuje zápisu MT, který nelze takto přímo v Pythonu použít:

import sympy as sp
 
a = sp.MatrixSymbol("A", 3, 4)
 
sp.pprint(sp.Matrix(a))
 
print()
 
sp.pprint(sp.Matrix(a.T))

Tento skript nejdříve vypíše původní matici a posléze matici transponovanou:

⎡A₀₀  A₀₁  A₀₂  A₀₃⎤
⎢                  ⎥
⎢A₁₀  A₁₁  A₁₂  A₁₃⎥
⎢                  ⎥
⎣A₂₀  A₂₁  A₂₂  A₂₃⎦
 
⎡A₀₀  A₁₀  A₂₀⎤
⎢             ⎥
⎢A₀₁  A₁₁  A₂₁⎥
⎢             ⎥
⎢A₀₂  A₁₂  A₂₂⎥
⎢             ⎥
⎣A₀₃  A₁₃  A₂₃⎦

17. Výpočet inverzní matice

Zápisem matice.I můžeme získat inverzní matici, a to samozřejmě opět v symbolické podobě (přístupem k atributu I, což evokuje zápis M-1). Podívejme se nyní na způsob výpočtu inverzní matice k relativně malé matici o rozměrech 2×2 prvky:

import sympy as sp
 
a = sp.MatrixSymbol("A", 2, 2)
 
sp.pprint(sp.Matrix(a))
 
print()
 
sp.pprint(sp.Matrix(a.I))

Nejprve se vypíše původní matice:

⎡A₀₀  A₀₁⎤
⎢        ⎥
⎣A₁₀  A₁₁⎦

A následně matice inverzní:

⎡       A₁₁               -A₀₁       ⎤
⎢─────────────────  ─────────────────⎥
⎢A₀₀⋅A₁₁ - A₀₁⋅A₁₀  A₀₀⋅A₁₁ - A₀₁⋅A₁₀⎥
⎢                                    ⎥
⎢      -A₁₀                A₀₀       ⎥
⎢─────────────────  ─────────────────⎥
⎣A₀₀⋅A₁₁ - A₀₁⋅A₁₀  A₀₀⋅A₁₁ - A₀₁⋅A₁₀⎦

Kontrola výpočtu:

import sympy as sp
 
a = sp.MatrixSymbol("A", 2, 2)
 
i = a.I
 
r = a*i
 
sp.pprint(sp.Matrix(r))

S výsledkem:

⎡1  0⎤
⎢    ⎥
⎣0  1⎦

Což je (podle očekávání) skutečně jednotková matice.

18. Výpočet inverze maticového součinu

V dnešním posledním demonstračním příkladu je ukázán výpočet maticového součinu (ten již ostatně velmi dobře známe), přičemž z výsledku této operace je vypočtena inverzní matice. Obě vstupní matice, které budeme násobit, mají pro jednoduchost rozměry 2×2 prvky. Pro větší matice totiž trvá výpočet inverzní matice neúměrně dlouhou dobu:

skolení ELK

import sympy as sp
 
a = sp.MatrixSymbol("A", 2, 2)
b = sp.MatrixSymbol("B", 2, 2)
 
c = a*b
 
sp.pprint(sp.Matrix(c))
 
print()
 
sp.pprint(sp.Matrix(c.I))

Skript po svém spuštění nejdříve vypíše výsledek maticového součinu a posléze i inverzní matici k tomuto výsledku:

⎡A₀₀⋅B₀₀ + A₀₁⋅B₁₀  A₀₀⋅B₀₁ + A₀₁⋅B₁₁⎤
⎢                                    ⎥
⎣A₁₀⋅B₀₀ + A₁₁⋅B₁₀  A₁₀⋅B₀₁ + A₁₁⋅B₁₁⎦
 
 
 
⎡                 A₁₀⋅B₀₁                                   A₁₁⋅B₁₁
⎢ ─────────────────────────────────────── + ──────────────────────────────────
⎢ (A₀₀⋅A₁₁ - A₀₁⋅A₁₀)⋅(B₀₀⋅B₁₁ - B₀₁⋅B₁₀)   (A₀₀⋅A₁₁ - A₀₁⋅A₁₀)⋅(B₀₀⋅B₁₁ - B₀₁
⎢
⎢                  A₁₀⋅B₀₀                                   A₁₁⋅B₁₀
⎢- ─────────────────────────────────────── - ─────────────────────────────────
⎣  (A₀₀⋅A₁₁ - A₀₁⋅A₁₀)⋅(B₀₀⋅B₁₁ - B₀₁⋅B₁₀)   (A₀₀⋅A₁₁ - A₀₁⋅A₁₀)⋅(B₀₀⋅B₁₁ - B₀
 
                          A₀₀⋅B₀₁                                   A₀₁⋅B₁₁
─────   - ─────────────────────────────────────── - ──────────────────────────
⋅B₁₀)     (A₀₀⋅A₁₁ - A₀₁⋅A₁₀)⋅(B₀₀⋅B₁₁ - B₀₁⋅B₁₀)   (A₀₀⋅A₁₁ - A₀₁⋅A₁₀)⋅(B₀₀⋅B
 
                         A₀₀⋅B₀₀                                   A₀₁⋅B₁₀
──────   ─────────────────────────────────────── + ───────────────────────────
₁⋅B₁₀)   (A₀₀⋅A₁₁ - A₀₁⋅A₁₀)⋅(B₀₀⋅B₁₁ - B₀₁⋅B₁₀)   (A₀₀⋅A₁₁ - A₀₁⋅A₁₀)⋅(B₀₀⋅B₁
 
             ⎤
─────────────⎥
₁₁ - B₀₁⋅B₁₀)⎥
             ⎥
             ⎥
──────────── ⎥
₁ - B₀₁⋅B₁₀) ⎦
Poznámka: povšimněte si, že výsledek je tak rozsáhlý, že musel být rozdělen na tři části.

19. Repositář s demonstračními příklady

Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python 3 byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má velikost zhruba několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Demonstrační příklad Stručný popis příkladu Cesta
1 sympy01.py zjednodušování konstantního výrazu s odmocninou https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy01.py
2 sympy02.py zjednodušování konstantních výrazů https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy02.py
3 sympy03.py čitelný výpis výrazů funkcí sympy.pprint https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy03.py
4 sympy04.py čitelný výpis výrazů funkcí sympy.pprint, složitější výsledky https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy04.py
5 sympy05.py deklarace a „terminálový“ výpis jednoduchého výrazu https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy05.py
6 sympy06.py deklarace a „matematický“ výpis jednoduchého výrazu https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy06.py
7 sympy07.py výraz s větším množstvím proměnných (zlomek) https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy07.py
8 sympy08.py výraz s větším množstvím proměnných (zlomek) https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy08.py
9 sympy09.py zjednodušení výrazu s jedinou proměnnou https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy09.py
10 sympy10.py zjednodušení výrazu se třemi proměnnými a se zlomky https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy10.py
11 sympy11.py pokus o použití nedefinované proměnné https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy11.py
12 sympy12.py výraz x2-y2 https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy12.py
13 sympy13.py složitější výraz s několika členy https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy13.py
14 sympy14.py faktorizace výrazu x2-y2 https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy14.py
15 sympy15.py faktorizace výrazu x2-2× + 1 https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy15.py
16 sympy16.py expanze výrazu https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy16.py
17 sympy17.py expanze (roznásobení závorek) složitějšího výrazu se dvěma proměnnými https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy17.py
18 sympy18.py expanze (roznásobení závorek) složitějšího výrazu se třemi proměnnými https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy18.py
19 sympy19.py řešení kvadratické rovnice https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy19.py
20 sympy20.py řešení kvadratické rovnice, odlišná forma výstupu https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy20.py
21 sympy21.py řešení kvadratické rovnice se dvěma neznámými https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy21.py
22 sympy22.py výpočet derivace polynomu https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy22.py
23 sympy23.py výpočet derivace složitějšího výrazu https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy23.py
24 sympy24.py výpočet integrace polynomu https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy24.py
25 sympy25.py výpočet integrace složitějšího polynomu https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy25.py
       
26 sympy26.py symboly vs. proměnné https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy26.py
27 sympy27.py reálné kořeny kvadratické rovnice https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy27.py
28 sympy28.py komplexní kořeny kvadratické rovnice https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy28.py
29 sympy29.py hledání kořenů kubické rovnice https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy29.py
30 sympy30.py kořeny polynomů vyšších stupňů https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy30.py
31 sympy31.py průchod nulovými body periodické funkce https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy31.py
32 sympy32.py vyhledání všech možných řešení https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy32.py
33 sympy33.py vyhledání řešení pro funkci sinc https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy33.py
34 sympy34.py řešení nerovnosti https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy34.py
35 sympy35.py složitější nerovnost s goniometrickou funkcí https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy35.py
36 sympy36.py řešení dvojice nerovnic https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy36.py
37 sympy37.py složitější dvojice nerovnic https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy37.py
       
38 sympy38.py první derivace funkce https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy38.py
39 sympy39.py první derivace odlišné funkce https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy39.py
40 sympy40.py první až čtvrtá derivace funkce https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy40.py
41 sympy41.py první až čtvrtá derivace odlišné funkce https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy41.py
42 sympy42.py první až čtvrtá derivace odlišné funkce https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy42.py
43 sympy43.py první až čtvrtá derivace odlišné funkce https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy43.py
44 sympy44.py výpočet singularit, jichž je konečné množství https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy44.py
45 sympy45.py výpočet singularit, jichž je nekonečné (spočetné) množství https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy45.py
46 sympy46.py výpočet singularit v oboru komplexních čísel https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy46.py
47 sympy47.py výpočet singularit v oboru komplexních čísel https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy47.py
48 sympy48.py výpočet lokálního minima https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy48.py
49 sympy49.py výpočet lokálního maxima https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy49.py
50 sympy50.py test, zda je funkce v zadaném intervalu rostoucí https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy50.py
51 sympy51.py test, zda je funkce v zadaném intervalu klesající https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy51.py
52 sympy52.py otevřené intervaly a test na rostoucí funkci https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy52.py
53 sympy53.py výpočet limity (zprava) funkce y=1/x v nule https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy53.py
54 sympy54.py výpočet limity (zleva) funkce y=1/x v nule https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy54.py
55 sympy55.py výpočet limity funkce y=1/x v nekonečnu https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy55.py
56 sympy56.py výpočet limity funkce y=1/x v záporném nekonečnu https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy56.py
57 sympy57.py výpočet limity funkce y=sin(1/x) v nule https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy57.py
       
58 sympy58.py konstrukce neměnného pole (matice) z prvků předaných v seznamu https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy58.py
59 sympy59.py konstrukce neměnného pole (matice) z prvků předaných v n-tici https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy59.py
60 sympy60.py tisk matice funkcí pprint https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy60.py
61 sympy61.py konstrukce jednorozměrného vektoru s využitím generátoru range https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy61.py
62 sympy62.py konstrukce dvourozměrné matice s využitím generátoru range https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy62.py
63 sympy63.py tisk řádu a tvaru jednorozměrného vektoru – rankshape https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy63.py
64 sympy64.py tisk řádu a tvaru dvourozměrné matice – rankshape https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy64.py
65 sympy65.py změna tvaru n-dimenzionálního pole metodou reshape https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy65.py
66 sympy66.py čtyřrozměrné pole https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy66.py
67 sympy67.py výchozí typ n-dimenzionálního pole https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy67.py
68 sympy68.py explicitní stanovení typu n-dimenzionálního pole https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy68.py
69 sympy69.py pokus o modifikaci neměnného n-dimenzionálního pole https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy69.py
70 sympy70.py modifikace měnitelného n-dimenzionálního pole https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy70.py
71 sympy71.py výpočty s celými maticemi https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy71.py
72 sympy72.py transpozice matice https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy72.py
73 sympy73.py transpozice matice https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy73.py
74 sympy74.py symbolicky reprezentovaná matice https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy74.py
75 sympy75.py symbolický maticový součin https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy75.py
76 sympy76.py symbolický výpočet transformované matice https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy76.py
77 sympy77.py symbolický výpočet inverzní matice https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy77.py
78 sympy78.py symbolický výpočet inverzní matice https://github.com/tisnik/most-popular-python-libs/blob/master/sympy/sympy78.py

20. Odkazy na Internetu

  1. SymPy
    https://www.sympy.org/en/index.html
  2. SymPy na PyPi
    https://pypi.org/project/sympy/
  3. mpmath
    https://mpmath.org/
  4. mpmath na PyPi
    https://pypi.org/project/mpmath/
  5. Symbolic Maths in Python
    https://alexandrugris.git­hub.io/maths/2017/04/30/sym­bolic-maths-python.html
  6. SymPy shell
    https://live.sympy.org/
  7. Symbolic programming
    https://en.wikipedia.org/wi­ki/Symbolic_programming
  8. Symbolic language (programming)
    https://en.wikipedia.org/wi­ki/Symbolic_language_(pro­gramming)
  9. Computer algebra
    https://en.wikipedia.org/wi­ki/Computer_algebra
  10. Common Lisp: A Gentle Introduction to Symbolic Computation
    https://www.cs.cmu.edu/~dst/LispBook/
  11. List of computer algebra systems
    https://en.wikipedia.org/wi­ki/List_of_computer_algebra_sys­tems
  12. Polynom
    https://cs.wikipedia.org/wiki/Polynom
  13. What is SimPy? How to run python simulations?
    https://divyas090909.medium.com/what-is-simpy-how-to-run-python-simulations-348736b50615
  14. SimPy: Simulating Real-World Processes With Python
    https://realpython.com/simpy-simulating-with-python/
  15. Jazyky umožňující operace s poli aneb rozsáhlý svět „array programmingu“
    https://www.root.cz/clanky/jazyky-umoznujici-operace-s-poli-aneb-rozsahly-svet-bdquo-array-programmingu-ldquo/
  16. What is an Array?
    https://vector.org.uk/what-is-an-array/
  17. Vector (Wolfram MathWorld)
    https://mathworld.wolfram­.com/Vector.html
  18. n-Tuple (Wolfram MathWorld)
    https://mathworld.wolfram.com/n-Tuple.html
  19. n-Vector (Wolfram MathWorld)
    https://mathworld.wolfram.com/n-Vector.html
  20. Matrix (Wolfram MathWorld)
    https://mathworld.wolfram­.com/Matrix.html
  21. Array (Wolfram MathWorld)
    https://mathworld.wolfram­.com/Array.html
  22. ND Arrays (Tensors) in different languages
    https://www.youtube.com/wat­ch?v=WbpbEilgQBc
  23. Extending APL to Infinity\
    https://www.jsoftware.com/pa­pers/eem/infinity.htm
  24. Sparse matrix
    https://en.wikipedia.org/wi­ki/Sparse_matrix
  25. Matice incidence
    https://cs.wikipedia.org/wi­ki/Matice_incidence