2023-06-30
This commit is contained in:
parent
c7c111e85d
commit
5731feea25
32 changed files with 2549 additions and 0 deletions
2
.gitattributes
vendored
2
.gitattributes
vendored
|
@ -0,0 +1,2 @@
|
|||
jekyll_site/ru/** linguist-language=Java
|
||||
jekyll_site/en/** linguist-language=Java
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1,5 @@
|
|||
.idea
|
||||
*.iml
|
||||
*.zip
|
||||
_site*
|
||||
.repo_*.sh
|
||||
|
|
61
DIRECTORY-TREE.md
Normal file
61
DIRECTORY-TREE.md
Normal file
|
@ -0,0 +1,61 @@
|
|||
## Дерево каталогов
|
||||
|
||||
<pre>
|
||||
.
|
||||
├─ <a href='jekyll_site'>jekyll_site</a>
|
||||
│ ├─ <a href='jekyll_site/_includes'>_includes</a>
|
||||
│ │ ├─ <a href='jekyll_site/_includes/counters_body.html'>counters_body.html</a>
|
||||
│ │ └─ <a href='jekyll_site/_includes/counters_head.html'>counters_head.html</a>
|
||||
│ ├─ <a href='jekyll_site/en'>en</a>
|
||||
│ │ ├─ <a href='jekyll_site/en/2021'>2021</a>
|
||||
│ │ │ └─ <a href='jekyll_site/en/2021/12'>12</a>
|
||||
│ │ │ ├─ <a href='jekyll_site/en/2021/12/10'>10</a>
|
||||
│ │ │ │ └─ <a href='jekyll_site/en/2021/12/10/optimizing-matrix-multiplication.md'>optimizing-matrix-multiplication.md</a>
|
||||
│ │ │ ├─ <a href='jekyll_site/en/2021/12/13'>13</a>
|
||||
│ │ │ │ └─ <a href='jekyll_site/en/2021/12/13/matrix-rotation-90-degrees.md'>matrix-rotation-90-degrees.md</a>
|
||||
│ │ │ └─ <a href='jekyll_site/en/2021/12/17'>17</a>
|
||||
│ │ │ └─ <a href='jekyll_site/en/2021/12/17/matrix-rotation-180-degrees.md'>matrix-rotation-180-degrees.md</a>
|
||||
│ │ ├─ <a href='jekyll_site/en/2022'>2022</a>
|
||||
│ │ │ └─ <a href='jekyll_site/en/2022/02'>02</a>
|
||||
│ │ │ ├─ <a href='jekyll_site/en/2022/02/09'>09</a>
|
||||
│ │ │ │ └─ <a href='jekyll_site/en/2022/02/09/matrix-multiplication-parallel-streams.md'>matrix-multiplication-parallel-streams.md</a>
|
||||
│ │ │ └─ <a href='jekyll_site/en/2022/02/11'>11</a>
|
||||
│ │ │ └─ <a href='jekyll_site/en/2022/02/11/winograd-strassen-algorithm.md'>winograd-strassen-algorithm.md</a>
|
||||
│ │ └─ <a href='jekyll_site/en/index.md'>index.md</a>
|
||||
│ ├─ <a href='jekyll_site/img'>img</a>
|
||||
│ │ ├─ <a href='jekyll_site/img/block-matrices.svg'>block-matrices.svg</a>
|
||||
│ │ ├─ <a href='jekyll_site/img/products.svg'>products.svg</a>
|
||||
│ │ ├─ <a href='jekyll_site/img/sums1.svg'>sums1.svg</a>
|
||||
│ │ ├─ <a href='jekyll_site/img/sums2.svg'>sums2.svg</a>
|
||||
│ │ └─ <a href='jekyll_site/img/sums3.svg'>sums3.svg</a>
|
||||
│ ├─ <a href='jekyll_site/ru'>ru</a>
|
||||
│ │ ├─ <a href='jekyll_site/ru/2021'>2021</a>
|
||||
│ │ │ └─ <a href='jekyll_site/ru/2021/12'>12</a>
|
||||
│ │ │ ├─ <a href='jekyll_site/ru/2021/12/09'>09</a>
|
||||
│ │ │ │ └─ <a href='jekyll_site/ru/2021/12/09/optimizing-matrix-multiplication.md'>optimizing-matrix-multiplication.md</a>
|
||||
│ │ │ ├─ <a href='jekyll_site/ru/2021/12/12'>12</a>
|
||||
│ │ │ │ └─ <a href='jekyll_site/ru/2021/12/12/matrix-rotation-90-degrees.md'>matrix-rotation-90-degrees.md</a>
|
||||
│ │ │ └─ <a href='jekyll_site/ru/2021/12/16'>16</a>
|
||||
│ │ │ └─ <a href='jekyll_site/ru/2021/12/16/matrix-rotation-180-degrees.md'>matrix-rotation-180-degrees.md</a>
|
||||
│ │ ├─ <a href='jekyll_site/ru/2022'>2022</a>
|
||||
│ │ │ └─ <a href='jekyll_site/ru/2022/02'>02</a>
|
||||
│ │ │ ├─ <a href='jekyll_site/ru/2022/02/08'>08</a>
|
||||
│ │ │ │ └─ <a href='jekyll_site/ru/2022/02/08/matrix-multiplication-parallel-streams.md'>matrix-multiplication-parallel-streams.md</a>
|
||||
│ │ │ └─ <a href='jekyll_site/ru/2022/02/10'>10</a>
|
||||
│ │ │ └─ <a href='jekyll_site/ru/2022/02/10/winograd-strassen-algorithm.md'>winograd-strassen-algorithm.md</a>
|
||||
│ │ └─ <a href='jekyll_site/ru/index.md'>index.md</a>
|
||||
│ ├─ <a href='jekyll_site/Gemfile_color'>Gemfile_color</a>
|
||||
│ ├─ <a href='jekyll_site/Gemfile_older'>Gemfile_older</a>
|
||||
│ ├─ <a href='jekyll_site/_config_color.yml'>_config_color.yml</a>
|
||||
│ ├─ <a href='jekyll_site/_config_older.yml'>_config_older.yml</a>
|
||||
│ └─ <a href='jekyll_site/robots.txt'>robots.txt</a>
|
||||
├─ <a href='CONTRIBUTING.md'>CONTRIBUTING.md</a>
|
||||
├─ <a href='DIRECTORY-TREE.md'>DIRECTORY-TREE.md</a>
|
||||
├─ <a href='LICENSE.md'>LICENSE.md</a>
|
||||
├─ <a href='OPEN_LICENSE.txt'>OPEN_LICENSE.txt</a>
|
||||
├─ <a href='README.en.md'>README.en.md</a>
|
||||
├─ <a href='README.md'>README.md</a>
|
||||
├─ <a href='build.sh'>build.sh</a>
|
||||
├─ <a href='package.sh'>package.sh</a>
|
||||
└─ <a href='serve.sh'>serve.sh</a>
|
||||
</pre>
|
17
README.en.md
Normal file
17
README.en.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
## Website pages
|
||||
|
||||
- [Winograd — Strassen algorithm](https://pomodoro3.mircloud.ru/en/2022/02/11/winograd-strassen-algorithm.html) — 11.02.2022.
|
||||
- [Matrix multiplication in parallel streams](https://pomodoro3.mircloud.ru/en/2022/02/09/matrix-multiplication-parallel-streams.html) — 09.02.2022.
|
||||
- [Matrix rotation 180 degrees](https://pomodoro3.mircloud.ru/en/2021/12/17/matrix-rotation-180-degrees.html) — 17.12.2021.
|
||||
- [Matrix rotation 90 degrees](https://pomodoro3.mircloud.ru/en/2021/12/13/matrix-rotation-90-degrees.html) — 13.12.2021.
|
||||
- [Optimizing matrix multiplication](https://pomodoro3.mircloud.ru/en/2021/12/10/optimizing-matrix-multiplication.html) — 10.12.2021.
|
||||
|
||||
## [Source texts](README.md)
|
||||
|
||||
- Series of the static websites [«Pomodori»](https://hub.mos.ru/golovin.gg/pomodoro/blob/master/README.en.md).
|
||||
- Used formats — Markdown, Liquid, YAML.
|
||||
- Build tool — Jekyll with tomato design themes.
|
||||
- Automation of processes — Bash scripts for command line.
|
||||
- [build.sh](build.sh) — Building a site in two tomato themes and optimizing the results.
|
||||
- [serve.sh](serve.sh) — Local deployment to verify the correctness of the build.
|
||||
- [package.sh](package.sh) — Preparing an archive for subsequent deployment.
|
17
README.md
Normal file
17
README.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
## Страницы вёб-сайта
|
||||
|
||||
- [Алгоритм Винограда — Штрассена](https://pomodoro3.mircloud.ru/ru/2022/02/10/winograd-strassen-algorithm.html) — 10.02.2022.
|
||||
- [Умножение матриц в параллельных потоках](https://pomodoro3.mircloud.ru/ru/2022/02/08/matrix-multiplication-parallel-streams.html) — 08.02.2022.
|
||||
- [Поворот матрицы на 180 градусов](https://pomodoro3.mircloud.ru/ru/2021/12/16/matrix-rotation-180-degrees.html) — 16.12.2021.
|
||||
- [Поворот матрицы на 90 градусов](https://pomodoro3.mircloud.ru/ru/2021/12/12/matrix-rotation-90-degrees.html) — 12.12.2021.
|
||||
- [Оптимизация умножения матриц](https://pomodoro3.mircloud.ru/ru/2021/12/09/optimizing-matrix-multiplication.html) — 09.12.2021.
|
||||
|
||||
## [Исходные тексты](README.en.md)
|
||||
|
||||
- Серия статических вёб-сайтов [«Помидоры»](https://hub.mos.ru/golovin.gg/pomodoro/blob/master/README.md).
|
||||
- Используемые форматы — Markdown, Liquid, YAML.
|
||||
- Инструмент сборки — Jekyll с помидорными темами оформления.
|
||||
- Автоматизация процессов — Bash скрипты для командной строки.
|
||||
- [build.sh](build.sh) — Сборка сайта в двух помидорных темах и оптимизация результатов.
|
||||
- [serve.sh](serve.sh) — Локальное развёртывание для проверки корректности сборки.
|
||||
- [package.sh](package.sh) — Подготовка архива для последующего развёртывания.
|
51
build.sh
Executable file
51
build.sh
Executable file
|
@ -0,0 +1,51 @@
|
|||
#!/bin/bash
|
||||
echo "Сборка сайта в двух помидорных темах и оптимизация результатов."
|
||||
milliseconds=$(date '+%s%3N')
|
||||
rm -rf _site
|
||||
rm -rf _site_older
|
||||
rm -rf _site_color
|
||||
echo "Сборка старого помидора."
|
||||
mkdir -p _site_older
|
||||
cp -r jekyll_site/_includes _site_older
|
||||
cp -r jekyll_site/ru _site_older
|
||||
cp -r jekyll_site/en _site_older
|
||||
cp -r jekyll_site/ru/index.md _site_older
|
||||
cp -r jekyll_site/_config_older.yml _site_older/_config.yml
|
||||
cp -r jekyll_site/Gemfile_older _site_older/Gemfile
|
||||
cd _site_older || exit
|
||||
jekyll build
|
||||
cp -r _site ..
|
||||
cd ..
|
||||
echo "Сборка цветного помидора."
|
||||
mkdir -p _site_color
|
||||
cp -r jekyll_site/_includes _site_color
|
||||
cp -r jekyll_site/ru _site_color
|
||||
cp -r jekyll_site/en _site_color
|
||||
cp -r jekyll_site/ru/index.md _site_color
|
||||
cp -r jekyll_site/_config_color.yml _site_color/_config.yml
|
||||
cp -r jekyll_site/Gemfile_color _site_color/Gemfile
|
||||
cd _site_color || exit
|
||||
jekyll build
|
||||
cp -r _site ../_site/color
|
||||
cd ..
|
||||
echo "Копирование без сборки."
|
||||
cp -r jekyll_site/img _site
|
||||
cp -r jekyll_site/robots.txt _site
|
||||
echo "Оптимизация собранного контента."
|
||||
cd _site || exit
|
||||
cp -r assets/* .
|
||||
rm -r assets
|
||||
rm -r color/assets/favicon.ico
|
||||
cp -r color/assets/* .
|
||||
rm -r color/assets
|
||||
rm -r color/404.html
|
||||
find . -type f -name '*.html' | sort -r | while read -r file; do
|
||||
sed -i 's/layout-padding=""/layout-padding/g' "$file"
|
||||
sed -i 's/ class="language-plaintext highlighter-rouge"//g' "$file"
|
||||
sed -i 's/ class="language-java highlighter-rouge"//g' "$file"
|
||||
sed -i 's/<div><div class="highlight"><pre class="highlight">/<div class="highlight"><pre class="highlight">/g' "$file"
|
||||
sed -i 's/<\/code><\/pre><\/div><\/div>/<\/code><\/pre><\/div>/g' "$file"
|
||||
sed -i 's/<hr \/>/<hr>/g' "$file"
|
||||
sed -i -r 's/<img(.+) \/>/<img\1>/g' "$file"
|
||||
done
|
||||
echo "Время выполнения сборки: $(("$(date '+%s%3N')" - "$milliseconds")) мс."
|
3
jekyll_site/Gemfile_color
Normal file
3
jekyll_site/Gemfile_color
Normal file
|
@ -0,0 +1,3 @@
|
|||
source "https://rubygems.org"
|
||||
gem "jekyll"
|
||||
gem "color-tomato-theme"
|
3
jekyll_site/Gemfile_older
Normal file
3
jekyll_site/Gemfile_older
Normal file
|
@ -0,0 +1,3 @@
|
|||
source "https://rubygems.org"
|
||||
gem "jekyll"
|
||||
gem "older-tomato-theme"
|
20
jekyll_site/_config_color.yml
Normal file
20
jekyll_site/_config_color.yml
Normal file
|
@ -0,0 +1,20 @@
|
|||
# site parameters
|
||||
name: "Код с комментариями"
|
||||
name_translated: "Code with comments"
|
||||
url: "https://pomodoro3.mircloud.ru"
|
||||
baseurl: "/color"
|
||||
homepage_url: "https://git.org.ru/pomodoro/3"
|
||||
homepage_name: "GIT.ORG.RU"
|
||||
older_tomato_baseurl: ""
|
||||
timezone: "Europe/Moscow"
|
||||
author: "Головин Г.Г."
|
||||
author_translated: "Golovin G.G."
|
||||
translation_caption: "translation from Russian"
|
||||
# build parameters
|
||||
disable_disk_cache: true
|
||||
theme: color-tomato-theme
|
||||
defaults:
|
||||
- scope:
|
||||
path: ""
|
||||
values:
|
||||
layout: default
|
20
jekyll_site/_config_older.yml
Normal file
20
jekyll_site/_config_older.yml
Normal file
|
@ -0,0 +1,20 @@
|
|||
# site parameters
|
||||
name: "Код с комментариями"
|
||||
name_translated: "Code with comments"
|
||||
url: "https://pomodoro3.mircloud.ru"
|
||||
baseurl: ""
|
||||
homepage_url: "https://git.org.ru/pomodoro/3"
|
||||
homepage_name: "GIT.ORG.RU"
|
||||
color_tomato_baseurl: "/color"
|
||||
timezone: "Europe/Moscow"
|
||||
author: "Головин Г.Г."
|
||||
author_translated: "Golovin G.G."
|
||||
translation_caption: "translation from Russian"
|
||||
# build parameters
|
||||
disable_disk_cache: true
|
||||
theme: older-tomato-theme
|
||||
defaults:
|
||||
- scope:
|
||||
path: ""
|
||||
values:
|
||||
layout: default
|
2
jekyll_site/_includes/counters_body.html
Normal file
2
jekyll_site/_includes/counters_body.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
<noscript><div><img src="https://mc.yandex.ru/watch/87058815" style="position:absolute; left:-9999px;" alt=""></div></noscript>
|
||||
<!-- /Yandex.Metrika counter -->
|
16
jekyll_site/_includes/counters_head.html
Normal file
16
jekyll_site/_includes/counters_head.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-209134013-3"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'UA-209134013-3');
|
||||
</script>
|
||||
<!-- Yandex.Metrika counter -->
|
||||
<script>
|
||||
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
||||
m[i].l=1*new Date();for(var j=0;j<document.scripts.length;j++){if(document.scripts[j].src===r){return;}}
|
||||
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
|
||||
(window,document,"script","https://mc.yandex.ru/metrika/tag.js","ym");
|
||||
ym(87058815,"init",{clickmap:true,trackLinks:true,accurateTrackBounce:true,webvisor:true});
|
||||
</script>
|
248
jekyll_site/en/2021/12/10/optimizing-matrix-multiplication.md
Normal file
248
jekyll_site/en/2021/12/10/optimizing-matrix-multiplication.md
Normal file
|
@ -0,0 +1,248 @@
|
|||
---
|
||||
title: Optimizing matrix multiplication
|
||||
description: Consider an algorithm for multiplying matrices using three nested loops. The complexity of such an algorithm by definition should be O(n³), but there are...
|
||||
sections: [Permutations,Nested loops,Comparing algorithms]
|
||||
tags: [java,arrays,multidimensional arrays,matrices,rows,columns,layers,loops]
|
||||
canonical_url: /en/2021/12/10/optimizing-matrix-multiplication.html
|
||||
url_translated: /ru/2021/12/09/optimizing-matrix-multiplication.html
|
||||
title_translated: Оптимизация умножения матриц
|
||||
date: 2021.12.10
|
||||
lang: en
|
||||
---
|
||||
|
||||
Consider an algorithm for multiplying matrices using three nested loops. The complexity of such
|
||||
an algorithm by definition should be `O(n³)`, but there are particularities related to the execution
|
||||
environment — the speed of the algorithm depends on the sequence in which the loops are executed.
|
||||
|
||||
Let's compare different permutations of nested loops and the execution time of the algorithms.
|
||||
Let's take two matrices: {`L×M`} and {`M×N`} → three loops → six permutations:
|
||||
`LMN`, `LNM`, `MLN`, `MNL`, `NLM`, `NML`.
|
||||
|
||||
The algorithms that work faster than others are those that write data to the resulting matrix
|
||||
*row-wise in layers*: `LMN` and `MLN`, — the percentage difference to other algorithms is substantial
|
||||
and depends on the execution environment.
|
||||
|
||||
*Further optimization: [Matrix multiplication in parallel streams]({{ '/en/2022/02/09/matrix-multiplication-parallel-streams.html' | relative_url }}).*
|
||||
|
||||
## Row-wise algorithm {#row-wise-algorithm}
|
||||
|
||||
The outer loop bypasses the rows of the first matrix `L`, then there is a loop across the *common side*
|
||||
of the two matrices `M` and it is followed by a loop across the columns of the second matrix `N`.
|
||||
Writing to the resulting matrix occurs row-wise, and each row is filled in layers.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param l rows of matrix 'a'
|
||||
* @param m columns of matrix 'a'
|
||||
* and rows of matrix 'b'
|
||||
* @param n columns of matrix 'b'
|
||||
* @param a first matrix 'l×m'
|
||||
* @param b second matrix 'm×n'
|
||||
* @return resulting matrix 'l×n'
|
||||
*/
|
||||
public static int[][] matrixMultiplicationLMN(int l, int m, int n, int[][] a, int[][] b) {
|
||||
// resulting matrix
|
||||
int[][] c = new int[l][n];
|
||||
// bypass the indexes of the rows of matrix 'a'
|
||||
for (int i = 0; i < l; i++)
|
||||
// bypass the indexes of the common side of two matrices:
|
||||
// the columns of matrix 'a' and the rows of matrix 'b'
|
||||
for (int k = 0; k < m; k++)
|
||||
// bypass the indexes of the columns of matrix 'b'
|
||||
for (int j = 0; j < n; j++)
|
||||
// the sum of the products of the elements of the i-th
|
||||
// row of matrix 'a' and the j-th column of matrix 'b'
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
|
||||
## Layer-wise algorithm {#layer-wise-algorithm}
|
||||
|
||||
The outer loop bypasses the *common side* of the two matrices `M`, then there is a loop across the rows
|
||||
of the first matrix `L`, and it is followed by a loop across the columns of the second matrix `N`.
|
||||
Writing to the resulting matrix occurs layer-wise, and each layer is filled row-wise.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param l rows of matrix 'a'
|
||||
* @param m columns of matrix 'a'
|
||||
* and rows of matrix 'b'
|
||||
* @param n columns of matrix 'b'
|
||||
* @param a first matrix 'l×m'
|
||||
* @param b second matrix 'm×n'
|
||||
* @return resulting matrix 'l×n'
|
||||
*/
|
||||
public static int[][] matrixMultiplicationMLN(int l, int m, int n, int[][] a, int[][] b) {
|
||||
// resulting matrix
|
||||
int[][] c = new int[l][n];
|
||||
// bypass the indexes of the common side of two matrices:
|
||||
// the columns of matrix 'a' and the rows of matrix 'b'
|
||||
for (int k = 0; k < m; k++)
|
||||
// bypass the indexes of the rows of matrix 'a'
|
||||
for (int i = 0; i < l; i++)
|
||||
// bypass the indexes of the columns of matrix 'b'
|
||||
for (int j = 0; j < n; j++)
|
||||
// the sum of the products of the elements of the i-th
|
||||
// row of matrix 'a' and the j-th column of matrix 'b'
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
|
||||
### Other algorithms {#other-algorithms}
|
||||
|
||||
The bypass of the columns of the second matrix `N` occurs before the bypass of the *common side* of the
|
||||
two matrices `M` and/or before the bypass of the rows of the first matrix `L`.
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
public static int[][] matrixMultiplicationLNM(int l, int m, int n, int[][] a, int[][] b) {
|
||||
int[][] c = new int[l][n];
|
||||
for (int i = 0; i < l; i++)
|
||||
for (int j = 0; j < n; j++)
|
||||
for (int k = 0; k < m; k++)
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
```java
|
||||
public static int[][] matrixMultiplicationNLM(int l, int m, int n, int[][] a, int[][] b) {
|
||||
int[][] c = new int[l][n];
|
||||
for (int j = 0; j < n; j++)
|
||||
for (int i = 0; i < l; i++)
|
||||
for (int k = 0; k < m; k++)
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
```java
|
||||
public static int[][] matrixMultiplicationMNL(int l, int m, int n, int[][] a, int[][] b) {
|
||||
int[][] c = new int[l][n];
|
||||
for (int k = 0; k < m; k++)
|
||||
for (int j = 0; j < n; j++)
|
||||
for (int i = 0; i < l; i++)
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
```java
|
||||
public static int[][] matrixMultiplicationNML(int l, int m, int n, int[][] a, int[][] b) {
|
||||
int[][] c = new int[l][n];
|
||||
for (int j = 0; j < n; j++)
|
||||
for (int k = 0; k < m; k++)
|
||||
for (int i = 0; i < l; i++)
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Code without comments" content=collapsed_md -%}
|
||||
|
||||
## Comparing algorithms {#comparing-algorithms}
|
||||
|
||||
To check, we take two matrices `A=[500×700]` and `B=[700×450]`, filled with random numbers. First, we
|
||||
compare the correctness of the implementation of the algorithms — all results obtained must match.
|
||||
Then we execute each method 10 times and calculate the average execution time.
|
||||
|
||||
```java
|
||||
// start the program and output the result
|
||||
public static void main(String[] args) throws Exception {
|
||||
// incoming data
|
||||
int l = 500, m = 700, n = 450, steps = 10;
|
||||
int[][] a = randomMatrix(l, m), b = randomMatrix(m, n);
|
||||
// map of methods for comparison
|
||||
var methods = new TreeMap<String, Callable<int[][]>>(Map.of(
|
||||
"LMN", () -> matrixMultiplicationLMN(l, m, n, a, b),
|
||||
"LNM", () -> matrixMultiplicationLNM(l, m, n, a, b),
|
||||
"MLN", () -> matrixMultiplicationMLN(l, m, n, a, b),
|
||||
"MNL", () -> matrixMultiplicationMNL(l, m, n, a, b),
|
||||
"NLM", () -> matrixMultiplicationNLM(l, m, n, a, b),
|
||||
"NML", () -> matrixMultiplicationNML(l, m, n, a, b)));
|
||||
int[][] last = null;
|
||||
// bypass the methods map, check the correctness of the returned
|
||||
// results, all results obtained must be equal to each other
|
||||
for (var method : methods.entrySet()) {
|
||||
// next method for comparison
|
||||
var next = methods.higherEntry(method.getKey());
|
||||
// if the current method is not the last — compare the results of two methods
|
||||
if (next != null) System.out.println(method.getKey() + "=" + next.getKey() + ": "
|
||||
// compare the result of executing the current method and the next one
|
||||
+ Arrays.deepEquals(method.getValue().call(), next.getValue().call()));
|
||||
// the result of the last method
|
||||
else last = method.getValue().call();
|
||||
}
|
||||
int[][] test = last;
|
||||
// bypass the methods map, measure the execution time of each method
|
||||
for (var method : methods.entrySet())
|
||||
// parameters: title, number of steps, runnable code
|
||||
benchmark(method.getKey(), steps, () -> {
|
||||
try { // execute the method, get the result
|
||||
int[][] result = method.getValue().call();
|
||||
// check the correctness of the results at each step
|
||||
if (!Arrays.deepEquals(result, test)) System.out.print("error");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
// helper method, returns a matrix of the specified size
|
||||
private static int[][] randomMatrix(int row, int col) {
|
||||
int[][] matrix = new int[row][col];
|
||||
for (int i = 0; i < row; i++)
|
||||
for (int j = 0; j < col; j++)
|
||||
matrix[i][j] = (int) (Math.random() * row * col);
|
||||
return matrix;
|
||||
}
|
||||
```
|
||||
```java
|
||||
// helper method for measuring the execution time of the passed code
|
||||
private static void benchmark(String title, int steps, Runnable runnable) {
|
||||
long time, avg = 0;
|
||||
System.out.print(title);
|
||||
for (int i = 0; i < steps; i++) {
|
||||
time = System.currentTimeMillis();
|
||||
runnable.run();
|
||||
time = System.currentTimeMillis() - time;
|
||||
// execution time of one step
|
||||
System.out.print(" | " + time);
|
||||
avg += time;
|
||||
}
|
||||
// average execution time
|
||||
System.out.println(" || " + (avg / steps));
|
||||
}
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Helper methods" content=collapsed_md -%}
|
||||
|
||||
Output depends on the execution environment, time in milliseconds:
|
||||
|
||||
```
|
||||
LMN=LNM: true
|
||||
LNM=MLN: true
|
||||
MLN=MNL: true
|
||||
MNL=NLM: true
|
||||
NLM=NML: true
|
||||
LMN | 191 | 109 | 105 | 106 | 105 | 106 | 106 | 105 | 123 | 109 || 116
|
||||
LNM | 417 | 418 | 419 | 416 | 416 | 417 | 418 | 417 | 416 | 417 || 417
|
||||
MLN | 113 | 115 | 113 | 115 | 114 | 114 | 114 | 115 | 114 | 113 || 114
|
||||
MNL | 857 | 864 | 857 | 859 | 860 | 863 | 862 | 860 | 858 | 860 || 860
|
||||
NLM | 404 | 404 | 407 | 404 | 406 | 405 | 405 | 404 | 403 | 404 || 404
|
||||
NML | 866 | 872 | 867 | 868 | 867 | 868 | 867 | 873 | 869 | 863 || 868
|
||||
```
|
||||
|
||||
All the methods described above, including collapsed blocks, can be placed in one class.
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.Callable;
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Required imports" content=collapsed_md -%}
|
97
jekyll_site/en/2021/12/13/matrix-rotation-90-degrees.md
Normal file
97
jekyll_site/en/2021/12/13/matrix-rotation-90-degrees.md
Normal file
|
@ -0,0 +1,97 @@
|
|||
---
|
||||
title: Matrix rotation 90 degrees
|
||||
description: Consider the algorithm for rotating a matrix 90 degrees clockwise and anticlockwise. This algorithm is similar to matrix transpose, with the difference...
|
||||
sections: [Transpose,Comparing algorithms]
|
||||
tags: [java,arrays,multidimensional arrays,matrices,rows,columns,loops,nested loops]
|
||||
canonical_url: /en/2021/12/13/matrix-rotation-90-degrees.html
|
||||
url_translated: /ru/2021/12/12/matrix-rotation-90-degrees.html
|
||||
title_translated: Поворот матрицы на 90 градусов
|
||||
date: 2021.12.13
|
||||
lang: en
|
||||
---
|
||||
|
||||
Consider the algorithm for rotating a matrix 90 degrees clockwise and anticlockwise. This
|
||||
algorithm is similar to *matrix transpose*, with the difference that here, for each point,
|
||||
one of the coordinates is mirrored.
|
||||
|
||||
```java
|
||||
// matrix transpose — rows and columns are swapped
|
||||
swapped[j][i] = matrix[i][j];
|
||||
// clockwise ↻ — rows are mirrored
|
||||
rotated[j][i] = matrix[m-i-1][j];
|
||||
// anticlockwise ↺ — columns are mirrored
|
||||
rotated[j][i] = matrix[i][n-j-1];
|
||||
```
|
||||
|
||||
*Similar algorithm: [Matrix rotation 180 degrees]({{ '/en/2021/12/17/matrix-rotation-180-degrees.html' | relative_url }}).*
|
||||
|
||||
Let's write a method in Java to rotate a matrix {`m×n`} 90 degrees. The additional parameter
|
||||
sets the direction of rotation: clockwise or anticlockwise. As an example, let's take a
|
||||
rectangular matrix {`4×3`}.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param m number of rows of the original matrix
|
||||
* @param n number of columns of the original matrix
|
||||
* @param clock direction of rotation:
|
||||
* clockwise ↻ or anticlockwise ↺
|
||||
* @param matrix the original matrix
|
||||
* @return the rotated matrix
|
||||
*/
|
||||
public static int[][] rotateMatrix(int m, int n, boolean clock, int[][] matrix) {
|
||||
// new matrix, the number of rows and columns are swapped
|
||||
int[][] rotated = new int[n][m];
|
||||
// bypass the rows of the original matrix
|
||||
for (int i = 0; i < m; i++)
|
||||
// bypass the columns of the original matrix
|
||||
for (int j = 0; j < n; j++)
|
||||
if (clock) // clockwise rotation ↻
|
||||
rotated[j][i] = matrix[m-i-1][j];
|
||||
else // anticlockwise rotation ↺
|
||||
rotated[j][i] = matrix[i][n-j-1];
|
||||
return rotated;
|
||||
}
|
||||
```
|
||||
{% raw %}
|
||||
```java
|
||||
// start the program and output the result
|
||||
public static void main(String[] args) {
|
||||
// incoming data
|
||||
int m = 4, n = 3;
|
||||
int[][] matrix = {{11, 12, 13}, {14, 15, 16}, {17, 18, 19}, {20, 21, 22}};
|
||||
// rotate the matrix and output the result
|
||||
outputMatrix("Original matrix:", matrix);
|
||||
outputMatrix("Clockwise ↻:", rotateMatrix(m, n, true, matrix));
|
||||
outputMatrix("Anticlockwise ↺:", rotateMatrix(m, n, false, matrix));
|
||||
}
|
||||
```
|
||||
{% endraw %}
|
||||
```java
|
||||
// helper method, prints the matrix to the console row-wise
|
||||
public static void outputMatrix(String title, int[][] matrix) {
|
||||
System.out.println(title);
|
||||
for (int[] row : matrix) {
|
||||
for (int el : row)
|
||||
System.out.print(" " + el);
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
Original matrix:
|
||||
11 12 13
|
||||
14 15 16
|
||||
17 18 19
|
||||
20 21 22
|
||||
Clockwise ↻:
|
||||
20 17 14 11
|
||||
21 18 15 12
|
||||
22 19 16 13
|
||||
Anticlockwise ↺:
|
||||
13 16 19 22
|
||||
12 15 18 21
|
||||
11 14 17 20
|
||||
```
|
85
jekyll_site/en/2021/12/17/matrix-rotation-180-degrees.md
Normal file
85
jekyll_site/en/2021/12/17/matrix-rotation-180-degrees.md
Normal file
|
@ -0,0 +1,85 @@
|
|||
---
|
||||
title: Matrix rotation 180 degrees
|
||||
description: Consider the algorithm for rotating a matrix 180 degrees. Unlike the transpose algorithm, here in the resulting matrix the rows and columns are not swapped...
|
||||
sections: [Transpose,Comparing algorithms]
|
||||
tags: [java,arrays,multidimensional arrays,matrices,rows,columns,loops,nested loops]
|
||||
canonical_url: /en/2021/12/17/matrix-rotation-180-degrees.html
|
||||
url_translated: /ru/2021/12/16/matrix-rotation-180-degrees.html
|
||||
title_translated: Поворот матрицы на 180 градусов
|
||||
date: 2021.12.17
|
||||
lang: en
|
||||
---
|
||||
|
||||
Consider the algorithm for rotating a matrix 180 degrees. Unlike the *transpose* algorithm,
|
||||
here in the resulting matrix the rows and columns are not swapped, but are mirrored.
|
||||
|
||||
```java
|
||||
// rows and columns are swapped
|
||||
swapped[j][i] = matrix[i][j];
|
||||
// rows and columns are mirrored
|
||||
rotated[i][j] = matrix[m-i-1][n-j-1];
|
||||
```
|
||||
|
||||
*Similar algorithm: [Matrix rotation 90 degrees]({{ '/en/2021/12/13/matrix-rotation-90-degrees.html' | relative_url }}).*
|
||||
|
||||
Let's write a method in Java to rotate a matrix {`m×n`} 180 degrees. As an example,
|
||||
let's take a rectangular matrix {`4×3`}.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param m number of rows of the original matrix
|
||||
* @param n number of columns of the original matrix
|
||||
* @param matrix the original matrix
|
||||
* @return the rotated matrix
|
||||
*/
|
||||
public static int[][] rotateMatrix(int m, int n, int[][] matrix) {
|
||||
// new matrix
|
||||
int[][] rotated = new int[m][n];
|
||||
// bypass the rows of the original matrix
|
||||
for (int i = 0; i < m; i++)
|
||||
// bypass the columns of the original matrix
|
||||
for (int j = 0; j < n; j++)
|
||||
// rows and columns are mirrored
|
||||
rotated[i][j] = matrix[m-i-1][n-j-1];
|
||||
return rotated;
|
||||
}
|
||||
```
|
||||
{% raw %}
|
||||
```java
|
||||
// start the program and output the result
|
||||
public static void main(String[] args) {
|
||||
// incoming data
|
||||
int m = 4, n = 3;
|
||||
int[][] matrix = {{11, 12, 13}, {14, 15, 16}, {17, 18, 19}, {20, 21, 22}};
|
||||
// rotate the matrix and output the result
|
||||
outputMatrix("Original matrix:", matrix);
|
||||
outputMatrix("Rotation by 180°:", rotateMatrix(m, n, matrix));
|
||||
}
|
||||
```
|
||||
{% endraw %}
|
||||
```java
|
||||
// helper method, prints the matrix to the console row-wise
|
||||
public static void outputMatrix(String title, int[][] matrix) {
|
||||
System.out.println(title);
|
||||
for (int[] row : matrix) {
|
||||
for (int el : row)
|
||||
System.out.print(" " + el);
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
Original matrix:
|
||||
11 12 13
|
||||
14 15 16
|
||||
17 18 19
|
||||
20 21 22
|
||||
Rotation by 180°:
|
||||
22 21 20
|
||||
19 18 17
|
||||
16 15 14
|
||||
13 12 11
|
||||
```
|
|
@ -0,0 +1,184 @@
|
|||
---
|
||||
title: Matrix multiplication in parallel streams
|
||||
description: Consider an algorithm for multiplying rectangular matrices using Java Streams. Let's take the optimized version of the algorithm using three nested loops...
|
||||
sections: [Multithreading,Nested loops,Comparing algorithms]
|
||||
tags: [java,streams,arrays,multidimensional arrays,matrices,rows,loops]
|
||||
canonical_url: /en/2022/02/09/matrix-multiplication-parallel-streams.html
|
||||
url_translated: /ru/2022/02/08/matrix-multiplication-parallel-streams.html
|
||||
title_translated: Умножение матриц в параллельных потоках
|
||||
date: 2022.02.09
|
||||
lang: en
|
||||
---
|
||||
|
||||
Consider an algorithm for multiplying rectangular matrices using Java Streams. Let's take the
|
||||
*optimized version* of the algorithm using three nested loops and replace the outer loop with
|
||||
a stream. Let's compare the operating time of two algorithms.
|
||||
|
||||
We process the rows of the resulting matrix in parallel mode, and populate each row layerwise. Due to
|
||||
the parallel use of cache of the execution environment on multi-core machines, the computation time can
|
||||
be reduced by more than two times. To check, let's take two rectangular matrices {`L×M`} and {`M×N`}.
|
||||
|
||||
*Further optimization: [Winograd — Strassen algorithm]({{ '/en/2022/02/11/winograd-strassen-algorithm.html' | relative_url }}).*
|
||||
|
||||
## Parallel stream {#parallel-stream}
|
||||
|
||||
We bypass the rows of the first matrix `L` in parallel mode. In each thread there are two nested loops:
|
||||
across the *common side* of two matrices `M` and across the columns of the second matrix `N`. Processing
|
||||
of the rows of the resulting matrix occurs independently of each other in parallel streams.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param l rows of matrix 'a'
|
||||
* @param m columns of matrix 'a'
|
||||
* and rows of matrix 'b'
|
||||
* @param n columns of matrix 'b'
|
||||
* @param a first matrix 'l×m'
|
||||
* @param b second matrix 'm×n'
|
||||
* @return resulting matrix 'l×n'
|
||||
*/
|
||||
public static int[][] parallelMatrixMultiplication(int l, int m, int n, int[][] a, int[][] b) {
|
||||
// resulting matrix
|
||||
int[][] c = new int[l][n];
|
||||
// bypass the indexes of the rows of matrix 'a' in parallel mode
|
||||
IntStream.range(0, l).parallel().forEach(i -> {
|
||||
// bypass the indexes of the common side of two matrices:
|
||||
// the columns of matrix 'a' and the rows of matrix 'b'
|
||||
for (int k = 0; k < m; k++)
|
||||
// bypass the indexes of the columns of matrix 'b'
|
||||
for (int j = 0; j < n; j++)
|
||||
// the sum of the products of the elements of the i-th
|
||||
// row of matrix 'a' and the j-th column of matrix 'b'
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
});
|
||||
return c;
|
||||
}
|
||||
```
|
||||
|
||||
## Three nested loops {#three-nested-loops}
|
||||
|
||||
We take the *optimized* variant of algorithm, that uses cache of the execution environment better
|
||||
than others. The outer loop bypasses the rows of the first matrix `L`, then there is a loop across
|
||||
the *common side* of the two matrices `M` and it is followed by a loop across the columns of the
|
||||
second matrix `N`. Writing to the resulting matrix occurs row-wise, and each row is filled in layers.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param l rows of matrix 'a'
|
||||
* @param m columns of matrix 'a'
|
||||
* and rows of matrix 'b'
|
||||
* @param n columns of matrix 'b'
|
||||
* @param a first matrix 'l×m'
|
||||
* @param b second matrix 'm×n'
|
||||
* @return resulting matrix 'l×n'
|
||||
*/
|
||||
public static int[][] sequentialMatrixMultiplication(int l, int m, int n, int[][] a, int[][] b) {
|
||||
// resulting matrix
|
||||
int[][] c = new int[l][n];
|
||||
// bypass the indexes of the rows of matrix 'a'
|
||||
for (int i = 0; i < l; i++)
|
||||
// bypass the indexes of the common side of two matrices:
|
||||
// the columns of matrix 'a' and the rows of matrix 'b'
|
||||
for (int k = 0; k < m; k++)
|
||||
// bypass the indexes of the columns of matrix 'b'
|
||||
for (int j = 0; j < n; j++)
|
||||
// the sum of the products of the elements of the i-th
|
||||
// row of matrix 'a' and the j-th column of matrix 'b'
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
|
||||
## Testing {#testing}
|
||||
|
||||
To check, we take two matrices `A=[900×1000]` and `B=[1000×750]`, filled with random numbers.
|
||||
First, we compare the correctness of the implementation of the two algorithms — matrix products
|
||||
must match. Then we execute each method 10 times and calculate the average execution time.
|
||||
|
||||
```java
|
||||
// start the program and output the result
|
||||
public static void main(String[] args) {
|
||||
// incoming data
|
||||
int l = 900, m = 1000, n = 750, steps = 10;
|
||||
int[][] a = randomMatrix(l, m), b = randomMatrix(m, n);
|
||||
// matrix products
|
||||
int[][] c1 = parallelMatrixMultiplication(l, m, n, a, b);
|
||||
int[][] c2 = sequentialMatrixMultiplication(l, m, n, a, b);
|
||||
// check the correctness of the results
|
||||
System.out.println("The results match: " + Arrays.deepEquals(c1, c2));
|
||||
// measure the execution time of two methods
|
||||
benchmark("Stream", steps, () -> {
|
||||
int[][] c = parallelMatrixMultiplication(l, m, n, a, b);
|
||||
if (!Arrays.deepEquals(c, c1)) System.out.print("error");
|
||||
});
|
||||
benchmark("Loops", steps, () -> {
|
||||
int[][] c = sequentialMatrixMultiplication(l, m, n, a, b);
|
||||
if (!Arrays.deepEquals(c, c2)) System.out.print("error");
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
// helper method, returns a matrix of the specified size
|
||||
private static int[][] randomMatrix(int row, int col) {
|
||||
int[][] matrix = new int[row][col];
|
||||
for (int i = 0; i < row; i++)
|
||||
for (int j = 0; j < col; j++)
|
||||
matrix[i][j] = (int) (Math.random() * row * col);
|
||||
return matrix;
|
||||
}
|
||||
```
|
||||
```java
|
||||
// helper method for measuring the execution time of the passed code
|
||||
private static void benchmark(String title, int steps, Runnable runnable) {
|
||||
long time, avg = 0;
|
||||
System.out.print(title);
|
||||
for (int i = 0; i < steps; i++) {
|
||||
time = System.currentTimeMillis();
|
||||
runnable.run();
|
||||
time = System.currentTimeMillis() - time;
|
||||
// execution time of one step
|
||||
System.out.print(" | " + time);
|
||||
avg += time;
|
||||
}
|
||||
// average execution time
|
||||
System.out.println(" || " + (avg / steps));
|
||||
}
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Helper methods" content=collapsed_md -%}
|
||||
|
||||
Output depends on the execution environment, time in milliseconds:
|
||||
|
||||
```
|
||||
The results match: true
|
||||
Stream | 113 | 144 | 117 | 114 | 114 | 117 | 120 | 125 | 111 | 113 || 118
|
||||
Loops | 1357 | 530 | 551 | 569 | 535 | 538 | 525 | 517 | 518 | 514 || 615
|
||||
```
|
||||
|
||||
## Comparing algorithms {#comparing-algorithms}
|
||||
|
||||
On an eight-core Linux x64 computer, we create a Windows x64 virtual machine for tests. All
|
||||
other things being equal, in the settings, we change the number of processors. We run the
|
||||
above test 100 times instead of 10 — we get a summary table of results. Time in milliseconds.
|
||||
|
||||
```
|
||||
CPU | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|
||||
-------|-----|-----|-----|-----|-----|-----|-----|-----|
|
||||
Stream | 522 | 276 | 179 | 154 | 128 | 112 | 110 | 93 |
|
||||
Loops | 519 | 603 | 583 | 571 | 545 | 571 | 559 | 467 |
|
||||
```
|
||||
|
||||
Results: with a large number of processors in the system, the multithreaded algorithm works
|
||||
out noticeably faster than three nested loops. The computation time can be reduced by more
|
||||
than two times.
|
||||
|
||||
All the methods described above, including collapsed blocks, can be placed in one class.
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.IntStream;
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Required imports" content=collapsed_md -%}
|
298
jekyll_site/en/2022/02/11/winograd-strassen-algorithm.md
Normal file
298
jekyll_site/en/2022/02/11/winograd-strassen-algorithm.md
Normal file
|
@ -0,0 +1,298 @@
|
|||
---
|
||||
title: Winograd — Strassen algorithm
|
||||
description: Consider a modification of Strassen's algorithm for square matrix multiplication with fewer number of summations between blocks than in the ordinary...
|
||||
sections: [Multithreading,Block matrices,Comparing algorithms]
|
||||
tags: [java,streams,arrays,multidimensional arrays,matrices,recursion,loops,nested loops]
|
||||
canonical_url: /en/2022/02/11/winograd-strassen-algorithm.html
|
||||
url_translated: /ru/2022/02/10/winograd-strassen-algorithm.html
|
||||
title_translated: Алгоритм Винограда — Штрассена
|
||||
date: 2022.02.11
|
||||
lang: en
|
||||
---
|
||||
|
||||
Consider a modification of Strassen's algorithm for square matrix multiplication with *fewer* number
|
||||
of summations between blocks than in the ordinary algorithm — 15 instead of 18 and the same number
|
||||
of multiplications as in the ordinary algorithm — 7. We will use Java Streams.
|
||||
|
||||
Recursive partitioning of matrices into blocks during multiplication makes sense up to a certain
|
||||
limit, and then it loses its sense, since the Strassen's algorithm does not use cache of the execution
|
||||
environment. Therefore, for small blocks we will use a parallel version of nested loops, and for large
|
||||
blocks we will perform recursive partitioning in parallel.
|
||||
|
||||
We determine the boundary between the two algorithms experimentally — we adjust it to the cache of the
|
||||
execution environment. The benefit of Strassen's algorithm becomes more evident on sizable matrices —
|
||||
the difference with the algorithm using nested loops becomes larger and depends on the execution
|
||||
environment. Let's compare the operating time of two algorithms.
|
||||
|
||||
*Algorithm using three nested loops: [Optimizing matrix multiplication]({{ '/en/2021/12/10/optimizing-matrix-multiplication.html' | relative_url }}).*
|
||||
|
||||
## Algorithm description {#algorithm-description}
|
||||
|
||||
Matrices must be the same size. We partition each matrix into 4 equally sized blocks. The blocks
|
||||
must be square, therefore if this is not the case, then first we supplement the matrices with zero
|
||||
rows and columns, and after that partition them into blocks. We will remove the redundant rows and
|
||||
columns later from the resulting matrix.
|
||||
|
||||
{% include image_svg.html src="/img/block-matrices.svg" style="width:221pt; height:33pt;"
|
||||
alt="{\displaystyle A={\begin{pmatrix}A_{11}&A_{12}\\A_{21}&A_{22}\end{pmatrix}},\quad B={\begin{pmatrix}B_{11}&B_{12}\\B_{21}&B_{22}\end{pmatrix}}.}" %}
|
||||
|
||||
Summation of blocks.
|
||||
|
||||
{% include image_svg.html src="/img/sums1.svg" style="width:101pt; height:148pt;"
|
||||
alt="{\displaystyle{\begin{aligned}S_{1}&=(A_{21}+A_{22});\\S_{2}&=(S_{1}-A_{11});\\S_{3}&=(A_{11}-A_{21});\\S_{4}&=(A_{12}-S_{2});\\S_{5}&=(B_{12}-B_{11});\\S_{6}&=(B_{22}-S_{5});\\S_{7}&=(B_{22}-B_{12});\\S_{8}&=(S_{6}-B_{21}).\end{aligned}}}" %}
|
||||
|
||||
Multiplication of blocks.
|
||||
|
||||
{% include image_svg.html src="/img/products.svg" style="width:75pt; height:127pt;"
|
||||
alt="{\displaystyle{\begin{aligned}P_{1}&=S_{2}S_{6};\\P_{2}&=A_{11}B_{11};\\P_{3}&=A_{12}B_{21};\\P_{4}&=S_{3}S_{7};\\P_{5}&=S_{1}S_{5};\\P_{6}&=S_{4}B_{22};\\P_{7}&=A_{22}S_{8}.\end{aligned}}}" %}
|
||||
|
||||
Summation of blocks.
|
||||
|
||||
{% include image_svg.html src="/img/sums2.svg" style="width:78pt; height:31pt;"
|
||||
alt="{\displaystyle{\begin{aligned}T_{1}&=P_{1}+P_{2};\\T_{2}&=T_{1}+P_{4}.\end{aligned}}}" %}
|
||||
|
||||
Blocks of the resulting matrix.
|
||||
|
||||
{% include image_svg.html src="/img/sums3.svg" style="width:240pt; height:33pt;"
|
||||
alt="{\displaystyle{\begin{pmatrix}C_{11}&C_{12}\\C_{21}&C_{22}\end{pmatrix}}={\begin{pmatrix}P_{2}+P_{3}&T_{1}+P_{5}+P_{6}\\T_{2}-P_{7}&T_{2}+P_{5}\end{pmatrix}}.}" %}
|
||||
|
||||
## Hybrid algorithm {#hybrid-algorithm}
|
||||
|
||||
We partition each matrix `A` and `B` into 4 equally sized blocks and, if necessary, we supplement
|
||||
the missing parts with zeros. Perform 15 summations and 7 multiplications over the blocks — we get
|
||||
4 blocks of the matrix `C`. Remove the redundant zeros, if added, and return the resulting matrix.
|
||||
We run recursive partitioning of large blocks in parallel mode, and for small blocks we call the
|
||||
algorithm with nested loops.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param n matrix size
|
||||
* @param brd minimum matrix size
|
||||
* @param a first matrix 'n×n'
|
||||
* @param b second matrix 'n×n'
|
||||
* @return resulting matrix 'n×n'
|
||||
*/
|
||||
public static int[][] multiplyMatrices(int n, int brd, int[][] a, int[][] b) {
|
||||
// multiply small blocks using algorithm with nested loops
|
||||
if (n < brd) return simpleMultiplication(n, a, b);
|
||||
// midpoint of the matrix, round up — blocks should
|
||||
// be square, if necessary add zero rows and columns
|
||||
int m = n - n / 2;
|
||||
// blocks of the first matrix
|
||||
int[][] a11 = getQuadrant(m, n, a, true, true);
|
||||
int[][] a12 = getQuadrant(m, n, a, true, false);
|
||||
int[][] a21 = getQuadrant(m, n, a, false, true);
|
||||
int[][] a22 = getQuadrant(m, n, a, false, false);
|
||||
// blocks of the second matrix
|
||||
int[][] b11 = getQuadrant(m, n, b, true, true);
|
||||
int[][] b12 = getQuadrant(m, n, b, true, false);
|
||||
int[][] b21 = getQuadrant(m, n, b, false, true);
|
||||
int[][] b22 = getQuadrant(m, n, b, false, false);
|
||||
// summation of blocks
|
||||
int[][] s1 = sumMatrices(m, a21, a22, true);
|
||||
int[][] s2 = sumMatrices(m, s1, a11, false);
|
||||
int[][] s3 = sumMatrices(m, a11, a21, false);
|
||||
int[][] s4 = sumMatrices(m, a12, s2, false);
|
||||
int[][] s5 = sumMatrices(m, b12, b11, false);
|
||||
int[][] s6 = sumMatrices(m, b22, s5, false);
|
||||
int[][] s7 = sumMatrices(m, b22, b12, false);
|
||||
int[][] s8 = sumMatrices(m, s6, b21, false);
|
||||
int[][][] p = new int[7][][];
|
||||
// multiplication of blocks in parallel streams
|
||||
IntStream.range(0, 7).parallel().forEach(i -> {
|
||||
switch (i) { // recursive calls
|
||||
case 0: p[i] = multiplyMatrices(m, brd, s2, s6); break;
|
||||
case 1: p[i] = multiplyMatrices(m, brd, a11, b11); break;
|
||||
case 2: p[i] = multiplyMatrices(m, brd, a12, b21); break;
|
||||
case 3: p[i] = multiplyMatrices(m, brd, s3, s7); break;
|
||||
case 4: p[i] = multiplyMatrices(m, brd, s1, s5); break;
|
||||
case 5: p[i] = multiplyMatrices(m, brd, s4, b22); break;
|
||||
case 6: p[i] = multiplyMatrices(m, brd, a22, s8); break;
|
||||
}
|
||||
});
|
||||
// summation of blocks
|
||||
int[][] t1 = sumMatrices(m, p[0], p[1], true);
|
||||
int[][] t2 = sumMatrices(m, t1, p[3], true);
|
||||
// blocks of the resulting matrix
|
||||
int[][] c11 = sumMatrices(m, p[1], p[2], true);
|
||||
int[][] c12 = sumMatrices(m, t1, sumMatrices(m, p[4], p[5], true), true);
|
||||
int[][] c21 = sumMatrices(m, t2, p[6], false);
|
||||
int[][] c22 = sumMatrices(m, t2, p[4], true);
|
||||
// assemble a matrix from blocks,
|
||||
// remove zero rows and columns, if added
|
||||
return putQuadrants(m, n, c11, c12, c21, c22);
|
||||
}
|
||||
```
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
// helper method for matrix summation
|
||||
private static int[][] sumMatrices(int n, int[][] a, int[][] b, boolean sign) {
|
||||
int[][] c = new int[n][n];
|
||||
for (int i = 0; i < n; i++)
|
||||
for (int j = 0; j < n; j++)
|
||||
c[i][j] = sign ? a[i][j] + b[i][j] : a[i][j] - b[i][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
```java
|
||||
// helper method, gets a block of a matrix
|
||||
private static int[][] getQuadrant(int m, int n, int[][] x,
|
||||
boolean first, boolean second) {
|
||||
int[][] q = new int[m][m];
|
||||
if (first) for (int i = 0; i < m; i++)
|
||||
if (second) System.arraycopy(x[i], 0, q[i], 0, m); // x11
|
||||
else System.arraycopy(x[i], m, q[i], 0, n - m); // x12
|
||||
else for (int i = m; i < n; i++)
|
||||
if (second) System.arraycopy(x[i], 0, q[i - m], 0, m); // x21
|
||||
else System.arraycopy(x[i], m, q[i - m], 0, n - m); // x22
|
||||
return q;
|
||||
}
|
||||
```
|
||||
```java
|
||||
// helper method, assembles a matrix from blocks
|
||||
private static int[][] putQuadrants(int m, int n,
|
||||
int[][] x11, int[][] x12,
|
||||
int[][] x21, int[][] x22) {
|
||||
int[][] x = new int[n][n];
|
||||
for (int i = 0; i < n; i++)
|
||||
if (i < m) {
|
||||
System.arraycopy(x11[i], 0, x[i], 0, m);
|
||||
System.arraycopy(x12[i], 0, x[i], m, n - m);
|
||||
} else {
|
||||
System.arraycopy(x21[i - m], 0, x[i], 0, m);
|
||||
System.arraycopy(x22[i - m], 0, x[i], m, n - m);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Helper methods" content=collapsed_md -%}
|
||||
|
||||
## Nested loops {#nested-loops}
|
||||
|
||||
To supplement the previous algorithm and to compare with it, we take the *optimized* variant of nested
|
||||
loops, that uses cache of the execution environment better than others — processing of the rows of the
|
||||
resulting matrix occurs independently of each other in parallel streams. For small matrices, we use this
|
||||
algorithm — large matrices we partition into small blocks and use the same algorithm.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param n matrix size
|
||||
* @param a first matrix 'n×n'
|
||||
* @param b second matrix 'n×n'
|
||||
* @return resulting matrix 'n×n'
|
||||
*/
|
||||
public static int[][] simpleMultiplication(int n, int[][] a, int[][] b) {
|
||||
// the resulting matrix
|
||||
int[][] c = new int[n][n];
|
||||
// bypass the rows of matrix 'a' in parallel mode
|
||||
IntStream.range(0, n).parallel().forEach(i -> {
|
||||
// bypass the indexes of the common side of two matrices:
|
||||
// the columns of matrix 'a' and the rows of matrix 'b'
|
||||
for (int k = 0; k < n; k++)
|
||||
// bypass the indexes of the columns of matrix 'b'
|
||||
for (int j = 0; j < n; j++)
|
||||
// the sum of the products of the elements of the i-th
|
||||
// row of matrix 'a' and the j-th column of matrix 'b'
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
});
|
||||
return c;
|
||||
}
|
||||
```
|
||||
|
||||
## Testing {#testing}
|
||||
|
||||
To check, we take two square matrices `A=[1000×1000]` and `B=[1000×1000]`, filled with random numbers.
|
||||
Take the minimum block size `[200×200]` elements. First, we compare the correctness of the implementation
|
||||
of the two algorithms — matrix products must match. Then we execute each method 10 times and calculate
|
||||
the average execution time.
|
||||
|
||||
```java
|
||||
// start the program and output the result
|
||||
public static void main(String[] args) {
|
||||
// incoming data
|
||||
int n = 1000, brd = 200, steps = 10;
|
||||
int[][] a = randomMatrix(n, n), b = randomMatrix(n, n);
|
||||
// matrix products
|
||||
int[][] c1 = multiplyMatrices(n, brd, a, b);
|
||||
int[][] c2 = simpleMultiplication(n, a, b);
|
||||
// check the correctness of the results
|
||||
System.out.println("The results match: " + Arrays.deepEquals(c1, c2));
|
||||
// measure the execution time of two methods
|
||||
benchmark("Hybrid algorithm", steps, () -> {
|
||||
int[][] c = multiplyMatrices(n, brd, a, b);
|
||||
if (!Arrays.deepEquals(c, c1)) System.out.print("error");
|
||||
});
|
||||
benchmark("Nested loops ", steps, () -> {
|
||||
int[][] c = simpleMultiplication(n, a, b);
|
||||
if (!Arrays.deepEquals(c, c2)) System.out.print("error");
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
// helper method, returns a matrix of the specified size
|
||||
private static int[][] randomMatrix(int row, int col) {
|
||||
int[][] matrix = new int[row][col];
|
||||
for (int i = 0; i < row; i++)
|
||||
for (int j = 0; j < col; j++)
|
||||
matrix[i][j] = (int) (Math.random() * row * col);
|
||||
return matrix;
|
||||
}
|
||||
```
|
||||
```java
|
||||
// helper method for measuring the execution time of the passed code
|
||||
private static void benchmark(String title, int steps, Runnable runnable) {
|
||||
long time, avg = 0;
|
||||
System.out.print(title);
|
||||
for (int i = 0; i < steps; i++) {
|
||||
time = System.currentTimeMillis();
|
||||
runnable.run();
|
||||
time = System.currentTimeMillis() - time;
|
||||
// execution time of one step
|
||||
System.out.print(" | " + time);
|
||||
avg += time;
|
||||
}
|
||||
// average execution time
|
||||
System.out.println(" || " + (avg / steps));
|
||||
}
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Helper methods" content=collapsed_md -%}
|
||||
|
||||
Output depends on the execution environment, time in milliseconds:
|
||||
|
||||
```
|
||||
The results match: true
|
||||
Hybrid algorithm | 196 | 177 | 156 | 205 | 154 | 165 | 133 | 118 | 132 | 134 || 157
|
||||
Nested loops | 165 | 164 | 168 | 167 | 168 | 168 | 170 | 179 | 173 | 168 || 169
|
||||
```
|
||||
|
||||
## Comparing algorithms {#comparing-algorithms}
|
||||
|
||||
On an eight-core Linux x64 computer, execute the above test 100 times instead of 10. Take the minimum
|
||||
block size `[brd=200]` elements. Change only `n` — sizes of both matrices `A=[n×n]` and `B=[n×n]`. Get
|
||||
a summary table of results. Time in milliseconds.
|
||||
|
||||
```
|
||||
n | 900 | 1000 | 1100 | 1200 | 1300 | 1400 | 1500 | 1600 | 1700 |
|
||||
-----------------|-----|------|------|------|------|------|------|------|------|
|
||||
Hybrid algorithm | 96 | 125 | 169 | 204 | 260 | 313 | 384 | 482 | 581 |
|
||||
Nested loops | 119 | 162 | 235 | 281 | 361 | 497 | 651 | 793 | 971 |
|
||||
```
|
||||
|
||||
Results: the benefit of the Strassen algorithm becomes more evident on large matrices, when the
|
||||
size of the matrix itself is several times larger than the size of the minimal block, and depends
|
||||
on the execution environment.
|
||||
|
||||
All the methods described above, including collapsed blocks, can be placed in one class.
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.IntStream;
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Required imports" content=collapsed_md -%}
|
76
jekyll_site/en/index.md
Normal file
76
jekyll_site/en/index.md
Normal file
|
@ -0,0 +1,76 @@
|
|||
---
|
||||
title: Code with comments
|
||||
description: Notes about programming with code snippets and comments. Problem solutions and solution descriptions.
|
||||
sections: [Problem solutions and solution descriptions]
|
||||
tags: [java,algorithms,implementation,arrays,multidimensional arrays,matrices,loops,streams]
|
||||
canonical_url: /en/
|
||||
url_translated: /ru/
|
||||
title_translated: Код с комментариями
|
||||
lang: en
|
||||
---
|
||||
|
||||
{%- assign articles = "" | split: "" %}
|
||||
{%- assign articles = articles | push: "Winograd — Strassen algorithm" %}
|
||||
{%- capture article_brief %}
|
||||
Consider a modification of Strassen's algorithm for square matrix multiplication with *fewer* number
|
||||
of summations between blocks than in the ordinary algorithm — 15 instead of 18 and the same number
|
||||
of multiplications as in the ordinary algorithm — 7. We will use Java Streams.
|
||||
|
||||
Recursive partitioning of matrices into blocks during multiplication makes sense up to a certain
|
||||
limit, and then it loses its sense, since the Strassen's algorithm does not use cache of the execution
|
||||
environment. Therefore, for small blocks we will use a parallel version of nested loops, and for large
|
||||
blocks we will perform recursive partitioning in parallel.
|
||||
|
||||
We determine the boundary between the two algorithms experimentally — we adjust it to the cache of the
|
||||
execution environment. The benefit of Strassen's algorithm becomes more evident on sizable matrices —
|
||||
the difference with the algorithm using nested loops becomes larger and depends on the execution
|
||||
environment. Let's compare the operating time of two algorithms.
|
||||
{%- endcapture %}
|
||||
{%- assign articles = articles | push: article_brief %}
|
||||
{%- assign articles = articles | push: "Matrix multiplication in parallel streams" %}
|
||||
{%- capture article_brief %}
|
||||
Consider an algorithm for multiplying rectangular matrices using Java Streams. Let's take the
|
||||
*optimized version* of the algorithm using three nested loops and replace the outer loop with
|
||||
a stream. Let's compare the operating time of two algorithms.
|
||||
|
||||
We process the rows of the resulting matrix in parallel mode, and populate each row layerwise. Due to
|
||||
the parallel use of cache of the execution environment on multi-core machines, the computation time can
|
||||
be reduced by more than two times. To check, let's take two rectangular matrices {`L×M`} and {`M×N`}.
|
||||
{%- endcapture %}
|
||||
{%- assign articles = articles | push: article_brief %}
|
||||
{%- assign articles = articles | push: "Matrix rotation 180 degrees" %}
|
||||
{%- capture article_brief %}
|
||||
Consider the algorithm for rotating a matrix 180 degrees. Unlike the *transpose* algorithm,
|
||||
here in the resulting matrix the rows and columns are not swapped, but are mirrored.
|
||||
|
||||
Let's write a method in Java to rotate a matrix {`m×n`} 180 degrees. As an example,
|
||||
let's take a rectangular matrix {`4×3`}.
|
||||
{%- endcapture %}
|
||||
{%- assign articles = articles | push: article_brief %}
|
||||
{%- assign articles = articles | push: "Matrix rotation 90 degrees" %}
|
||||
{%- capture article_brief %}
|
||||
Consider the algorithm for rotating a matrix 90 degrees clockwise and anticlockwise. This
|
||||
algorithm is similar to *matrix transpose*, with the difference that here, for each point,
|
||||
one of the coordinates is mirrored.
|
||||
|
||||
Let's write a method in Java to rotate a matrix {`m×n`} 90 degrees. The additional parameter
|
||||
sets the direction of rotation: clockwise or anticlockwise. As an example, let's take a
|
||||
rectangular matrix {`4×3`}.
|
||||
{%- endcapture %}
|
||||
{%- assign articles = articles | push: article_brief %}
|
||||
{%- assign articles = articles | push: "Optimizing matrix multiplication" %}
|
||||
{%- capture article_brief %}
|
||||
Consider an algorithm for multiplying matrices using three nested loops. The complexity of such
|
||||
an algorithm by definition should be `O(n³)`, but there are particularities related to the execution
|
||||
environment — the speed of the algorithm depends on the sequence in which the loops are executed.
|
||||
|
||||
Let's compare different permutations of nested loops and the execution time of the algorithms.
|
||||
Let's take two matrices: {`L×M`} and {`M×N`} → three loops → six permutations:
|
||||
`LMN`, `LNM`, `MLN`, `MNL`, `NLM`, `NML`.
|
||||
|
||||
The algorithms that work faster than others are those that write data to the resulting matrix
|
||||
*row-wise in layers*: `LMN` and `MLN`, — the percentage difference to other algorithms is substantial
|
||||
and depends on the execution environment.
|
||||
{%- endcapture %}
|
||||
{%- assign articles = articles | push: article_brief %}
|
||||
{%- include main_page.html articles = articles -%}
|
50
jekyll_site/img/block-matrices.svg
Normal file
50
jekyll_site/img/block-matrices.svg
Normal file
|
@ -0,0 +1,50 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="220.625102pt" height="32.408907pt" viewBox="-.239051 -.228392 220.625102 32.408907">
|
||||
<defs>
|
||||
<path id="g2-49" d="M2.502615-5.076961C2.502615-5.292154 2.486675-5.300125 2.271482-5.300125C1.944707-4.98132 1.522291-4.790037 .765131-4.790037V-4.527024C.980324-4.527024 1.41071-4.527024 1.872976-4.742217V-.653549C1.872976-.358655 1.849066-.263014 1.091905-.263014H.812951V0C1.139726-.02391 1.825156-.02391 2.183811-.02391S3.235866-.02391 3.56264 0V-.263014H3.283686C2.526526-.263014 2.502615-.358655 2.502615-.653549V-5.076961Z"/>
|
||||
<path id="g2-50" d="M2.247572-1.625903C2.375093-1.745455 2.709838-2.008468 2.83736-2.12005C3.331507-2.574346 3.801743-3.012702 3.801743-3.737983C3.801743-4.686426 3.004732-5.300125 2.008468-5.300125C1.052055-5.300125 .422416-4.574844 .422416-3.865504C.422416-3.474969 .73325-3.419178 .844832-3.419178C1.012204-3.419178 1.259278-3.53873 1.259278-3.841594C1.259278-4.25604 .860772-4.25604 .765131-4.25604C.996264-4.837858 1.530262-5.037111 1.920797-5.037111C2.662017-5.037111 3.044583-4.407472 3.044583-3.737983C3.044583-2.909091 2.462765-2.303362 1.522291-1.338979L.518057-.302864C.422416-.215193 .422416-.199253 .422416 0H3.57061L3.801743-1.42665H3.55467C3.53076-1.267248 3.466999-.868742 3.371357-.71731C3.323537-.653549 2.717808-.653549 2.590286-.653549H1.171606L2.247572-1.625903Z"/>
|
||||
<path id="g0-18" d="M8.368618 28.08269C8.368618 28.034869 8.344707 28.010959 8.320797 27.975093C7.878456 27.532752 7.07746 26.731756 6.276463 25.440598C4.351681 22.356164 3.478954 18.470735 3.478954 13.867995C3.478954 10.652055 3.90934 6.503611 5.881943 2.940971C6.826401 1.243337 7.806725 .263014 8.332752-.263014C8.368618-.298879 8.368618-.32279 8.368618-.358655C8.368618-.478207 8.284932-.478207 8.117559-.478207S7.926276-.478207 7.746949-.298879C3.741968 3.347447 2.486675 8.822914 2.486675 13.85604C2.486675 18.554421 3.56264 23.288667 6.599253 26.863263C6.838356 27.138232 7.292653 27.628394 7.782814 28.05878C7.926276 28.202242 7.950187 28.202242 8.117559 28.202242S8.368618 28.202242 8.368618 28.08269Z"/>
|
||||
<path id="g0-19" d="M6.300374 13.867995C6.300374 9.169614 5.224408 4.435367 2.187796 .860772C1.948692 .585803 1.494396 .095641 1.004234-.334745C.860772-.478207 .836862-.478207 .669489-.478207C.526027-.478207 .418431-.478207 .418431-.358655C.418431-.310834 .466252-.263014 .490162-.239103C.908593 .191283 1.709589 .992279 2.510585 2.283437C4.435367 5.36787 5.308095 9.2533 5.308095 13.85604C5.308095 17.07198 4.877709 21.220423 2.905106 24.783064C1.960648 26.480697 .968369 27.472976 .466252 27.975093C.442341 28.010959 .418431 28.046824 .418431 28.08269C.418431 28.202242 .526027 28.202242 .669489 28.202242C.836862 28.202242 .860772 28.202242 1.0401 28.022914C5.045081 24.376588 6.300374 18.901121 6.300374 13.867995Z"/>
|
||||
<path id="g3-61" d="M8.069738-3.873474C8.237111-3.873474 8.452304-3.873474 8.452304-4.088667C8.452304-4.315816 8.249066-4.315816 8.069738-4.315816H1.028144C.860772-4.315816 .645579-4.315816 .645579-4.100623C.645579-3.873474 .848817-3.873474 1.028144-3.873474H8.069738ZM8.069738-1.649813C8.237111-1.649813 8.452304-1.649813 8.452304-1.865006C8.452304-2.092154 8.249066-2.092154 8.069738-2.092154H1.028144C.860772-2.092154 .645579-2.092154 .645579-1.876961C.645579-1.649813 .848817-1.649813 1.028144-1.649813H8.069738Z"/>
|
||||
<path id="g1-58" d="M2.199751-.573848C2.199751-.920548 1.912827-1.159651 1.625903-1.159651C1.279203-1.159651 1.0401-.872727 1.0401-.585803C1.0401-.239103 1.327024 0 1.613948 0C1.960648 0 2.199751-.286924 2.199751-.573848Z"/>
|
||||
<path id="g1-59" d="M2.331258 .047821C2.331258-.645579 2.10411-1.159651 1.613948-1.159651C1.231382-1.159651 1.0401-.848817 1.0401-.585803S1.219427 0 1.625903 0C1.78132 0 1.912827-.047821 2.020423-.155417C2.044334-.179328 2.056289-.179328 2.068244-.179328C2.092154-.179328 2.092154-.011955 2.092154 .047821C2.092154 .442341 2.020423 1.219427 1.327024 1.996513C1.195517 2.139975 1.195517 2.163885 1.195517 2.187796C1.195517 2.247572 1.255293 2.307347 1.315068 2.307347C1.41071 2.307347 2.331258 1.422665 2.331258 .047821Z"/>
|
||||
<path id="g1-65" d="M2.032379-1.327024C1.613948-.621669 1.207472-.382565 .633624-.3467C.502117-.334745 .406476-.334745 .406476-.119552C.406476-.047821 .466252 0 .549938 0C.765131 0 1.303113-.02391 1.518306-.02391C1.865006-.02391 2.247572 0 2.582316 0C2.654047 0 2.797509 0 2.797509-.227148C2.797509-.334745 2.701868-.3467 2.630137-.3467C2.355168-.37061 2.12802-.466252 2.12802-.753176C2.12802-.920548 2.199751-1.052055 2.355168-1.315068L3.263761-2.82142H6.312329C6.324284-2.713823 6.324284-2.618182 6.336239-2.510585C6.372105-2.199751 6.515567-.956413 6.515567-.729265C6.515567-.37061 5.905853-.3467 5.71457-.3467C5.583064-.3467 5.451557-.3467 5.451557-.131507C5.451557 0 5.559153 0 5.630884 0C5.834122 0 6.073225-.02391 6.276463-.02391H6.957908C7.687173-.02391 8.2132 0 8.225156 0C8.308842 0 8.440349 0 8.440349-.227148C8.440349-.3467 8.332752-.3467 8.153425-.3467C7.49589-.3467 7.483935-.454296 7.44807-.812951L6.718804-8.272976C6.694894-8.51208 6.647073-8.53599 6.515567-8.53599C6.396015-8.53599 6.324284-8.51208 6.216687-8.332752L2.032379-1.327024ZM3.466999-3.16812L5.869988-7.185056L6.276463-3.16812H3.466999Z"/>
|
||||
<path id="g1-66" d="M4.375592-7.352428C4.483188-7.79477 4.531009-7.81868 4.99726-7.81868H6.551432C7.902366-7.81868 7.902366-6.670984 7.902366-6.563387C7.902366-5.595019 6.933998-4.363636 5.355915-4.363636H3.634371L4.375592-7.352428ZM6.396015-4.267995C7.699128-4.507098 8.88269-5.415691 8.88269-6.515567C8.88269-7.44807 8.057783-8.16538 6.706849-8.16538H2.86924C2.642092-8.16538 2.534496-8.16538 2.534496-7.938232C2.534496-7.81868 2.642092-7.81868 2.82142-7.81868C3.550685-7.81868 3.550685-7.723039 3.550685-7.591532C3.550685-7.567621 3.550685-7.49589 3.502864-7.316563L1.888917-.884682C1.78132-.466252 1.75741-.3467 .920548-.3467C.6934-.3467 .573848-.3467 .573848-.131507C.573848 0 .645579 0 .884682 0H4.985305C6.814446 0 8.225156-1.3868 8.225156-2.594271C8.225156-3.574595 7.364384-4.172354 6.396015-4.267995ZM4.698381-.3467H3.084433C2.917061-.3467 2.893151-.3467 2.82142-.358655C2.689913-.37061 2.677958-.394521 2.677958-.490162C2.677958-.573848 2.701868-.645579 2.725778-.753176L3.56264-4.124533H5.810212C7.220922-4.124533 7.220922-2.809465 7.220922-2.713823C7.220922-1.566127 6.180822-.3467 4.698381-.3467Z"/>
|
||||
</defs>
|
||||
<g fill="#222" stroke="#222" style="fill: var(--color, #222); stroke: var(--color, #222);" stroke-width="0.3" transform="matrix(1.13 0 0 1.13 -63.986043 -61.132812)">
|
||||
<use x="56.413267" y="71.232862" xlink:href="#g1-65"/>
|
||||
<use x="68.509443" y="71.232862" xlink:href="#g3-61"/>
|
||||
<use x="80.934924" y="54.375924" xlink:href="#g0-18"/>
|
||||
<use x="89.735297" y="63.910285" xlink:href="#g1-65"/>
|
||||
<use x="98.510643" y="65.703548" xlink:href="#g2-49"/>
|
||||
<use x="102.744826" y="65.703548" xlink:href="#g2-49"/>
|
||||
<use x="117.43977" y="63.910285" xlink:href="#g1-65"/>
|
||||
<use x="126.215116" y="65.703548" xlink:href="#g2-49"/>
|
||||
<use x="130.449299" y="65.703548" xlink:href="#g2-50"/>
|
||||
<use x="89.735297" y="78.356097" xlink:href="#g1-65"/>
|
||||
<use x="98.510643" y="80.149361" xlink:href="#g2-50"/>
|
||||
<use x="102.744826" y="80.149361" xlink:href="#g2-49"/>
|
||||
<use x="117.43977" y="78.356097" xlink:href="#g1-65"/>
|
||||
<use x="126.215116" y="80.149361" xlink:href="#g2-50"/>
|
||||
<use x="130.449299" y="80.149361" xlink:href="#g2-50"/>
|
||||
<use x="135.181603" y="54.375924" xlink:href="#g0-19"/>
|
||||
<use x="143.981975" y="71.232862" xlink:href="#g1-59"/>
|
||||
<use x="160.932115" y="71.232862" xlink:href="#g1-66"/>
|
||||
<use x="173.749205" y="71.232862" xlink:href="#g3-61"/>
|
||||
<use x="186.174686" y="54.375924" xlink:href="#g0-18"/>
|
||||
<use x="194.975058" y="63.910285" xlink:href="#g1-66"/>
|
||||
<use x="203.868021" y="65.703548" xlink:href="#g2-49"/>
|
||||
<use x="208.102204" y="65.703548" xlink:href="#g2-49"/>
|
||||
<use x="222.797148" y="63.910285" xlink:href="#g1-66"/>
|
||||
<use x="231.690111" y="65.703548" xlink:href="#g2-49"/>
|
||||
<use x="235.924293" y="65.703548" xlink:href="#g2-50"/>
|
||||
<use x="194.975058" y="78.356097" xlink:href="#g1-66"/>
|
||||
<use x="203.868021" y="80.149361" xlink:href="#g2-50"/>
|
||||
<use x="208.102204" y="80.149361" xlink:href="#g2-49"/>
|
||||
<use x="222.797148" y="78.356097" xlink:href="#g1-66"/>
|
||||
<use x="231.690111" y="80.149361" xlink:href="#g2-50"/>
|
||||
<use x="235.924293" y="80.149361" xlink:href="#g2-50"/>
|
||||
<use x="240.656597" y="54.375924" xlink:href="#g0-19"/>
|
||||
<use x="249.45697" y="71.232862" xlink:href="#g1-58"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.2 KiB |
84
jekyll_site/img/products.svg
Normal file
84
jekyll_site/img/products.svg
Normal file
|
@ -0,0 +1,84 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="74.42272pt" height="126.555483pt" viewBox="-.299738 -.250564 74.42272 126.555483">
|
||||
<defs>
|
||||
<path id="g2-59" d="M2.199751-4.578829C2.199751-4.901619 1.924782-5.152677 1.625903-5.152677C1.279203-5.152677 1.0401-4.877709 1.0401-4.578829C1.0401-4.220174 1.338979-3.993026 1.613948-3.993026C1.936737-3.993026 2.199751-4.244085 2.199751-4.578829ZM1.996513-.119552C1.996513 .298879 1.996513 1.147696 1.267248 2.044334C1.195517 2.139975 1.195517 2.163885 1.195517 2.187796C1.195517 2.247572 1.255293 2.307347 1.315068 2.307347C1.398755 2.307347 2.235616 1.422665 2.235616 .02391C2.235616-.418431 2.199751-1.159651 1.613948-1.159651C1.267248-1.159651 1.0401-.896638 1.0401-.585803C1.0401-.263014 1.267248 0 1.625903 0C1.853051 0 1.936737-.071731 1.996513-.119552Z"/>
|
||||
<path id="g2-61" d="M8.069738-3.873474C8.237111-3.873474 8.452304-3.873474 8.452304-4.088667C8.452304-4.315816 8.249066-4.315816 8.069738-4.315816H1.028144C.860772-4.315816 .645579-4.315816 .645579-4.100623C.645579-3.873474 .848817-3.873474 1.028144-3.873474H8.069738ZM8.069738-1.649813C8.237111-1.649813 8.452304-1.649813 8.452304-1.865006C8.452304-2.092154 8.249066-2.092154 8.069738-2.092154H1.028144C.860772-2.092154 .645579-2.092154 .645579-1.876961C.645579-1.649813 .848817-1.649813 1.028144-1.649813H8.069738Z"/>
|
||||
<path id="g1-49" d="M2.502615-5.076961C2.502615-5.292154 2.486675-5.300125 2.271482-5.300125C1.944707-4.98132 1.522291-4.790037 .765131-4.790037V-4.527024C.980324-4.527024 1.41071-4.527024 1.872976-4.742217V-.653549C1.872976-.358655 1.849066-.263014 1.091905-.263014H.812951V0C1.139726-.02391 1.825156-.02391 2.183811-.02391S3.235866-.02391 3.56264 0V-.263014H3.283686C2.526526-.263014 2.502615-.358655 2.502615-.653549V-5.076961Z"/>
|
||||
<path id="g1-50" d="M2.247572-1.625903C2.375093-1.745455 2.709838-2.008468 2.83736-2.12005C3.331507-2.574346 3.801743-3.012702 3.801743-3.737983C3.801743-4.686426 3.004732-5.300125 2.008468-5.300125C1.052055-5.300125 .422416-4.574844 .422416-3.865504C.422416-3.474969 .73325-3.419178 .844832-3.419178C1.012204-3.419178 1.259278-3.53873 1.259278-3.841594C1.259278-4.25604 .860772-4.25604 .765131-4.25604C.996264-4.837858 1.530262-5.037111 1.920797-5.037111C2.662017-5.037111 3.044583-4.407472 3.044583-3.737983C3.044583-2.909091 2.462765-2.303362 1.522291-1.338979L.518057-.302864C.422416-.215193 .422416-.199253 .422416 0H3.57061L3.801743-1.42665H3.55467C3.53076-1.267248 3.466999-.868742 3.371357-.71731C3.323537-.653549 2.717808-.653549 2.590286-.653549H1.171606L2.247572-1.625903Z"/>
|
||||
<path id="g1-51" d="M2.016438-2.662017C2.646077-2.662017 3.044583-2.199751 3.044583-1.362889C3.044583-.366625 2.478705-.071731 2.056289-.071731C1.617933-.071731 1.020174-.231133 .74122-.653549C1.028144-.653549 1.227397-.836862 1.227397-1.099875C1.227397-1.354919 1.044085-1.538232 .789041-1.538232C.573848-1.538232 .350685-1.40274 .350685-1.083935C.350685-.326775 1.163636 .167372 2.072229 .167372C3.132254 .167372 3.873474-.565878 3.873474-1.362889C3.873474-2.024408 3.347447-2.630137 2.534496-2.805479C3.164134-3.028643 3.634371-3.57061 3.634371-4.208219S2.917061-5.300125 2.088169-5.300125C1.235367-5.300125 .589788-4.837858 .589788-4.23213C.589788-3.937235 .789041-3.809714 .996264-3.809714C1.243337-3.809714 1.40274-3.985056 1.40274-4.216189C1.40274-4.511083 1.147696-4.622665 .972354-4.630635C1.307098-5.068991 1.920797-5.092902 2.064259-5.092902C2.271482-5.092902 2.87721-5.029141 2.87721-4.208219C2.87721-3.650311 2.646077-3.315567 2.534496-3.188045C2.295392-2.940971 2.11208-2.925031 1.625903-2.893151C1.474471-2.885181 1.41071-2.87721 1.41071-2.773599C1.41071-2.662017 1.482441-2.662017 1.617933-2.662017H2.016438Z"/>
|
||||
<path id="g1-52" d="M3.140224-5.156663C3.140224-5.316065 3.140224-5.379826 2.972852-5.379826C2.86924-5.379826 2.86127-5.371856 2.781569-5.260274L.239103-1.570112V-1.307098H2.486675V-.645579C2.486675-.350685 2.462765-.263014 1.849066-.263014H1.665753V0C2.343213-.02391 2.359153-.02391 2.81345-.02391S3.283686-.02391 3.961146 0V-.263014H3.777833C3.164134-.263014 3.140224-.350685 3.140224-.645579V-1.307098H3.985056V-1.570112H3.140224V-5.156663ZM2.542466-4.511083V-1.570112H.518057L2.542466-4.511083Z"/>
|
||||
<path id="g1-53" d="M1.115816-4.479203C1.219427-4.447323 1.538232-4.367621 1.872976-4.367621C2.86924-4.367621 3.474969-5.068991 3.474969-5.188543C3.474969-5.276214 3.419178-5.300125 3.379328-5.300125C3.363387-5.300125 3.347447-5.300125 3.275716-5.260274C2.964882-5.140722 2.598257-5.045081 2.16787-5.045081C1.697634-5.045081 1.307098-5.164633 1.060025-5.260274C.980324-5.300125 .964384-5.300125 .956413-5.300125C.852802-5.300125 .852802-5.212453 .852802-5.068991V-2.733748C.852802-2.590286 .852802-2.494645 .980324-2.494645C1.044085-2.494645 1.067995-2.526526 1.107846-2.590286C1.203487-2.709838 1.506351-3.116314 2.183811-3.116314C2.630137-3.116314 2.84533-2.749689 2.917061-2.598257C3.052553-2.311333 3.068493-1.944707 3.068493-1.633873C3.068493-1.338979 3.060523-.908593 2.83736-.557908C2.685928-.318804 2.367123-.071731 1.944707-.071731C1.42665-.071731 .916563-.398506 .73325-.916563C.757161-.908593 .804981-.908593 .812951-.908593C1.036115-.908593 1.211457-1.052055 1.211457-1.299128C1.211457-1.594022 .980324-1.697634 .820922-1.697634C.67746-1.697634 .422416-1.617933 .422416-1.275218C.422416-.557908 1.044085 .167372 1.960648 .167372C2.956912 .167372 3.801743-.605729 3.801743-1.594022C3.801743-2.518555 3.132254-3.339477 2.191781-3.339477C1.793275-3.339477 1.41868-3.211955 1.115816-2.940971V-4.479203Z"/>
|
||||
<path id="g1-54" d="M1.099875-2.638107C1.099875-3.299626 1.155666-3.881445 1.44259-4.367621C1.681694-4.766127 2.088169-5.092902 2.590286-5.092902C2.749689-5.092902 3.116314-5.068991 3.299626-4.790037C2.940971-4.774097 2.909091-4.503113 2.909091-4.415442C2.909091-4.176339 3.092403-4.040847 3.283686-4.040847C3.427148-4.040847 3.658281-4.128518 3.658281-4.431382C3.658281-4.909589 3.299626-5.300125 2.582316-5.300125C1.474471-5.300125 .350685-4.24807 .350685-2.526526C.350685-.366625 1.354919 .167372 2.12802 .167372C2.510585 .167372 2.925031 .063761 3.283686-.278954C3.602491-.589788 3.873474-.924533 3.873474-1.617933C3.873474-2.662017 3.084433-3.395268 2.199751-3.395268C1.625903-3.395268 1.283188-3.028643 1.099875-2.638107ZM2.12802-.071731C1.705604-.071731 1.44259-.366625 1.323039-.589788C1.139726-.948443 1.123786-1.490411 1.123786-1.793275C1.123786-2.582316 1.554172-3.172105 2.16787-3.172105C2.566376-3.172105 2.805479-2.964882 2.956912-2.685928C3.124284-2.391034 3.124284-2.032379 3.124284-1.625903S3.124284-.868742 2.964882-.581818C2.757659-.215193 2.478705-.071731 2.12802-.071731Z"/>
|
||||
<path id="g1-55" d="M4.032877-4.853798C4.104608-4.941469 4.104608-4.95741 4.104608-5.132752H2.080199C1.880946-5.132752 1.633873-5.140722 1.43462-5.156663C1.020174-5.188543 1.012204-5.260274 .988294-5.387796H.74122L.470237-3.706102H.71731C.73325-3.825654 .820922-4.375592 .932503-4.439352C1.020174-4.479203 1.617933-4.479203 1.737484-4.479203H3.427148L2.606227-3.379328C1.697634-2.16787 1.506351-.908593 1.506351-.278954C1.506351-.199253 1.506351 .167372 1.880946 .167372S2.255542-.191283 2.255542-.286924V-.669489C2.255542-1.817186 2.446824-2.757659 2.83736-3.275716L4.032877-4.853798Z"/>
|
||||
<path id="g1-56" d="M2.646077-2.87721C3.092403-3.092403 3.634371-3.490909 3.634371-4.112578C3.634371-4.869738 2.86127-5.300125 2.12005-5.300125C1.275218-5.300125 .589788-4.718306 .589788-3.969116C.589788-3.674222 .6934-3.403238 .892653-3.172105C1.028144-3.004732 1.060025-2.988792 1.554172-2.677958C.565878-2.239601 .350685-1.657783 .350685-1.211457C.350685-.334745 1.235367 .167372 2.10411 .167372C3.084433 .167372 3.873474-.494147 3.873474-1.338979C3.873474-1.841096 3.602491-2.175841 3.474969-2.311333C3.339477-2.438854 3.331507-2.446824 2.646077-2.87721ZM1.41071-3.626401C1.179577-3.761893 .988294-3.993026 .988294-4.27198C.988294-4.774097 1.538232-5.092902 2.10411-5.092902C2.725778-5.092902 3.235866-4.670486 3.235866-4.112578C3.235866-3.650311 2.87721-3.259776 2.406974-3.028643L1.41071-3.626401ZM1.801245-2.534496C1.833126-2.518555 2.741719-1.960648 2.87721-1.872976C3.004732-1.801245 3.419178-1.546202 3.419178-1.067995C3.419178-.454296 2.773599-.071731 2.12005-.071731C1.41071-.071731 .804981-.557908 .804981-1.211457C.804981-1.809215 1.251308-2.279452 1.801245-2.534496Z"/>
|
||||
<path id="g0-58" d="M2.199751-.573848C2.199751-.920548 1.912827-1.159651 1.625903-1.159651C1.279203-1.159651 1.0401-.872727 1.0401-.585803C1.0401-.239103 1.327024 0 1.613948 0C1.960648 0 2.199751-.286924 2.199751-.573848Z"/>
|
||||
<path id="g0-65" d="M2.032379-1.327024C1.613948-.621669 1.207472-.382565 .633624-.3467C.502117-.334745 .406476-.334745 .406476-.119552C.406476-.047821 .466252 0 .549938 0C.765131 0 1.303113-.02391 1.518306-.02391C1.865006-.02391 2.247572 0 2.582316 0C2.654047 0 2.797509 0 2.797509-.227148C2.797509-.334745 2.701868-.3467 2.630137-.3467C2.355168-.37061 2.12802-.466252 2.12802-.753176C2.12802-.920548 2.199751-1.052055 2.355168-1.315068L3.263761-2.82142H6.312329C6.324284-2.713823 6.324284-2.618182 6.336239-2.510585C6.372105-2.199751 6.515567-.956413 6.515567-.729265C6.515567-.37061 5.905853-.3467 5.71457-.3467C5.583064-.3467 5.451557-.3467 5.451557-.131507C5.451557 0 5.559153 0 5.630884 0C5.834122 0 6.073225-.02391 6.276463-.02391H6.957908C7.687173-.02391 8.2132 0 8.225156 0C8.308842 0 8.440349 0 8.440349-.227148C8.440349-.3467 8.332752-.3467 8.153425-.3467C7.49589-.3467 7.483935-.454296 7.44807-.812951L6.718804-8.272976C6.694894-8.51208 6.647073-8.53599 6.515567-8.53599C6.396015-8.53599 6.324284-8.51208 6.216687-8.332752L2.032379-1.327024ZM3.466999-3.16812L5.869988-7.185056L6.276463-3.16812H3.466999Z"/>
|
||||
<path id="g0-66" d="M4.375592-7.352428C4.483188-7.79477 4.531009-7.81868 4.99726-7.81868H6.551432C7.902366-7.81868 7.902366-6.670984 7.902366-6.563387C7.902366-5.595019 6.933998-4.363636 5.355915-4.363636H3.634371L4.375592-7.352428ZM6.396015-4.267995C7.699128-4.507098 8.88269-5.415691 8.88269-6.515567C8.88269-7.44807 8.057783-8.16538 6.706849-8.16538H2.86924C2.642092-8.16538 2.534496-8.16538 2.534496-7.938232C2.534496-7.81868 2.642092-7.81868 2.82142-7.81868C3.550685-7.81868 3.550685-7.723039 3.550685-7.591532C3.550685-7.567621 3.550685-7.49589 3.502864-7.316563L1.888917-.884682C1.78132-.466252 1.75741-.3467 .920548-.3467C.6934-.3467 .573848-.3467 .573848-.131507C.573848 0 .645579 0 .884682 0H4.985305C6.814446 0 8.225156-1.3868 8.225156-2.594271C8.225156-3.574595 7.364384-4.172354 6.396015-4.267995ZM4.698381-.3467H3.084433C2.917061-.3467 2.893151-.3467 2.82142-.358655C2.689913-.37061 2.677958-.394521 2.677958-.490162C2.677958-.573848 2.701868-.645579 2.725778-.753176L3.56264-4.124533H5.810212C7.220922-4.124533 7.220922-2.809465 7.220922-2.713823C7.220922-1.566127 6.180822-.3467 4.698381-.3467Z"/>
|
||||
<path id="g0-80" d="M3.53873-3.801743H5.547198C7.197011-3.801743 8.846824-5.021171 8.846824-6.38406C8.846824-7.316563 8.057783-8.16538 6.551432-8.16538H2.857285C2.630137-8.16538 2.52254-8.16538 2.52254-7.938232C2.52254-7.81868 2.630137-7.81868 2.809465-7.81868C3.53873-7.81868 3.53873-7.723039 3.53873-7.591532C3.53873-7.567621 3.53873-7.49589 3.490909-7.316563L1.876961-.884682C1.769365-.466252 1.745455-.3467 .908593-.3467C.681445-.3467 .561893-.3467 .561893-.131507C.561893 0 .669489 0 .74122 0C.968369 0 1.207472-.02391 1.43462-.02391H2.833375C3.060523-.02391 3.311582 0 3.53873 0C3.634371 0 3.765878 0 3.765878-.227148C3.765878-.3467 3.658281-.3467 3.478954-.3467C2.761644-.3467 2.749689-.430386 2.749689-.549938C2.749689-.609714 2.761644-.6934 2.773599-.753176L3.53873-3.801743ZM4.399502-7.352428C4.507098-7.79477 4.554919-7.81868 5.021171-7.81868H6.204732C7.10137-7.81868 7.84259-7.531756 7.84259-6.635118C7.84259-6.324284 7.687173-5.308095 7.137235-4.758157C6.933998-4.542964 6.360149-4.088667 5.272229-4.088667H3.58655L4.399502-7.352428Z"/>
|
||||
<path id="g0-83" d="M7.591532-8.308842C7.591532-8.416438 7.507846-8.416438 7.483935-8.416438C7.436115-8.416438 7.424159-8.404483 7.280697-8.225156C7.208966-8.141469 6.718804-7.519801 6.706849-7.507846C6.312329-8.284932 5.523288-8.416438 5.021171-8.416438C3.502864-8.416438 2.12802-7.029639 2.12802-5.678705C2.12802-4.782067 2.666002-4.25604 3.251806-4.052802C3.383313-4.004981 4.088667-3.813699 4.447323-3.730012C5.057036-3.56264 5.212453-3.514819 5.463512-3.251806C5.511333-3.19203 5.750436-2.917061 5.750436-2.355168C5.750436-1.243337 4.722291-.095641 3.526775-.095641C2.546451-.095641 1.458531-.514072 1.458531-1.853051C1.458531-2.080199 1.506351-2.367123 1.542217-2.486675C1.542217-2.52254 1.554172-2.582316 1.554172-2.606227C1.554172-2.654047 1.530262-2.713823 1.43462-2.713823C1.327024-2.713823 1.315068-2.689913 1.267248-2.486675L.657534-.035866C.657534-.02391 .609714 .131507 .609714 .143462C.609714 .251059 .705355 .251059 .729265 .251059C.777086 .251059 .789041 .239103 .932503 .059776L1.482441-.657534C1.769365-.227148 2.391034 .251059 3.502864 .251059C5.045081 .251059 6.455791-1.243337 6.455791-2.737733C6.455791-3.239851 6.336239-3.682192 5.881943-4.124533C5.630884-4.375592 5.415691-4.435367 4.315816-4.722291C3.514819-4.937484 3.407223-4.97335 3.19203-5.164633C2.988792-5.36787 2.833375-5.654795 2.833375-6.06127C2.833375-7.065504 3.849564-8.093649 4.985305-8.093649C6.156912-8.093649 6.706849-7.376339 6.706849-6.240598C6.706849-5.929763 6.647073-5.606974 6.647073-5.559153C6.647073-5.451557 6.742715-5.451557 6.77858-5.451557C6.886177-5.451557 6.898132-5.487422 6.945953-5.678705L7.591532-8.308842Z"/>
|
||||
</defs>
|
||||
<g fill="#222" stroke="#222" style="fill: var(--color, #222); stroke: var(--color, #222);" stroke-width="0.3" transform="matrix(1.13 0 0 1.13 -80.23 -67.067712)">
|
||||
<use x="70.734745" y="67.546657" xlink:href="#g0-80"/>
|
||||
<use x="78.280013" y="69.33992" xlink:href="#g1-49"/>
|
||||
<use x="86.333157" y="67.546657" xlink:href="#g2-61"/>
|
||||
<use x="98.758638" y="67.546657" xlink:href="#g0-83"/>
|
||||
<use x="105.957978" y="69.33992" xlink:href="#g1-50"/>
|
||||
<use x="110.690292" y="67.546657" xlink:href="#g0-83"/>
|
||||
<use x="117.889632" y="69.33992" xlink:href="#g1-54"/>
|
||||
<use x="122.621947" y="67.546657" xlink:href="#g2-59"/>
|
||||
<use x="70.734745" y="84.483145" xlink:href="#g0-80"/>
|
||||
<use x="78.280013" y="86.276409" xlink:href="#g1-50"/>
|
||||
<use x="86.333157" y="84.483145" xlink:href="#g2-61"/>
|
||||
<use x="98.758638" y="84.483145" xlink:href="#g0-65"/>
|
||||
<use x="107.533984" y="86.276409" xlink:href="#g1-49"/>
|
||||
<use x="111.768167" y="86.276409" xlink:href="#g1-49"/>
|
||||
<use x="116.500482" y="84.483145" xlink:href="#g0-66"/>
|
||||
<use x="125.393445" y="86.276409" xlink:href="#g1-49"/>
|
||||
<use x="129.627628" y="86.276409" xlink:href="#g1-49"/>
|
||||
<use x="134.359943" y="84.483145" xlink:href="#g2-59"/>
|
||||
<use x="70.734745" y="101.419634" xlink:href="#g0-80"/>
|
||||
<use x="78.280013" y="103.212897" xlink:href="#g1-51"/>
|
||||
<use x="86.333157" y="101.419634" xlink:href="#g2-61"/>
|
||||
<use x="98.758638" y="101.419634" xlink:href="#g0-65"/>
|
||||
<use x="107.533984" y="103.212897" xlink:href="#g1-49"/>
|
||||
<use x="111.768167" y="103.212897" xlink:href="#g1-50"/>
|
||||
<use x="116.500482" y="101.419634" xlink:href="#g0-66"/>
|
||||
<use x="125.393445" y="103.212897" xlink:href="#g1-50"/>
|
||||
<use x="129.627628" y="103.212897" xlink:href="#g1-49"/>
|
||||
<use x="134.359943" y="101.419634" xlink:href="#g2-59"/>
|
||||
<use x="70.734745" y="118.356122" xlink:href="#g0-80"/>
|
||||
<use x="78.280013" y="120.149385" xlink:href="#g1-52"/>
|
||||
<use x="86.333157" y="118.356122" xlink:href="#g2-61"/>
|
||||
<use x="98.758638" y="118.356122" xlink:href="#g0-83"/>
|
||||
<use x="105.957978" y="120.149385" xlink:href="#g1-51"/>
|
||||
<use x="110.690292" y="118.356122" xlink:href="#g0-83"/>
|
||||
<use x="117.889632" y="120.149385" xlink:href="#g1-55"/>
|
||||
<use x="122.621947" y="118.356122" xlink:href="#g2-59"/>
|
||||
<use x="70.734745" y="135.29261" xlink:href="#g0-80"/>
|
||||
<use x="78.280013" y="137.085873" xlink:href="#g1-53"/>
|
||||
<use x="86.333157" y="135.29261" xlink:href="#g2-61"/>
|
||||
<use x="98.758638" y="135.29261" xlink:href="#g0-83"/>
|
||||
<use x="105.957978" y="137.085873" xlink:href="#g1-49"/>
|
||||
<use x="110.690292" y="135.29261" xlink:href="#g0-83"/>
|
||||
<use x="117.889632" y="137.085873" xlink:href="#g1-53"/>
|
||||
<use x="122.621947" y="135.29261" xlink:href="#g2-59"/>
|
||||
<use x="70.734745" y="152.229098" xlink:href="#g0-80"/>
|
||||
<use x="78.280013" y="154.022361" xlink:href="#g1-54"/>
|
||||
<use x="86.333157" y="152.229098" xlink:href="#g2-61"/>
|
||||
<use x="98.758638" y="152.229098" xlink:href="#g0-83"/>
|
||||
<use x="105.957978" y="154.022361" xlink:href="#g1-52"/>
|
||||
<use x="110.690292" y="152.229098" xlink:href="#g0-66"/>
|
||||
<use x="119.583255" y="154.022361" xlink:href="#g1-50"/>
|
||||
<use x="123.817438" y="154.022361" xlink:href="#g1-50"/>
|
||||
<use x="128.549753" y="152.229098" xlink:href="#g2-59"/>
|
||||
<use x="70.734745" y="169.165586" xlink:href="#g0-80"/>
|
||||
<use x="78.280013" y="170.958849" xlink:href="#g1-55"/>
|
||||
<use x="86.333157" y="169.165586" xlink:href="#g2-61"/>
|
||||
<use x="98.758638" y="169.165586" xlink:href="#g0-65"/>
|
||||
<use x="107.533984" y="170.958849" xlink:href="#g1-50"/>
|
||||
<use x="111.768167" y="170.958849" xlink:href="#g1-50"/>
|
||||
<use x="116.500482" y="169.165586" xlink:href="#g0-83"/>
|
||||
<use x="123.699822" y="170.958849" xlink:href="#g1-56"/>
|
||||
<use x="128.432137" y="169.165586" xlink:href="#g0-58"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 17 KiB |
125
jekyll_site/img/sums1.svg
Normal file
125
jekyll_site/img/sums1.svg
Normal file
|
@ -0,0 +1,125 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="100.964341pt" height="147.490471pt" viewBox="-.299738 -.248234 100.964341 147.490471">
|
||||
<defs>
|
||||
<path id="g0-0" d="M7.878456-2.749689C8.081694-2.749689 8.296887-2.749689 8.296887-2.988792S8.081694-3.227895 7.878456-3.227895H1.41071C1.207472-3.227895 .992279-3.227895 .992279-2.988792S1.207472-2.749689 1.41071-2.749689H7.878456Z"/>
|
||||
<path id="g3-40" d="M3.88543 2.905106C3.88543 2.86924 3.88543 2.84533 3.682192 2.642092C2.486675 1.43462 1.817186-.537983 1.817186-2.976837C1.817186-5.296139 2.379078-7.292653 3.765878-8.703362C3.88543-8.810959 3.88543-8.834869 3.88543-8.870735C3.88543-8.942466 3.825654-8.966376 3.777833-8.966376C3.622416-8.966376 2.642092-8.105604 2.056289-6.933998C1.446575-5.726526 1.171606-4.447323 1.171606-2.976837C1.171606-1.912827 1.338979-.490162 1.960648 .789041C2.666002 2.223661 3.646326 3.000747 3.777833 3.000747C3.825654 3.000747 3.88543 2.976837 3.88543 2.905106Z"/>
|
||||
<path id="g3-41" d="M3.371357-2.976837C3.371357-3.88543 3.251806-5.36787 2.582316-6.75467C1.876961-8.18929 .896638-8.966376 .765131-8.966376C.71731-8.966376 .657534-8.942466 .657534-8.870735C.657534-8.834869 .657534-8.810959 .860772-8.607721C2.056289-7.400249 2.725778-5.427646 2.725778-2.988792C2.725778-.669489 2.163885 1.327024 .777086 2.737733C.657534 2.84533 .657534 2.86924 .657534 2.905106C.657534 2.976837 .71731 3.000747 .765131 3.000747C.920548 3.000747 1.900872 2.139975 2.486675 .968369C3.096389-.251059 3.371357-1.542217 3.371357-2.976837Z"/>
|
||||
<path id="g3-43" d="M4.770112-2.761644H8.069738C8.237111-2.761644 8.452304-2.761644 8.452304-2.976837C8.452304-3.203985 8.249066-3.203985 8.069738-3.203985H4.770112V-6.503611C4.770112-6.670984 4.770112-6.886177 4.554919-6.886177C4.327771-6.886177 4.327771-6.682939 4.327771-6.503611V-3.203985H1.028144C.860772-3.203985 .645579-3.203985 .645579-2.988792C.645579-2.761644 .848817-2.761644 1.028144-2.761644H4.327771V.537983C4.327771 .705355 4.327771 .920548 4.542964 .920548C4.770112 .920548 4.770112 .71731 4.770112 .537983V-2.761644Z"/>
|
||||
<path id="g3-59" d="M2.199751-4.578829C2.199751-4.901619 1.924782-5.152677 1.625903-5.152677C1.279203-5.152677 1.0401-4.877709 1.0401-4.578829C1.0401-4.220174 1.338979-3.993026 1.613948-3.993026C1.936737-3.993026 2.199751-4.244085 2.199751-4.578829ZM1.996513-.119552C1.996513 .298879 1.996513 1.147696 1.267248 2.044334C1.195517 2.139975 1.195517 2.163885 1.195517 2.187796C1.195517 2.247572 1.255293 2.307347 1.315068 2.307347C1.398755 2.307347 2.235616 1.422665 2.235616 .02391C2.235616-.418431 2.199751-1.159651 1.613948-1.159651C1.267248-1.159651 1.0401-.896638 1.0401-.585803C1.0401-.263014 1.267248 0 1.625903 0C1.853051 0 1.936737-.071731 1.996513-.119552Z"/>
|
||||
<path id="g3-61" d="M8.069738-3.873474C8.237111-3.873474 8.452304-3.873474 8.452304-4.088667C8.452304-4.315816 8.249066-4.315816 8.069738-4.315816H1.028144C.860772-4.315816 .645579-4.315816 .645579-4.100623C.645579-3.873474 .848817-3.873474 1.028144-3.873474H8.069738ZM8.069738-1.649813C8.237111-1.649813 8.452304-1.649813 8.452304-1.865006C8.452304-2.092154 8.249066-2.092154 8.069738-2.092154H1.028144C.860772-2.092154 .645579-2.092154 .645579-1.876961C.645579-1.649813 .848817-1.649813 1.028144-1.649813H8.069738Z"/>
|
||||
<path id="g2-49" d="M2.502615-5.076961C2.502615-5.292154 2.486675-5.300125 2.271482-5.300125C1.944707-4.98132 1.522291-4.790037 .765131-4.790037V-4.527024C.980324-4.527024 1.41071-4.527024 1.872976-4.742217V-.653549C1.872976-.358655 1.849066-.263014 1.091905-.263014H.812951V0C1.139726-.02391 1.825156-.02391 2.183811-.02391S3.235866-.02391 3.56264 0V-.263014H3.283686C2.526526-.263014 2.502615-.358655 2.502615-.653549V-5.076961Z"/>
|
||||
<path id="g2-50" d="M2.247572-1.625903C2.375093-1.745455 2.709838-2.008468 2.83736-2.12005C3.331507-2.574346 3.801743-3.012702 3.801743-3.737983C3.801743-4.686426 3.004732-5.300125 2.008468-5.300125C1.052055-5.300125 .422416-4.574844 .422416-3.865504C.422416-3.474969 .73325-3.419178 .844832-3.419178C1.012204-3.419178 1.259278-3.53873 1.259278-3.841594C1.259278-4.25604 .860772-4.25604 .765131-4.25604C.996264-4.837858 1.530262-5.037111 1.920797-5.037111C2.662017-5.037111 3.044583-4.407472 3.044583-3.737983C3.044583-2.909091 2.462765-2.303362 1.522291-1.338979L.518057-.302864C.422416-.215193 .422416-.199253 .422416 0H3.57061L3.801743-1.42665H3.55467C3.53076-1.267248 3.466999-.868742 3.371357-.71731C3.323537-.653549 2.717808-.653549 2.590286-.653549H1.171606L2.247572-1.625903Z"/>
|
||||
<path id="g2-51" d="M2.016438-2.662017C2.646077-2.662017 3.044583-2.199751 3.044583-1.362889C3.044583-.366625 2.478705-.071731 2.056289-.071731C1.617933-.071731 1.020174-.231133 .74122-.653549C1.028144-.653549 1.227397-.836862 1.227397-1.099875C1.227397-1.354919 1.044085-1.538232 .789041-1.538232C.573848-1.538232 .350685-1.40274 .350685-1.083935C.350685-.326775 1.163636 .167372 2.072229 .167372C3.132254 .167372 3.873474-.565878 3.873474-1.362889C3.873474-2.024408 3.347447-2.630137 2.534496-2.805479C3.164134-3.028643 3.634371-3.57061 3.634371-4.208219S2.917061-5.300125 2.088169-5.300125C1.235367-5.300125 .589788-4.837858 .589788-4.23213C.589788-3.937235 .789041-3.809714 .996264-3.809714C1.243337-3.809714 1.40274-3.985056 1.40274-4.216189C1.40274-4.511083 1.147696-4.622665 .972354-4.630635C1.307098-5.068991 1.920797-5.092902 2.064259-5.092902C2.271482-5.092902 2.87721-5.029141 2.87721-4.208219C2.87721-3.650311 2.646077-3.315567 2.534496-3.188045C2.295392-2.940971 2.11208-2.925031 1.625903-2.893151C1.474471-2.885181 1.41071-2.87721 1.41071-2.773599C1.41071-2.662017 1.482441-2.662017 1.617933-2.662017H2.016438Z"/>
|
||||
<path id="g2-52" d="M3.140224-5.156663C3.140224-5.316065 3.140224-5.379826 2.972852-5.379826C2.86924-5.379826 2.86127-5.371856 2.781569-5.260274L.239103-1.570112V-1.307098H2.486675V-.645579C2.486675-.350685 2.462765-.263014 1.849066-.263014H1.665753V0C2.343213-.02391 2.359153-.02391 2.81345-.02391S3.283686-.02391 3.961146 0V-.263014H3.777833C3.164134-.263014 3.140224-.350685 3.140224-.645579V-1.307098H3.985056V-1.570112H3.140224V-5.156663ZM2.542466-4.511083V-1.570112H.518057L2.542466-4.511083Z"/>
|
||||
<path id="g2-53" d="M1.115816-4.479203C1.219427-4.447323 1.538232-4.367621 1.872976-4.367621C2.86924-4.367621 3.474969-5.068991 3.474969-5.188543C3.474969-5.276214 3.419178-5.300125 3.379328-5.300125C3.363387-5.300125 3.347447-5.300125 3.275716-5.260274C2.964882-5.140722 2.598257-5.045081 2.16787-5.045081C1.697634-5.045081 1.307098-5.164633 1.060025-5.260274C.980324-5.300125 .964384-5.300125 .956413-5.300125C.852802-5.300125 .852802-5.212453 .852802-5.068991V-2.733748C.852802-2.590286 .852802-2.494645 .980324-2.494645C1.044085-2.494645 1.067995-2.526526 1.107846-2.590286C1.203487-2.709838 1.506351-3.116314 2.183811-3.116314C2.630137-3.116314 2.84533-2.749689 2.917061-2.598257C3.052553-2.311333 3.068493-1.944707 3.068493-1.633873C3.068493-1.338979 3.060523-.908593 2.83736-.557908C2.685928-.318804 2.367123-.071731 1.944707-.071731C1.42665-.071731 .916563-.398506 .73325-.916563C.757161-.908593 .804981-.908593 .812951-.908593C1.036115-.908593 1.211457-1.052055 1.211457-1.299128C1.211457-1.594022 .980324-1.697634 .820922-1.697634C.67746-1.697634 .422416-1.617933 .422416-1.275218C.422416-.557908 1.044085 .167372 1.960648 .167372C2.956912 .167372 3.801743-.605729 3.801743-1.594022C3.801743-2.518555 3.132254-3.339477 2.191781-3.339477C1.793275-3.339477 1.41868-3.211955 1.115816-2.940971V-4.479203Z"/>
|
||||
<path id="g2-54" d="M1.099875-2.638107C1.099875-3.299626 1.155666-3.881445 1.44259-4.367621C1.681694-4.766127 2.088169-5.092902 2.590286-5.092902C2.749689-5.092902 3.116314-5.068991 3.299626-4.790037C2.940971-4.774097 2.909091-4.503113 2.909091-4.415442C2.909091-4.176339 3.092403-4.040847 3.283686-4.040847C3.427148-4.040847 3.658281-4.128518 3.658281-4.431382C3.658281-4.909589 3.299626-5.300125 2.582316-5.300125C1.474471-5.300125 .350685-4.24807 .350685-2.526526C.350685-.366625 1.354919 .167372 2.12802 .167372C2.510585 .167372 2.925031 .063761 3.283686-.278954C3.602491-.589788 3.873474-.924533 3.873474-1.617933C3.873474-2.662017 3.084433-3.395268 2.199751-3.395268C1.625903-3.395268 1.283188-3.028643 1.099875-2.638107ZM2.12802-.071731C1.705604-.071731 1.44259-.366625 1.323039-.589788C1.139726-.948443 1.123786-1.490411 1.123786-1.793275C1.123786-2.582316 1.554172-3.172105 2.16787-3.172105C2.566376-3.172105 2.805479-2.964882 2.956912-2.685928C3.124284-2.391034 3.124284-2.032379 3.124284-1.625903S3.124284-.868742 2.964882-.581818C2.757659-.215193 2.478705-.071731 2.12802-.071731Z"/>
|
||||
<path id="g2-55" d="M4.032877-4.853798C4.104608-4.941469 4.104608-4.95741 4.104608-5.132752H2.080199C1.880946-5.132752 1.633873-5.140722 1.43462-5.156663C1.020174-5.188543 1.012204-5.260274 .988294-5.387796H.74122L.470237-3.706102H.71731C.73325-3.825654 .820922-4.375592 .932503-4.439352C1.020174-4.479203 1.617933-4.479203 1.737484-4.479203H3.427148L2.606227-3.379328C1.697634-2.16787 1.506351-.908593 1.506351-.278954C1.506351-.199253 1.506351 .167372 1.880946 .167372S2.255542-.191283 2.255542-.286924V-.669489C2.255542-1.817186 2.446824-2.757659 2.83736-3.275716L4.032877-4.853798Z"/>
|
||||
<path id="g2-56" d="M2.646077-2.87721C3.092403-3.092403 3.634371-3.490909 3.634371-4.112578C3.634371-4.869738 2.86127-5.300125 2.12005-5.300125C1.275218-5.300125 .589788-4.718306 .589788-3.969116C.589788-3.674222 .6934-3.403238 .892653-3.172105C1.028144-3.004732 1.060025-2.988792 1.554172-2.677958C.565878-2.239601 .350685-1.657783 .350685-1.211457C.350685-.334745 1.235367 .167372 2.10411 .167372C3.084433 .167372 3.873474-.494147 3.873474-1.338979C3.873474-1.841096 3.602491-2.175841 3.474969-2.311333C3.339477-2.438854 3.331507-2.446824 2.646077-2.87721ZM1.41071-3.626401C1.179577-3.761893 .988294-3.993026 .988294-4.27198C.988294-4.774097 1.538232-5.092902 2.10411-5.092902C2.725778-5.092902 3.235866-4.670486 3.235866-4.112578C3.235866-3.650311 2.87721-3.259776 2.406974-3.028643L1.41071-3.626401ZM1.801245-2.534496C1.833126-2.518555 2.741719-1.960648 2.87721-1.872976C3.004732-1.801245 3.419178-1.546202 3.419178-1.067995C3.419178-.454296 2.773599-.071731 2.12005-.071731C1.41071-.071731 .804981-.557908 .804981-1.211457C.804981-1.809215 1.251308-2.279452 1.801245-2.534496Z"/>
|
||||
<path id="g1-58" d="M2.199751-.573848C2.199751-.920548 1.912827-1.159651 1.625903-1.159651C1.279203-1.159651 1.0401-.872727 1.0401-.585803C1.0401-.239103 1.327024 0 1.613948 0C1.960648 0 2.199751-.286924 2.199751-.573848Z"/>
|
||||
<path id="g1-65" d="M2.032379-1.327024C1.613948-.621669 1.207472-.382565 .633624-.3467C.502117-.334745 .406476-.334745 .406476-.119552C.406476-.047821 .466252 0 .549938 0C.765131 0 1.303113-.02391 1.518306-.02391C1.865006-.02391 2.247572 0 2.582316 0C2.654047 0 2.797509 0 2.797509-.227148C2.797509-.334745 2.701868-.3467 2.630137-.3467C2.355168-.37061 2.12802-.466252 2.12802-.753176C2.12802-.920548 2.199751-1.052055 2.355168-1.315068L3.263761-2.82142H6.312329C6.324284-2.713823 6.324284-2.618182 6.336239-2.510585C6.372105-2.199751 6.515567-.956413 6.515567-.729265C6.515567-.37061 5.905853-.3467 5.71457-.3467C5.583064-.3467 5.451557-.3467 5.451557-.131507C5.451557 0 5.559153 0 5.630884 0C5.834122 0 6.073225-.02391 6.276463-.02391H6.957908C7.687173-.02391 8.2132 0 8.225156 0C8.308842 0 8.440349 0 8.440349-.227148C8.440349-.3467 8.332752-.3467 8.153425-.3467C7.49589-.3467 7.483935-.454296 7.44807-.812951L6.718804-8.272976C6.694894-8.51208 6.647073-8.53599 6.515567-8.53599C6.396015-8.53599 6.324284-8.51208 6.216687-8.332752L2.032379-1.327024ZM3.466999-3.16812L5.869988-7.185056L6.276463-3.16812H3.466999Z"/>
|
||||
<path id="g1-66" d="M4.375592-7.352428C4.483188-7.79477 4.531009-7.81868 4.99726-7.81868H6.551432C7.902366-7.81868 7.902366-6.670984 7.902366-6.563387C7.902366-5.595019 6.933998-4.363636 5.355915-4.363636H3.634371L4.375592-7.352428ZM6.396015-4.267995C7.699128-4.507098 8.88269-5.415691 8.88269-6.515567C8.88269-7.44807 8.057783-8.16538 6.706849-8.16538H2.86924C2.642092-8.16538 2.534496-8.16538 2.534496-7.938232C2.534496-7.81868 2.642092-7.81868 2.82142-7.81868C3.550685-7.81868 3.550685-7.723039 3.550685-7.591532C3.550685-7.567621 3.550685-7.49589 3.502864-7.316563L1.888917-.884682C1.78132-.466252 1.75741-.3467 .920548-.3467C.6934-.3467 .573848-.3467 .573848-.131507C.573848 0 .645579 0 .884682 0H4.985305C6.814446 0 8.225156-1.3868 8.225156-2.594271C8.225156-3.574595 7.364384-4.172354 6.396015-4.267995ZM4.698381-.3467H3.084433C2.917061-.3467 2.893151-.3467 2.82142-.358655C2.689913-.37061 2.677958-.394521 2.677958-.490162C2.677958-.573848 2.701868-.645579 2.725778-.753176L3.56264-4.124533H5.810212C7.220922-4.124533 7.220922-2.809465 7.220922-2.713823C7.220922-1.566127 6.180822-.3467 4.698381-.3467Z"/>
|
||||
<path id="g1-83" d="M7.591532-8.308842C7.591532-8.416438 7.507846-8.416438 7.483935-8.416438C7.436115-8.416438 7.424159-8.404483 7.280697-8.225156C7.208966-8.141469 6.718804-7.519801 6.706849-7.507846C6.312329-8.284932 5.523288-8.416438 5.021171-8.416438C3.502864-8.416438 2.12802-7.029639 2.12802-5.678705C2.12802-4.782067 2.666002-4.25604 3.251806-4.052802C3.383313-4.004981 4.088667-3.813699 4.447323-3.730012C5.057036-3.56264 5.212453-3.514819 5.463512-3.251806C5.511333-3.19203 5.750436-2.917061 5.750436-2.355168C5.750436-1.243337 4.722291-.095641 3.526775-.095641C2.546451-.095641 1.458531-.514072 1.458531-1.853051C1.458531-2.080199 1.506351-2.367123 1.542217-2.486675C1.542217-2.52254 1.554172-2.582316 1.554172-2.606227C1.554172-2.654047 1.530262-2.713823 1.43462-2.713823C1.327024-2.713823 1.315068-2.689913 1.267248-2.486675L.657534-.035866C.657534-.02391 .609714 .131507 .609714 .143462C.609714 .251059 .705355 .251059 .729265 .251059C.777086 .251059 .789041 .239103 .932503 .059776L1.482441-.657534C1.769365-.227148 2.391034 .251059 3.502864 .251059C5.045081 .251059 6.455791-1.243337 6.455791-2.737733C6.455791-3.239851 6.336239-3.682192 5.881943-4.124533C5.630884-4.375592 5.415691-4.435367 4.315816-4.722291C3.514819-4.937484 3.407223-4.97335 3.19203-5.164633C2.988792-5.36787 2.833375-5.654795 2.833375-6.06127C2.833375-7.065504 3.849564-8.093649 4.985305-8.093649C6.156912-8.093649 6.706849-7.376339 6.706849-6.240598C6.706849-5.929763 6.647073-5.606974 6.647073-5.559153C6.647073-5.451557 6.742715-5.451557 6.77858-5.451557C6.886177-5.451557 6.898132-5.487422 6.945953-5.678705L7.591532-8.308842Z"/>
|
||||
</defs>
|
||||
<g fill="#222" stroke="#222" style="fill: var(--color, #222); stroke: var(--color, #222);" stroke-width="0.3" transform="matrix(1.13 0 0 1.13 -80.23 -66.443952)">
|
||||
<use x="70.734745" y="67.546657" xlink:href="#g1-83"/>
|
||||
<use x="77.934085" y="69.33992" xlink:href="#g2-49"/>
|
||||
<use x="85.987225" y="67.546657" xlink:href="#g3-61"/>
|
||||
<use x="98.412706" y="67.546657" xlink:href="#g3-40"/>
|
||||
<use x="102.965032" y="67.546657" xlink:href="#g1-65"/>
|
||||
<use x="111.740378" y="69.33992" xlink:href="#g2-50"/>
|
||||
<use x="115.974561" y="69.33992" xlink:href="#g2-49"/>
|
||||
<use x="123.36354" y="67.546657" xlink:href="#g3-43"/>
|
||||
<use x="135.124855" y="67.546657" xlink:href="#g1-65"/>
|
||||
<use x="143.900201" y="69.33992" xlink:href="#g2-50"/>
|
||||
<use x="148.134384" y="69.33992" xlink:href="#g2-50"/>
|
||||
<use x="152.866699" y="67.546657" xlink:href="#g3-41"/>
|
||||
<use x="157.419025" y="67.546657" xlink:href="#g3-59"/>
|
||||
<use x="70.734745" y="84.483145" xlink:href="#g1-83"/>
|
||||
<use x="77.934085" y="86.276409" xlink:href="#g2-50"/>
|
||||
<use x="85.987225" y="84.483145" xlink:href="#g3-61"/>
|
||||
<use x="98.412706" y="84.483145" xlink:href="#g3-40"/>
|
||||
<use x="102.965032" y="84.483145" xlink:href="#g1-83"/>
|
||||
<use x="110.164372" y="86.276409" xlink:href="#g2-49"/>
|
||||
<use x="117.55335" y="84.483145" xlink:href="#g0-0"/>
|
||||
<use x="129.50851" y="84.483145" xlink:href="#g1-65"/>
|
||||
<use x="138.283857" y="86.276409" xlink:href="#g2-49"/>
|
||||
<use x="142.51804" y="86.276409" xlink:href="#g2-49"/>
|
||||
<use x="147.250355" y="84.483145" xlink:href="#g3-41"/>
|
||||
<use x="151.802681" y="84.483145" xlink:href="#g3-59"/>
|
||||
<use x="70.734745" y="101.419634" xlink:href="#g1-83"/>
|
||||
<use x="77.934085" y="103.212897" xlink:href="#g2-51"/>
|
||||
<use x="85.987225" y="101.419634" xlink:href="#g3-61"/>
|
||||
<use x="98.412706" y="101.419634" xlink:href="#g3-40"/>
|
||||
<use x="102.965032" y="101.419634" xlink:href="#g1-65"/>
|
||||
<use x="111.740378" y="103.212897" xlink:href="#g2-49"/>
|
||||
<use x="115.974561" y="103.212897" xlink:href="#g2-49"/>
|
||||
<use x="123.36354" y="101.419634" xlink:href="#g0-0"/>
|
||||
<use x="135.3187" y="101.419634" xlink:href="#g1-65"/>
|
||||
<use x="144.094047" y="103.212897" xlink:href="#g2-50"/>
|
||||
<use x="148.32823" y="103.212897" xlink:href="#g2-49"/>
|
||||
<use x="153.060544" y="101.419634" xlink:href="#g3-41"/>
|
||||
<use x="157.61287" y="101.419634" xlink:href="#g3-59"/>
|
||||
<use x="70.734745" y="118.356122" xlink:href="#g1-83"/>
|
||||
<use x="77.934085" y="120.149385" xlink:href="#g2-52"/>
|
||||
<use x="85.987225" y="118.356122" xlink:href="#g3-61"/>
|
||||
<use x="98.412706" y="118.356122" xlink:href="#g3-40"/>
|
||||
<use x="102.965032" y="118.356122" xlink:href="#g1-65"/>
|
||||
<use x="111.740378" y="120.149385" xlink:href="#g2-49"/>
|
||||
<use x="115.974561" y="120.149385" xlink:href="#g2-50"/>
|
||||
<use x="123.36354" y="118.356122" xlink:href="#g0-0"/>
|
||||
<use x="135.3187" y="118.356122" xlink:href="#g1-83"/>
|
||||
<use x="142.51804" y="120.149385" xlink:href="#g2-50"/>
|
||||
<use x="147.250355" y="118.356122" xlink:href="#g3-41"/>
|
||||
<use x="151.802681" y="118.356122" xlink:href="#g3-59"/>
|
||||
<use x="70.734745" y="135.29261" xlink:href="#g1-83"/>
|
||||
<use x="77.934085" y="137.085873" xlink:href="#g2-53"/>
|
||||
<use x="85.987225" y="135.29261" xlink:href="#g3-61"/>
|
||||
<use x="98.412706" y="135.29261" xlink:href="#g3-40"/>
|
||||
<use x="102.965032" y="135.29261" xlink:href="#g1-66"/>
|
||||
<use x="111.857995" y="137.085873" xlink:href="#g2-49"/>
|
||||
<use x="116.092177" y="137.085873" xlink:href="#g2-50"/>
|
||||
<use x="123.481156" y="135.29261" xlink:href="#g0-0"/>
|
||||
<use x="135.436316" y="135.29261" xlink:href="#g1-66"/>
|
||||
<use x="144.329279" y="137.085873" xlink:href="#g2-49"/>
|
||||
<use x="148.563462" y="137.085873" xlink:href="#g2-49"/>
|
||||
<use x="153.295777" y="135.29261" xlink:href="#g3-41"/>
|
||||
<use x="157.848103" y="135.29261" xlink:href="#g3-59"/>
|
||||
<use x="70.734745" y="152.229098" xlink:href="#g1-83"/>
|
||||
<use x="77.934085" y="154.022361" xlink:href="#g2-54"/>
|
||||
<use x="85.987225" y="152.229098" xlink:href="#g3-61"/>
|
||||
<use x="98.412706" y="152.229098" xlink:href="#g3-40"/>
|
||||
<use x="102.965032" y="152.229098" xlink:href="#g1-66"/>
|
||||
<use x="111.857995" y="154.022361" xlink:href="#g2-50"/>
|
||||
<use x="116.092177" y="154.022361" xlink:href="#g2-50"/>
|
||||
<use x="123.481156" y="152.229098" xlink:href="#g0-0"/>
|
||||
<use x="135.436316" y="152.229098" xlink:href="#g1-83"/>
|
||||
<use x="142.635656" y="154.022361" xlink:href="#g2-53"/>
|
||||
<use x="147.367971" y="152.229098" xlink:href="#g3-41"/>
|
||||
<use x="151.920297" y="152.229098" xlink:href="#g3-59"/>
|
||||
<use x="70.734745" y="169.165586" xlink:href="#g1-83"/>
|
||||
<use x="77.934085" y="170.958849" xlink:href="#g2-55"/>
|
||||
<use x="85.987225" y="169.165586" xlink:href="#g3-61"/>
|
||||
<use x="98.412706" y="169.165586" xlink:href="#g3-40"/>
|
||||
<use x="102.965032" y="169.165586" xlink:href="#g1-66"/>
|
||||
<use x="111.857995" y="170.958849" xlink:href="#g2-50"/>
|
||||
<use x="116.092177" y="170.958849" xlink:href="#g2-50"/>
|
||||
<use x="123.481156" y="169.165586" xlink:href="#g0-0"/>
|
||||
<use x="135.436316" y="169.165586" xlink:href="#g1-66"/>
|
||||
<use x="144.329279" y="170.958849" xlink:href="#g2-49"/>
|
||||
<use x="148.563462" y="170.958849" xlink:href="#g2-50"/>
|
||||
<use x="153.295777" y="169.165586" xlink:href="#g3-41"/>
|
||||
<use x="157.848103" y="169.165586" xlink:href="#g3-59"/>
|
||||
<use x="70.734745" y="186.102074" xlink:href="#g1-83"/>
|
||||
<use x="77.934085" y="187.895338" xlink:href="#g2-56"/>
|
||||
<use x="85.987225" y="186.102074" xlink:href="#g3-61"/>
|
||||
<use x="98.412706" y="186.102074" xlink:href="#g3-40"/>
|
||||
<use x="102.965032" y="186.102074" xlink:href="#g1-83"/>
|
||||
<use x="110.164372" y="187.895338" xlink:href="#g2-54"/>
|
||||
<use x="117.55335" y="186.102074" xlink:href="#g0-0"/>
|
||||
<use x="129.50851" y="186.102074" xlink:href="#g1-66"/>
|
||||
<use x="138.401473" y="187.895338" xlink:href="#g2-50"/>
|
||||
<use x="142.635656" y="187.895338" xlink:href="#g2-49"/>
|
||||
<use x="147.367971" y="186.102074" xlink:href="#g3-41"/>
|
||||
<use x="151.920297" y="186.102074" xlink:href="#g1-58"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 20 KiB |
34
jekyll_site/img/sums2.svg
Normal file
34
jekyll_site/img/sums2.svg
Normal file
|
@ -0,0 +1,34 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="77.459681pt" height="30.391498pt" viewBox="-.299738 -.251628 77.459681 30.391498">
|
||||
<defs>
|
||||
<path id="g2-43" d="M4.770112-2.761644H8.069738C8.237111-2.761644 8.452304-2.761644 8.452304-2.976837C8.452304-3.203985 8.249066-3.203985 8.069738-3.203985H4.770112V-6.503611C4.770112-6.670984 4.770112-6.886177 4.554919-6.886177C4.327771-6.886177 4.327771-6.682939 4.327771-6.503611V-3.203985H1.028144C.860772-3.203985 .645579-3.203985 .645579-2.988792C.645579-2.761644 .848817-2.761644 1.028144-2.761644H4.327771V.537983C4.327771 .705355 4.327771 .920548 4.542964 .920548C4.770112 .920548 4.770112 .71731 4.770112 .537983V-2.761644Z"/>
|
||||
<path id="g2-59" d="M2.199751-4.578829C2.199751-4.901619 1.924782-5.152677 1.625903-5.152677C1.279203-5.152677 1.0401-4.877709 1.0401-4.578829C1.0401-4.220174 1.338979-3.993026 1.613948-3.993026C1.936737-3.993026 2.199751-4.244085 2.199751-4.578829ZM1.996513-.119552C1.996513 .298879 1.996513 1.147696 1.267248 2.044334C1.195517 2.139975 1.195517 2.163885 1.195517 2.187796C1.195517 2.247572 1.255293 2.307347 1.315068 2.307347C1.398755 2.307347 2.235616 1.422665 2.235616 .02391C2.235616-.418431 2.199751-1.159651 1.613948-1.159651C1.267248-1.159651 1.0401-.896638 1.0401-.585803C1.0401-.263014 1.267248 0 1.625903 0C1.853051 0 1.936737-.071731 1.996513-.119552Z"/>
|
||||
<path id="g2-61" d="M8.069738-3.873474C8.237111-3.873474 8.452304-3.873474 8.452304-4.088667C8.452304-4.315816 8.249066-4.315816 8.069738-4.315816H1.028144C.860772-4.315816 .645579-4.315816 .645579-4.100623C.645579-3.873474 .848817-3.873474 1.028144-3.873474H8.069738ZM8.069738-1.649813C8.237111-1.649813 8.452304-1.649813 8.452304-1.865006C8.452304-2.092154 8.249066-2.092154 8.069738-2.092154H1.028144C.860772-2.092154 .645579-2.092154 .645579-1.876961C.645579-1.649813 .848817-1.649813 1.028144-1.649813H8.069738Z"/>
|
||||
<path id="g1-49" d="M2.502615-5.076961C2.502615-5.292154 2.486675-5.300125 2.271482-5.300125C1.944707-4.98132 1.522291-4.790037 .765131-4.790037V-4.527024C.980324-4.527024 1.41071-4.527024 1.872976-4.742217V-.653549C1.872976-.358655 1.849066-.263014 1.091905-.263014H.812951V0C1.139726-.02391 1.825156-.02391 2.183811-.02391S3.235866-.02391 3.56264 0V-.263014H3.283686C2.526526-.263014 2.502615-.358655 2.502615-.653549V-5.076961Z"/>
|
||||
<path id="g1-50" d="M2.247572-1.625903C2.375093-1.745455 2.709838-2.008468 2.83736-2.12005C3.331507-2.574346 3.801743-3.012702 3.801743-3.737983C3.801743-4.686426 3.004732-5.300125 2.008468-5.300125C1.052055-5.300125 .422416-4.574844 .422416-3.865504C.422416-3.474969 .73325-3.419178 .844832-3.419178C1.012204-3.419178 1.259278-3.53873 1.259278-3.841594C1.259278-4.25604 .860772-4.25604 .765131-4.25604C.996264-4.837858 1.530262-5.037111 1.920797-5.037111C2.662017-5.037111 3.044583-4.407472 3.044583-3.737983C3.044583-2.909091 2.462765-2.303362 1.522291-1.338979L.518057-.302864C.422416-.215193 .422416-.199253 .422416 0H3.57061L3.801743-1.42665H3.55467C3.53076-1.267248 3.466999-.868742 3.371357-.71731C3.323537-.653549 2.717808-.653549 2.590286-.653549H1.171606L2.247572-1.625903Z"/>
|
||||
<path id="g1-52" d="M3.140224-5.156663C3.140224-5.316065 3.140224-5.379826 2.972852-5.379826C2.86924-5.379826 2.86127-5.371856 2.781569-5.260274L.239103-1.570112V-1.307098H2.486675V-.645579C2.486675-.350685 2.462765-.263014 1.849066-.263014H1.665753V0C2.343213-.02391 2.359153-.02391 2.81345-.02391S3.283686-.02391 3.961146 0V-.263014H3.777833C3.164134-.263014 3.140224-.350685 3.140224-.645579V-1.307098H3.985056V-1.570112H3.140224V-5.156663ZM2.542466-4.511083V-1.570112H.518057L2.542466-4.511083Z"/>
|
||||
<path id="g0-58" d="M2.199751-.573848C2.199751-.920548 1.912827-1.159651 1.625903-1.159651C1.279203-1.159651 1.0401-.872727 1.0401-.585803C1.0401-.239103 1.327024 0 1.613948 0C1.960648 0 2.199751-.286924 2.199751-.573848Z"/>
|
||||
<path id="g0-80" d="M3.53873-3.801743H5.547198C7.197011-3.801743 8.846824-5.021171 8.846824-6.38406C8.846824-7.316563 8.057783-8.16538 6.551432-8.16538H2.857285C2.630137-8.16538 2.52254-8.16538 2.52254-7.938232C2.52254-7.81868 2.630137-7.81868 2.809465-7.81868C3.53873-7.81868 3.53873-7.723039 3.53873-7.591532C3.53873-7.567621 3.53873-7.49589 3.490909-7.316563L1.876961-.884682C1.769365-.466252 1.745455-.3467 .908593-.3467C.681445-.3467 .561893-.3467 .561893-.131507C.561893 0 .669489 0 .74122 0C.968369 0 1.207472-.02391 1.43462-.02391H2.833375C3.060523-.02391 3.311582 0 3.53873 0C3.634371 0 3.765878 0 3.765878-.227148C3.765878-.3467 3.658281-.3467 3.478954-.3467C2.761644-.3467 2.749689-.430386 2.749689-.549938C2.749689-.609714 2.761644-.6934 2.773599-.753176L3.53873-3.801743ZM4.399502-7.352428C4.507098-7.79477 4.554919-7.81868 5.021171-7.81868H6.204732C7.10137-7.81868 7.84259-7.531756 7.84259-6.635118C7.84259-6.324284 7.687173-5.308095 7.137235-4.758157C6.933998-4.542964 6.360149-4.088667 5.272229-4.088667H3.58655L4.399502-7.352428Z"/>
|
||||
<path id="g0-84" d="M4.985305-7.292653C5.057036-7.579577 5.080946-7.687173 5.260274-7.734994C5.355915-7.758904 5.750436-7.758904 6.001494-7.758904C7.197011-7.758904 7.758904-7.711083 7.758904-6.77858C7.758904-6.599253 7.711083-6.144956 7.639352-5.702615L7.627397-5.559153C7.627397-5.511333 7.675218-5.439601 7.746949-5.439601C7.866501-5.439601 7.866501-5.499377 7.902366-5.69066L8.249066-7.806725C8.272976-7.914321 8.272976-7.938232 8.272976-7.974097C8.272976-8.105604 8.201245-8.105604 7.962142-8.105604H1.422665C1.147696-8.105604 1.135741-8.093649 1.06401-7.878456L.334745-5.726526C.32279-5.702615 .286924-5.571108 .286924-5.559153C.286924-5.499377 .334745-5.439601 .406476-5.439601C.502117-5.439601 .526027-5.487422 .573848-5.642839C1.075965-7.089415 1.327024-7.758904 2.917061-7.758904H3.718057C4.004981-7.758904 4.124533-7.758904 4.124533-7.627397C4.124533-7.591532 4.124533-7.567621 4.064757-7.352428L2.462765-.932503C2.343213-.466252 2.319303-.3467 1.052055-.3467C.753176-.3467 .669489-.3467 .669489-.119552C.669489 0 .800996 0 .860772 0C1.159651 0 1.470486-.02391 1.769365-.02391H3.634371C3.93325-.02391 4.25604 0 4.554919 0C4.686426 0 4.805978 0 4.805978-.227148C4.805978-.3467 4.722291-.3467 4.411457-.3467C3.335492-.3467 3.335492-.454296 3.335492-.633624C3.335492-.645579 3.335492-.729265 3.383313-.920548L4.985305-7.292653Z"/>
|
||||
</defs>
|
||||
<g fill="#222" stroke="#222" style="fill: var(--color, #222); stroke: var(--color, #222);" stroke-width="0.3" transform="matrix(1.13 0 0 1.13 -80.23 -67.352472)">
|
||||
<use x="70.734745" y="67.546657" xlink:href="#g0-84"/>
|
||||
<use x="77.59575" y="69.33992" xlink:href="#g1-49"/>
|
||||
<use x="85.648894" y="67.546657" xlink:href="#g2-61"/>
|
||||
<use x="98.074375" y="67.546657" xlink:href="#g0-80"/>
|
||||
<use x="105.619643" y="69.33992" xlink:href="#g1-49"/>
|
||||
<use x="113.008621" y="67.546657" xlink:href="#g2-43"/>
|
||||
<use x="124.769936" y="67.546657" xlink:href="#g0-80"/>
|
||||
<use x="132.315204" y="69.33992" xlink:href="#g1-50"/>
|
||||
<use x="137.047519" y="67.546657" xlink:href="#g2-59"/>
|
||||
<use x="70.734745" y="84.483145" xlink:href="#g0-84"/>
|
||||
<use x="77.59575" y="86.276409" xlink:href="#g1-50"/>
|
||||
<use x="85.648894" y="84.483145" xlink:href="#g2-61"/>
|
||||
<use x="98.074375" y="84.483145" xlink:href="#g0-84"/>
|
||||
<use x="104.93538" y="86.276409" xlink:href="#g1-49"/>
|
||||
<use x="112.324359" y="84.483145" xlink:href="#g2-43"/>
|
||||
<use x="124.085674" y="84.483145" xlink:href="#g0-80"/>
|
||||
<use x="131.630941" y="86.276409" xlink:href="#g1-52"/>
|
||||
<use x="136.363256" y="84.483145" xlink:href="#g0-58"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.2 KiB |
63
jekyll_site/img/sums3.svg
Normal file
63
jekyll_site/img/sums3.svg
Normal file
|
@ -0,0 +1,63 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="239.236252pt" height="32.408907pt" viewBox="-.299738 -.244857 239.236252 32.408907">
|
||||
<defs>
|
||||
<path id="g1-0" d="M7.878456-2.749689C8.081694-2.749689 8.296887-2.749689 8.296887-2.988792S8.081694-3.227895 7.878456-3.227895H1.41071C1.207472-3.227895 .992279-3.227895 .992279-2.988792S1.207472-2.749689 1.41071-2.749689H7.878456Z"/>
|
||||
<path id="g4-43" d="M4.770112-2.761644H8.069738C8.237111-2.761644 8.452304-2.761644 8.452304-2.976837C8.452304-3.203985 8.249066-3.203985 8.069738-3.203985H4.770112V-6.503611C4.770112-6.670984 4.770112-6.886177 4.554919-6.886177C4.327771-6.886177 4.327771-6.682939 4.327771-6.503611V-3.203985H1.028144C.860772-3.203985 .645579-3.203985 .645579-2.988792C.645579-2.761644 .848817-2.761644 1.028144-2.761644H4.327771V.537983C4.327771 .705355 4.327771 .920548 4.542964 .920548C4.770112 .920548 4.770112 .71731 4.770112 .537983V-2.761644Z"/>
|
||||
<path id="g4-61" d="M8.069738-3.873474C8.237111-3.873474 8.452304-3.873474 8.452304-4.088667C8.452304-4.315816 8.249066-4.315816 8.069738-4.315816H1.028144C.860772-4.315816 .645579-4.315816 .645579-4.100623C.645579-3.873474 .848817-3.873474 1.028144-3.873474H8.069738ZM8.069738-1.649813C8.237111-1.649813 8.452304-1.649813 8.452304-1.865006C8.452304-2.092154 8.249066-2.092154 8.069738-2.092154H1.028144C.860772-2.092154 .645579-2.092154 .645579-1.876961C.645579-1.649813 .848817-1.649813 1.028144-1.649813H8.069738Z"/>
|
||||
<path id="g3-49" d="M2.502615-5.076961C2.502615-5.292154 2.486675-5.300125 2.271482-5.300125C1.944707-4.98132 1.522291-4.790037 .765131-4.790037V-4.527024C.980324-4.527024 1.41071-4.527024 1.872976-4.742217V-.653549C1.872976-.358655 1.849066-.263014 1.091905-.263014H.812951V0C1.139726-.02391 1.825156-.02391 2.183811-.02391S3.235866-.02391 3.56264 0V-.263014H3.283686C2.526526-.263014 2.502615-.358655 2.502615-.653549V-5.076961Z"/>
|
||||
<path id="g3-50" d="M2.247572-1.625903C2.375093-1.745455 2.709838-2.008468 2.83736-2.12005C3.331507-2.574346 3.801743-3.012702 3.801743-3.737983C3.801743-4.686426 3.004732-5.300125 2.008468-5.300125C1.052055-5.300125 .422416-4.574844 .422416-3.865504C.422416-3.474969 .73325-3.419178 .844832-3.419178C1.012204-3.419178 1.259278-3.53873 1.259278-3.841594C1.259278-4.25604 .860772-4.25604 .765131-4.25604C.996264-4.837858 1.530262-5.037111 1.920797-5.037111C2.662017-5.037111 3.044583-4.407472 3.044583-3.737983C3.044583-2.909091 2.462765-2.303362 1.522291-1.338979L.518057-.302864C.422416-.215193 .422416-.199253 .422416 0H3.57061L3.801743-1.42665H3.55467C3.53076-1.267248 3.466999-.868742 3.371357-.71731C3.323537-.653549 2.717808-.653549 2.590286-.653549H1.171606L2.247572-1.625903Z"/>
|
||||
<path id="g3-51" d="M2.016438-2.662017C2.646077-2.662017 3.044583-2.199751 3.044583-1.362889C3.044583-.366625 2.478705-.071731 2.056289-.071731C1.617933-.071731 1.020174-.231133 .74122-.653549C1.028144-.653549 1.227397-.836862 1.227397-1.099875C1.227397-1.354919 1.044085-1.538232 .789041-1.538232C.573848-1.538232 .350685-1.40274 .350685-1.083935C.350685-.326775 1.163636 .167372 2.072229 .167372C3.132254 .167372 3.873474-.565878 3.873474-1.362889C3.873474-2.024408 3.347447-2.630137 2.534496-2.805479C3.164134-3.028643 3.634371-3.57061 3.634371-4.208219S2.917061-5.300125 2.088169-5.300125C1.235367-5.300125 .589788-4.837858 .589788-4.23213C.589788-3.937235 .789041-3.809714 .996264-3.809714C1.243337-3.809714 1.40274-3.985056 1.40274-4.216189C1.40274-4.511083 1.147696-4.622665 .972354-4.630635C1.307098-5.068991 1.920797-5.092902 2.064259-5.092902C2.271482-5.092902 2.87721-5.029141 2.87721-4.208219C2.87721-3.650311 2.646077-3.315567 2.534496-3.188045C2.295392-2.940971 2.11208-2.925031 1.625903-2.893151C1.474471-2.885181 1.41071-2.87721 1.41071-2.773599C1.41071-2.662017 1.482441-2.662017 1.617933-2.662017H2.016438Z"/>
|
||||
<path id="g3-53" d="M1.115816-4.479203C1.219427-4.447323 1.538232-4.367621 1.872976-4.367621C2.86924-4.367621 3.474969-5.068991 3.474969-5.188543C3.474969-5.276214 3.419178-5.300125 3.379328-5.300125C3.363387-5.300125 3.347447-5.300125 3.275716-5.260274C2.964882-5.140722 2.598257-5.045081 2.16787-5.045081C1.697634-5.045081 1.307098-5.164633 1.060025-5.260274C.980324-5.300125 .964384-5.300125 .956413-5.300125C.852802-5.300125 .852802-5.212453 .852802-5.068991V-2.733748C.852802-2.590286 .852802-2.494645 .980324-2.494645C1.044085-2.494645 1.067995-2.526526 1.107846-2.590286C1.203487-2.709838 1.506351-3.116314 2.183811-3.116314C2.630137-3.116314 2.84533-2.749689 2.917061-2.598257C3.052553-2.311333 3.068493-1.944707 3.068493-1.633873C3.068493-1.338979 3.060523-.908593 2.83736-.557908C2.685928-.318804 2.367123-.071731 1.944707-.071731C1.42665-.071731 .916563-.398506 .73325-.916563C.757161-.908593 .804981-.908593 .812951-.908593C1.036115-.908593 1.211457-1.052055 1.211457-1.299128C1.211457-1.594022 .980324-1.697634 .820922-1.697634C.67746-1.697634 .422416-1.617933 .422416-1.275218C.422416-.557908 1.044085 .167372 1.960648 .167372C2.956912 .167372 3.801743-.605729 3.801743-1.594022C3.801743-2.518555 3.132254-3.339477 2.191781-3.339477C1.793275-3.339477 1.41868-3.211955 1.115816-2.940971V-4.479203Z"/>
|
||||
<path id="g3-54" d="M1.099875-2.638107C1.099875-3.299626 1.155666-3.881445 1.44259-4.367621C1.681694-4.766127 2.088169-5.092902 2.590286-5.092902C2.749689-5.092902 3.116314-5.068991 3.299626-4.790037C2.940971-4.774097 2.909091-4.503113 2.909091-4.415442C2.909091-4.176339 3.092403-4.040847 3.283686-4.040847C3.427148-4.040847 3.658281-4.128518 3.658281-4.431382C3.658281-4.909589 3.299626-5.300125 2.582316-5.300125C1.474471-5.300125 .350685-4.24807 .350685-2.526526C.350685-.366625 1.354919 .167372 2.12802 .167372C2.510585 .167372 2.925031 .063761 3.283686-.278954C3.602491-.589788 3.873474-.924533 3.873474-1.617933C3.873474-2.662017 3.084433-3.395268 2.199751-3.395268C1.625903-3.395268 1.283188-3.028643 1.099875-2.638107ZM2.12802-.071731C1.705604-.071731 1.44259-.366625 1.323039-.589788C1.139726-.948443 1.123786-1.490411 1.123786-1.793275C1.123786-2.582316 1.554172-3.172105 2.16787-3.172105C2.566376-3.172105 2.805479-2.964882 2.956912-2.685928C3.124284-2.391034 3.124284-2.032379 3.124284-1.625903S3.124284-.868742 2.964882-.581818C2.757659-.215193 2.478705-.071731 2.12802-.071731Z"/>
|
||||
<path id="g3-55" d="M4.032877-4.853798C4.104608-4.941469 4.104608-4.95741 4.104608-5.132752H2.080199C1.880946-5.132752 1.633873-5.140722 1.43462-5.156663C1.020174-5.188543 1.012204-5.260274 .988294-5.387796H.74122L.470237-3.706102H.71731C.73325-3.825654 .820922-4.375592 .932503-4.439352C1.020174-4.479203 1.617933-4.479203 1.737484-4.479203H3.427148L2.606227-3.379328C1.697634-2.16787 1.506351-.908593 1.506351-.278954C1.506351-.199253 1.506351 .167372 1.880946 .167372S2.255542-.191283 2.255542-.286924V-.669489C2.255542-1.817186 2.446824-2.757659 2.83736-3.275716L4.032877-4.853798Z"/>
|
||||
<path id="g2-58" d="M2.199751-.573848C2.199751-.920548 1.912827-1.159651 1.625903-1.159651C1.279203-1.159651 1.0401-.872727 1.0401-.585803C1.0401-.239103 1.327024 0 1.613948 0C1.960648 0 2.199751-.286924 2.199751-.573848Z"/>
|
||||
<path id="g2-67" d="M8.930511-8.308842C8.930511-8.416438 8.846824-8.416438 8.822914-8.416438S8.751183-8.416438 8.655542-8.296887L7.830635-7.292653C7.412204-8.009963 6.75467-8.416438 5.858032-8.416438C3.275716-8.416438 .597758-5.798257 .597758-2.988792C.597758-.992279 1.996513 .251059 3.741968 .251059C4.698381 .251059 5.535243-.155417 6.228643-.74122C7.268742-1.613948 7.579577-2.773599 7.579577-2.86924C7.579577-2.976837 7.483935-2.976837 7.44807-2.976837C7.340473-2.976837 7.328518-2.905106 7.304608-2.857285C6.75467-.992279 5.140722-.095641 3.945205-.095641C2.677958-.095641 1.578082-.908593 1.578082-2.606227C1.578082-2.988792 1.697634-5.068991 3.048568-6.635118C3.706102-7.400249 4.829888-8.069738 5.965629-8.069738C7.280697-8.069738 7.866501-6.981818 7.866501-5.762391C7.866501-5.451557 7.830635-5.188543 7.830635-5.140722C7.830635-5.033126 7.950187-5.033126 7.986052-5.033126C8.117559-5.033126 8.129514-5.045081 8.177335-5.260274L8.930511-8.308842Z"/>
|
||||
<path id="g2-80" d="M3.53873-3.801743H5.547198C7.197011-3.801743 8.846824-5.021171 8.846824-6.38406C8.846824-7.316563 8.057783-8.16538 6.551432-8.16538H2.857285C2.630137-8.16538 2.52254-8.16538 2.52254-7.938232C2.52254-7.81868 2.630137-7.81868 2.809465-7.81868C3.53873-7.81868 3.53873-7.723039 3.53873-7.591532C3.53873-7.567621 3.53873-7.49589 3.490909-7.316563L1.876961-.884682C1.769365-.466252 1.745455-.3467 .908593-.3467C.681445-.3467 .561893-.3467 .561893-.131507C.561893 0 .669489 0 .74122 0C.968369 0 1.207472-.02391 1.43462-.02391H2.833375C3.060523-.02391 3.311582 0 3.53873 0C3.634371 0 3.765878 0 3.765878-.227148C3.765878-.3467 3.658281-.3467 3.478954-.3467C2.761644-.3467 2.749689-.430386 2.749689-.549938C2.749689-.609714 2.761644-.6934 2.773599-.753176L3.53873-3.801743ZM4.399502-7.352428C4.507098-7.79477 4.554919-7.81868 5.021171-7.81868H6.204732C7.10137-7.81868 7.84259-7.531756 7.84259-6.635118C7.84259-6.324284 7.687173-5.308095 7.137235-4.758157C6.933998-4.542964 6.360149-4.088667 5.272229-4.088667H3.58655L4.399502-7.352428Z"/>
|
||||
<path id="g2-84" d="M4.985305-7.292653C5.057036-7.579577 5.080946-7.687173 5.260274-7.734994C5.355915-7.758904 5.750436-7.758904 6.001494-7.758904C7.197011-7.758904 7.758904-7.711083 7.758904-6.77858C7.758904-6.599253 7.711083-6.144956 7.639352-5.702615L7.627397-5.559153C7.627397-5.511333 7.675218-5.439601 7.746949-5.439601C7.866501-5.439601 7.866501-5.499377 7.902366-5.69066L8.249066-7.806725C8.272976-7.914321 8.272976-7.938232 8.272976-7.974097C8.272976-8.105604 8.201245-8.105604 7.962142-8.105604H1.422665C1.147696-8.105604 1.135741-8.093649 1.06401-7.878456L.334745-5.726526C.32279-5.702615 .286924-5.571108 .286924-5.559153C.286924-5.499377 .334745-5.439601 .406476-5.439601C.502117-5.439601 .526027-5.487422 .573848-5.642839C1.075965-7.089415 1.327024-7.758904 2.917061-7.758904H3.718057C4.004981-7.758904 4.124533-7.758904 4.124533-7.627397C4.124533-7.591532 4.124533-7.567621 4.064757-7.352428L2.462765-.932503C2.343213-.466252 2.319303-.3467 1.052055-.3467C.753176-.3467 .669489-.3467 .669489-.119552C.669489 0 .800996 0 .860772 0C1.159651 0 1.470486-.02391 1.769365-.02391H3.634371C3.93325-.02391 4.25604 0 4.554919 0C4.686426 0 4.805978 0 4.805978-.227148C4.805978-.3467 4.722291-.3467 4.411457-.3467C3.335492-.3467 3.335492-.454296 3.335492-.633624C3.335492-.645579 3.335492-.729265 3.383313-.920548L4.985305-7.292653Z"/>
|
||||
<path id="g0-18" d="M8.368618 28.08269C8.368618 28.034869 8.344707 28.010959 8.320797 27.975093C7.878456 27.532752 7.07746 26.731756 6.276463 25.440598C4.351681 22.356164 3.478954 18.470735 3.478954 13.867995C3.478954 10.652055 3.90934 6.503611 5.881943 2.940971C6.826401 1.243337 7.806725 .263014 8.332752-.263014C8.368618-.298879 8.368618-.32279 8.368618-.358655C8.368618-.478207 8.284932-.478207 8.117559-.478207S7.926276-.478207 7.746949-.298879C3.741968 3.347447 2.486675 8.822914 2.486675 13.85604C2.486675 18.554421 3.56264 23.288667 6.599253 26.863263C6.838356 27.138232 7.292653 27.628394 7.782814 28.05878C7.926276 28.202242 7.950187 28.202242 8.117559 28.202242S8.368618 28.202242 8.368618 28.08269Z"/>
|
||||
<path id="g0-19" d="M6.300374 13.867995C6.300374 9.169614 5.224408 4.435367 2.187796 .860772C1.948692 .585803 1.494396 .095641 1.004234-.334745C.860772-.478207 .836862-.478207 .669489-.478207C.526027-.478207 .418431-.478207 .418431-.358655C.418431-.310834 .466252-.263014 .490162-.239103C.908593 .191283 1.709589 .992279 2.510585 2.283437C4.435367 5.36787 5.308095 9.2533 5.308095 13.85604C5.308095 17.07198 4.877709 21.220423 2.905106 24.783064C1.960648 26.480697 .968369 27.472976 .466252 27.975093C.442341 28.010959 .418431 28.046824 .418431 28.08269C.418431 28.202242 .526027 28.202242 .669489 28.202242C.836862 28.202242 .860772 28.202242 1.0401 28.022914C5.045081 24.376588 6.300374 18.901121 6.300374 13.867995Z"/>
|
||||
</defs>
|
||||
<g fill="#222" stroke="#222" style="fill: var(--color, #222); stroke: var(--color, #222);" stroke-width="0.3" transform="matrix(1.13 0 0 1.13 -80.23 -65.539978)">
|
||||
<use x="70.734745" y="58.2615" xlink:href="#g0-18"/>
|
||||
<use x="79.535117" y="67.945294" xlink:href="#g2-67"/>
|
||||
<use x="87.910163" y="69.738557" xlink:href="#g3-49"/>
|
||||
<use x="92.144346" y="69.738557" xlink:href="#g3-49"/>
|
||||
<use x="106.839297" y="67.945294" xlink:href="#g2-67"/>
|
||||
<use x="115.214343" y="69.738557" xlink:href="#g3-49"/>
|
||||
<use x="119.448526" y="69.738557" xlink:href="#g3-50"/>
|
||||
<use x="79.535117" y="81.89299" xlink:href="#g2-67"/>
|
||||
<use x="87.910163" y="83.686253" xlink:href="#g3-50"/>
|
||||
<use x="92.144346" y="83.686253" xlink:href="#g3-49"/>
|
||||
<use x="106.839297" y="81.89299" xlink:href="#g2-67"/>
|
||||
<use x="115.214343" y="83.686253" xlink:href="#g3-50"/>
|
||||
<use x="119.448526" y="83.686253" xlink:href="#g3-50"/>
|
||||
<use x="124.180837" y="58.2615" xlink:href="#g0-19"/>
|
||||
<use x="136.302039" y="75.118437" xlink:href="#g4-61"/>
|
||||
<use x="148.72752" y="58.2615" xlink:href="#g0-18"/>
|
||||
<use x="157.527892" y="67.945294" xlink:href="#g2-80"/>
|
||||
<use x="165.07316" y="69.738557" xlink:href="#g3-50"/>
|
||||
<use x="172.462138" y="67.945294" xlink:href="#g4-43"/>
|
||||
<use x="184.223453" y="67.945294" xlink:href="#g2-80"/>
|
||||
<use x="191.768721" y="69.738557" xlink:href="#g3-51"/>
|
||||
<use x="206.463676" y="67.945294" xlink:href="#g2-84"/>
|
||||
<use x="213.324681" y="69.738557" xlink:href="#g3-49"/>
|
||||
<use x="220.71366" y="67.945294" xlink:href="#g4-43"/>
|
||||
<use x="232.474975" y="67.945294" xlink:href="#g2-80"/>
|
||||
<use x="240.020243" y="69.738557" xlink:href="#g3-53"/>
|
||||
<use x="247.409221" y="67.945294" xlink:href="#g4-43"/>
|
||||
<use x="259.170536" y="67.945294" xlink:href="#g2-80"/>
|
||||
<use x="266.715804" y="69.738557" xlink:href="#g3-54"/>
|
||||
<use x="157.773112" y="81.89299" xlink:href="#g2-84"/>
|
||||
<use x="164.634117" y="83.686253" xlink:href="#g3-50"/>
|
||||
<use x="172.023096" y="81.89299" xlink:href="#g1-0"/>
|
||||
<use x="183.978256" y="81.89299" xlink:href="#g2-80"/>
|
||||
<use x="191.523524" y="83.686253" xlink:href="#g3-55"/>
|
||||
<use x="219.811464" y="81.89299" xlink:href="#g2-84"/>
|
||||
<use x="226.67247" y="83.686253" xlink:href="#g3-50"/>
|
||||
<use x="234.061448" y="81.89299" xlink:href="#g4-43"/>
|
||||
<use x="245.822763" y="81.89299" xlink:href="#g2-80"/>
|
||||
<use x="253.368031" y="83.686253" xlink:href="#g3-53"/>
|
||||
<use x="271.448119" y="58.2615" xlink:href="#g0-19"/>
|
||||
<use x="280.248491" y="75.118437" xlink:href="#g2-58"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 14 KiB |
7
jekyll_site/robots.txt
Normal file
7
jekyll_site/robots.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
User-agent: *
|
||||
Disallow: *404*
|
||||
|
||||
Sitemap: https://pomodoro3.mircloud.ru/pagesmap.xml
|
||||
Sitemap: https://pomodoro3.mircloud.ru/color/pagesmap.xml
|
||||
|
||||
Host: https://pomodoro3.mircloud.ru
|
246
jekyll_site/ru/2021/12/09/optimizing-matrix-multiplication.md
Normal file
246
jekyll_site/ru/2021/12/09/optimizing-matrix-multiplication.md
Normal file
|
@ -0,0 +1,246 @@
|
|||
---
|
||||
title: Оптимизация умножения матриц
|
||||
description: Рассмотрим алгоритм перемножения матриц с использованием трёх вложенных циклов. Сложность такого алгоритма по определению должна составлять O(n³), но есть...
|
||||
sections: [Перестановки,Вложенные циклы,Сравнение алгоритмов]
|
||||
tags: [java,массивы,многомерные массивы,матрицы,строки,колонки,слои,циклы]
|
||||
canonical_url: /ru/2021/12/09/optimizing-matrix-multiplication.html
|
||||
url_translated: /en/2021/12/10/optimizing-matrix-multiplication.html
|
||||
title_translated: Optimizing matrix multiplication
|
||||
date: 2021.12.09
|
||||
---
|
||||
|
||||
Рассмотрим алгоритм перемножения матриц с использованием трёх вложенных циклов. Сложность такого
|
||||
алгоритма по определению должна составлять `O(n³)`, но есть особенности, связанные со средой
|
||||
выполнения — скорость работы алгоритма зависит от последовательности, в которой выполняются циклы.
|
||||
|
||||
Сравним различные варианты перестановок вложенных циклов и время выполнения алгоритмов.
|
||||
Возьмём две матрицы: {`L×M`} и {`M×N`} → три цикла → шесть перестановок:
|
||||
`LMN`, `LNM`, `MLN`, `MNL`, `NLM`, `NML`.
|
||||
|
||||
Быстрее других отрабатывают те алгоритмы, которые пишут данные в результирующую матрицу *построчно слоями*:
|
||||
`LMN` и `MLN`, — разница в процентах к другим алгоритмам значительная и зависит от среды выполнения.
|
||||
|
||||
*Дальнейшая оптимизация: [Умножение матриц в параллельных потоках]({{ '/ru/2022/02/08/matrix-multiplication-parallel-streams.html' | relative_url }}).*
|
||||
|
||||
## Построчный алгоритм {#row-wise-algorithm}
|
||||
|
||||
Внешний цикл обходит строки первой матрицы `L`, далее идёт цикл по *общей стороне* двух матриц `M`
|
||||
и за ним цикл по колонкам второй матрицы `N`. Запись в результирующую матрицу происходит построчно,
|
||||
а каждая строка заполняется слоями.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param l строки матрицы 'a'
|
||||
* @param m колонки матрицы 'a'
|
||||
* и строки матрицы 'b'
|
||||
* @param n колонки матрицы 'b'
|
||||
* @param a первая матрица 'l×m'
|
||||
* @param b вторая матрица 'm×n'
|
||||
* @return результирующая матрица 'l×n'
|
||||
*/
|
||||
public static int[][] matrixMultiplicationLMN(int l, int m, int n, int[][] a, int[][] b) {
|
||||
// результирующая матрица
|
||||
int[][] c = new int[l][n];
|
||||
// обходим индексы строк матрицы 'a'
|
||||
for (int i = 0; i < l; i++)
|
||||
// обходим индексы общей стороны двух матриц:
|
||||
// колонок матрицы 'a' и строк матрицы 'b'
|
||||
for (int k = 0; k < m; k++)
|
||||
// обходим индексы колонок матрицы 'b'
|
||||
for (int j = 0; j < n; j++)
|
||||
// сумма произведений элементов i-ой строки
|
||||
// матрицы 'a' и j-ой колонки матрицы 'b'
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
|
||||
## Послойный алгоритм {#layer-wise-algorithm}
|
||||
|
||||
Внешний цикл обходит *общую сторону* двух матриц `M`, далее идёт цикл по строкам первой матрицы
|
||||
`L` и за ним цикл по колонкам второй матрицы `N`. Запись в результирующую матрицу происходит слоями,
|
||||
а каждый слой заполняется построчно.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param l строки матрицы 'a'
|
||||
* @param m колонки матрицы 'a'
|
||||
* и строки матрицы 'b'
|
||||
* @param n колонки матрицы 'b'
|
||||
* @param a первая матрица 'l×m'
|
||||
* @param b вторая матрица 'm×n'
|
||||
* @return результирующая матрица 'l×n'
|
||||
*/
|
||||
public static int[][] matrixMultiplicationMLN(int l, int m, int n, int[][] a, int[][] b) {
|
||||
// результирующая матрица
|
||||
int[][] c = new int[l][n];
|
||||
// обходим индексы общей стороны двух матриц:
|
||||
// колонок матрицы 'a' и строк матрицы 'b'
|
||||
for (int k = 0; k < m; k++)
|
||||
// обходим индексы строк матрицы 'a'
|
||||
for (int i = 0; i < l; i++)
|
||||
// обходим индексы колонок матрицы 'b'
|
||||
for (int j = 0; j < n; j++)
|
||||
// сумма произведений элементов i-ой строки
|
||||
// матрицы 'a' и j-ой колонки матрицы 'b'
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
|
||||
### Прочие алгоритмы {#other-algorithms}
|
||||
|
||||
Обход колонок второй матрицы `N` происходит перед обходом *общей стороны* двух матриц `M`
|
||||
и/или перед обходом строк первой матрицы `L`.
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
public static int[][] matrixMultiplicationLNM(int l, int m, int n, int[][] a, int[][] b) {
|
||||
int[][] c = new int[l][n];
|
||||
for (int i = 0; i < l; i++)
|
||||
for (int j = 0; j < n; j++)
|
||||
for (int k = 0; k < m; k++)
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
```java
|
||||
public static int[][] matrixMultiplicationNLM(int l, int m, int n, int[][] a, int[][] b) {
|
||||
int[][] c = new int[l][n];
|
||||
for (int j = 0; j < n; j++)
|
||||
for (int i = 0; i < l; i++)
|
||||
for (int k = 0; k < m; k++)
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
```java
|
||||
public static int[][] matrixMultiplicationMNL(int l, int m, int n, int[][] a, int[][] b) {
|
||||
int[][] c = new int[l][n];
|
||||
for (int k = 0; k < m; k++)
|
||||
for (int j = 0; j < n; j++)
|
||||
for (int i = 0; i < l; i++)
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
```java
|
||||
public static int[][] matrixMultiplicationNML(int l, int m, int n, int[][] a, int[][] b) {
|
||||
int[][] c = new int[l][n];
|
||||
for (int j = 0; j < n; j++)
|
||||
for (int k = 0; k < m; k++)
|
||||
for (int i = 0; i < l; i++)
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Код без комментариев" content=collapsed_md -%}
|
||||
|
||||
## Сравнение алгоритмов {#comparing-algorithms}
|
||||
|
||||
Для проверки возьмём две матрицы `A=[500×700]` и `B=[700×450]`, заполненные случайными числами.
|
||||
Сначала сравниваем между собой корректность реализации алгоритмов — все полученные результаты
|
||||
должны совпадать. Затем выполняем каждый метод по 10 раз и подсчитываем среднее время выполнения.
|
||||
|
||||
```java
|
||||
// запускаем программу и выводим результат
|
||||
public static void main(String[] args) throws Exception {
|
||||
// входящие данные
|
||||
int l = 500, m = 700, n = 450, steps = 10;
|
||||
int[][] a = randomMatrix(l, m), b = randomMatrix(m, n);
|
||||
// карта методов для сравнения
|
||||
var methods = new TreeMap<String, Callable<int[][]>>(Map.of(
|
||||
"LMN", () -> matrixMultiplicationLMN(l, m, n, a, b),
|
||||
"LNM", () -> matrixMultiplicationLNM(l, m, n, a, b),
|
||||
"MLN", () -> matrixMultiplicationMLN(l, m, n, a, b),
|
||||
"MNL", () -> matrixMultiplicationMNL(l, m, n, a, b),
|
||||
"NLM", () -> matrixMultiplicationNLM(l, m, n, a, b),
|
||||
"NML", () -> matrixMultiplicationNML(l, m, n, a, b)));
|
||||
int[][] last = null;
|
||||
// обходим карту методов, проверяем корректность результатов,
|
||||
// все полученные результаты должны быть равны друг другу
|
||||
for (var method : methods.entrySet()) {
|
||||
// следующий метод для сравнения
|
||||
var next = methods.higherEntry(method.getKey());
|
||||
// если текущий метод не последний — сравниваем результаты двух методов
|
||||
if (next != null) System.out.println(method.getKey() + "=" + next.getKey() + ": "
|
||||
// сравниваем результат выполнения текущего метода и следующего за ним
|
||||
+ Arrays.deepEquals(method.getValue().call(), next.getValue().call()));
|
||||
// результат выполнения последнего метода
|
||||
else last = method.getValue().call();
|
||||
}
|
||||
int[][] test = last;
|
||||
// обходим карту методов, замеряем время работы каждого метода
|
||||
for (var method : methods.entrySet())
|
||||
// параметры: заголовок, количество шагов, исполняемый код
|
||||
benchmark(method.getKey(), steps, () -> {
|
||||
try { // выполняем метод, получаем результат
|
||||
int[][] result = method.getValue().call();
|
||||
// проверяем корректность результатов на каждом шаге
|
||||
if (!Arrays.deepEquals(result, test)) System.out.print("error");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
// вспомогательный метод, возвращает матрицу указанного размера
|
||||
private static int[][] randomMatrix(int row, int col) {
|
||||
int[][] matrix = new int[row][col];
|
||||
for (int i = 0; i < row; i++)
|
||||
for (int j = 0; j < col; j++)
|
||||
matrix[i][j] = (int) (Math.random() * row * col);
|
||||
return matrix;
|
||||
}
|
||||
```
|
||||
```java
|
||||
// вспомогательный метод для замера времени работы переданного кода
|
||||
private static void benchmark(String title, int steps, Runnable runnable) {
|
||||
long time, avg = 0;
|
||||
System.out.print(title);
|
||||
for (int i = 0; i < steps; i++) {
|
||||
time = System.currentTimeMillis();
|
||||
runnable.run();
|
||||
time = System.currentTimeMillis() - time;
|
||||
// время выполнения одного шага
|
||||
System.out.print(" | " + time);
|
||||
avg += time;
|
||||
}
|
||||
// среднее время выполнения
|
||||
System.out.println(" || " + (avg / steps));
|
||||
}
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Вспомогательные методы" content=collapsed_md -%}
|
||||
|
||||
Вывод зависит от среды выполнения, время в миллисекундах:
|
||||
|
||||
```
|
||||
LMN=LNM: true
|
||||
LNM=MLN: true
|
||||
MLN=MNL: true
|
||||
MNL=NLM: true
|
||||
NLM=NML: true
|
||||
LMN | 191 | 109 | 105 | 106 | 105 | 106 | 106 | 105 | 123 | 109 || 116
|
||||
LNM | 417 | 418 | 419 | 416 | 416 | 417 | 418 | 417 | 416 | 417 || 417
|
||||
MLN | 113 | 115 | 113 | 115 | 114 | 114 | 114 | 115 | 114 | 113 || 114
|
||||
MNL | 857 | 864 | 857 | 859 | 860 | 863 | 862 | 860 | 858 | 860 || 860
|
||||
NLM | 404 | 404 | 407 | 404 | 406 | 405 | 405 | 404 | 403 | 404 || 404
|
||||
NML | 866 | 872 | 867 | 868 | 867 | 868 | 867 | 873 | 869 | 863 || 868
|
||||
```
|
||||
|
||||
Все описанные выше методы, включая свёрнутые блоки, можно поместить в одном классе.
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.Callable;
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Необходимые импорты" content=collapsed_md -%}
|
96
jekyll_site/ru/2021/12/12/matrix-rotation-90-degrees.md
Normal file
96
jekyll_site/ru/2021/12/12/matrix-rotation-90-degrees.md
Normal file
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
title: Поворот матрицы на 90 градусов
|
||||
description: Рассмотрим алгоритм поворота матрицы на 90 градусов по часовой стрелке и против часовой стрелки. Этот алгоритм похож на транспонирование матрицы с тем...
|
||||
sections: [Транспонирование,Сравнение алгоритмов]
|
||||
tags: [java,массивы,многомерные массивы,матрицы,строки,колонки,циклы,вложенные циклы]
|
||||
canonical_url: /ru/2021/12/12/matrix-rotation-90-degrees.html
|
||||
url_translated: /en/2021/12/13/matrix-rotation-90-degrees.html
|
||||
title_translated: Matrix rotation 90 degrees
|
||||
date: 2021.12.12
|
||||
---
|
||||
|
||||
Рассмотрим алгоритм поворота матрицы на 90 градусов по часовой стрелке и против часовой стрелки.
|
||||
Этот алгоритм похож на *транспонирование матрицы* с тем отличием, что здесь для каждой точки
|
||||
одна из координат отображается зеркально.
|
||||
|
||||
```java
|
||||
// транспонирование матрицы — строки и колонки меняются местами
|
||||
swapped[j][i] = matrix[i][j];
|
||||
// по часовой стрелке ↻ — строки отображаются зеркально
|
||||
rotated[j][i] = matrix[m-i-1][j];
|
||||
// против часовой стрелки ↺ — колонки отображаются зеркально
|
||||
rotated[j][i] = matrix[i][n-j-1];
|
||||
```
|
||||
|
||||
*Похожий алгоритм: [Поворот матрицы на 180 градусов]({{ '/ru/2021/12/16/matrix-rotation-180-degrees.html' | relative_url }}).*
|
||||
|
||||
Напишем метод на Java для поворота матрицы {`m×n`} на 90 градусов. Дополнительный параметр задаёт
|
||||
направление поворота: по часовой стрелке или против часовой стрелки. Для примера возьмём
|
||||
прямоугольную матрицу {`4×3`}.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param m количество строк исходной матрицы
|
||||
* @param n количество колонок исходной матрицы
|
||||
* @param clock направление поворота: по часовой
|
||||
* стрелке ↻ или против часовой стрелки ↺
|
||||
* @param matrix исходная матрица
|
||||
* @return повёрнутая матрица
|
||||
*/
|
||||
public static int[][] rotateMatrix(int m, int n, boolean clock, int[][] matrix) {
|
||||
// новая матрица, количества строк и колонок меняются местами
|
||||
int[][] rotated = new int[n][m];
|
||||
// обходим строки исходной матрицы
|
||||
for (int i = 0; i < m; i++)
|
||||
// обходим колонки исходной матрицы
|
||||
for (int j = 0; j < n; j++)
|
||||
if (clock) // поворот по часовой стрелке ↻
|
||||
rotated[j][i] = matrix[m-i-1][j];
|
||||
else // поворот против часовой стрелки ↺
|
||||
rotated[j][i] = matrix[i][n-j-1];
|
||||
return rotated;
|
||||
}
|
||||
```
|
||||
{% raw %}
|
||||
```java
|
||||
// запускаем программу и выводим результат
|
||||
public static void main(String[] args) {
|
||||
// исходные данные
|
||||
int m = 4, n = 3;
|
||||
int[][] matrix = {{11, 12, 13}, {14, 15, 16}, {17, 18, 19}, {20, 21, 22}};
|
||||
// поворачиваем матрицу и выводим результат
|
||||
outputMatrix("Исходная матрица:", matrix);
|
||||
outputMatrix("По часовой стрелке ↻:", rotateMatrix(m, n, true, matrix));
|
||||
outputMatrix("Против часовой стрелки ↺:", rotateMatrix(m, n, false, matrix));
|
||||
}
|
||||
```
|
||||
{% endraw %}
|
||||
```java
|
||||
// вспомогательный метод, выводит матрицу в консоль построчно
|
||||
public static void outputMatrix(String title, int[][] matrix) {
|
||||
System.out.println(title);
|
||||
for (int[] row : matrix) {
|
||||
for (int el : row)
|
||||
System.out.print(" " + el);
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Вывод:
|
||||
|
||||
```
|
||||
Исходная матрица:
|
||||
11 12 13
|
||||
14 15 16
|
||||
17 18 19
|
||||
20 21 22
|
||||
По часовой стрелке ↻:
|
||||
20 17 14 11
|
||||
21 18 15 12
|
||||
22 19 16 13
|
||||
Против часовой стрелки ↺:
|
||||
13 16 19 22
|
||||
12 15 18 21
|
||||
11 14 17 20
|
||||
```
|
84
jekyll_site/ru/2021/12/16/matrix-rotation-180-degrees.md
Normal file
84
jekyll_site/ru/2021/12/16/matrix-rotation-180-degrees.md
Normal file
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
title: Поворот матрицы на 180 градусов
|
||||
description: Рассмотрим алгоритм разворота матрицы на 180 градусов. В отличие от алгоритма транспонирования, здесь в результирующей матрице строки и колонки не меняются...
|
||||
sections: [Транспонирование,Сравнение алгоритмов]
|
||||
tags: [java,массивы,многомерные массивы,матрицы,строки,колонки,циклы,вложенные циклы]
|
||||
canonical_url: /ru/2021/12/16/matrix-rotation-180-degrees.html
|
||||
url_translated: /en/2021/12/17/matrix-rotation-180-degrees.html
|
||||
title_translated: Matrix rotation 180 degrees
|
||||
date: 2021.12.16
|
||||
---
|
||||
|
||||
Рассмотрим алгоритм разворота матрицы на 180 градусов. В отличие от алгоритма *транспонирования*,
|
||||
здесь в результирующей матрице строки и колонки не меняются местами, но отображаются зеркально.
|
||||
|
||||
```java
|
||||
// строки и колонки меняются местами
|
||||
swapped[j][i] = matrix[i][j];
|
||||
// строки и колонки отображаются зеркально
|
||||
rotated[i][j] = matrix[m-i-1][n-j-1];
|
||||
```
|
||||
|
||||
*Похожий алгоритм: [Поворот матрицы на 90 градусов]({{ '/ru/2021/12/12/matrix-rotation-90-degrees.html' | relative_url }}).*
|
||||
|
||||
Напишем метод на Java для поворота матрицы {`m×n`} на 180 градусов. Для примера возьмём
|
||||
прямоугольную матрицу {`4×3`}.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param m количество строк исходной матрицы
|
||||
* @param n количество колонок исходной матрицы
|
||||
* @param matrix исходная матрица
|
||||
* @return повёрнутая матрица
|
||||
*/
|
||||
public static int[][] rotateMatrix(int m, int n, int[][] matrix) {
|
||||
// новая матрица
|
||||
int[][] rotated = new int[m][n];
|
||||
// обходим строки исходной матрицы
|
||||
for (int i = 0; i < m; i++)
|
||||
// обходим колонки исходной матрицы
|
||||
for (int j = 0; j < n; j++)
|
||||
// строки и колонки отображаются зеркально
|
||||
rotated[i][j] = matrix[m-i-1][n-j-1];
|
||||
return rotated;
|
||||
}
|
||||
```
|
||||
{% raw %}
|
||||
```java
|
||||
// запускаем программу и выводим результат
|
||||
public static void main(String[] args) {
|
||||
// исходные данные
|
||||
int m = 4, n = 3;
|
||||
int[][] matrix = {{11, 12, 13}, {14, 15, 16}, {17, 18, 19}, {20, 21, 22}};
|
||||
// поворачиваем матрицу и выводим результат
|
||||
outputMatrix("Исходная матрица:", matrix);
|
||||
outputMatrix("Поворот на 180°:", rotateMatrix(m, n, matrix));
|
||||
}
|
||||
```
|
||||
{% endraw %}
|
||||
```java
|
||||
// вспомогательный метод, выводит матрицу в консоль построчно
|
||||
public static void outputMatrix(String title, int[][] matrix) {
|
||||
System.out.println(title);
|
||||
for (int[] row : matrix) {
|
||||
for (int el : row)
|
||||
System.out.print(" " + el);
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Вывод:
|
||||
|
||||
```
|
||||
Исходная матрица:
|
||||
11 12 13
|
||||
14 15 16
|
||||
17 18 19
|
||||
20 21 22
|
||||
Поворот на 180°:
|
||||
22 21 20
|
||||
19 18 17
|
||||
16 15 14
|
||||
13 12 11
|
||||
```
|
|
@ -0,0 +1,182 @@
|
|||
---
|
||||
title: Умножение матриц в параллельных потоках
|
||||
description: Рассмотрим алгоритм перемножения прямоугольных матриц с использованием потоков Java Stream. Возьмём оптимизированный вариант алгоритма на трёх вложенных...
|
||||
sections: [Многопоточность,Вложенные циклы,Сравнение алгоритмов]
|
||||
tags: [java,потоки,массивы,многомерные массивы,матрицы,строки,циклы]
|
||||
canonical_url: /ru/2022/02/08/matrix-multiplication-parallel-streams.html
|
||||
url_translated: /en/2022/02/09/matrix-multiplication-parallel-streams.html
|
||||
title_translated: Matrix multiplication in parallel streams
|
||||
date: 2022.02.08
|
||||
---
|
||||
|
||||
Рассмотрим алгоритм перемножения прямоугольных матриц с использованием потоков Java Stream. Возьмём
|
||||
*оптимизированный вариант* алгоритма на трёх вложенных циклах и заменим внешний цикл на поток. Сравним
|
||||
время работы двух алгоритмов.
|
||||
|
||||
Строки результирующей матрицы обрабатываем в параллельном режиме, а каждую строку заполняем слоями.
|
||||
За счёт параллельного использования кеша среды выполнения на многоядерных машинах время вычислений
|
||||
можно сократить более чем в два раза. Для проверки возьмём две прямоугольные матрицы: {`L×M`} и {`M×N`}.
|
||||
|
||||
*Дальнейшая оптимизация: [Алгоритм Винограда — Штрассена]({{ '/ru/2022/02/10/winograd-strassen-algorithm.html' | relative_url }}).*
|
||||
|
||||
## Параллельный поток {#parallel-stream}
|
||||
|
||||
Строки первой матрицы `L` обходим в параллельном режиме. Внутри каждого потока два вложенных цикла:
|
||||
по *общей стороне* двух матриц `M` и по колонкам второй матрицы `N`. Обработка строк результирующей
|
||||
матрицы происходит независимо друг от друга в параллельных потоках.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param l строки матрицы 'a'
|
||||
* @param m колонки матрицы 'a'
|
||||
* и строки матрицы 'b'
|
||||
* @param n колонки матрицы 'b'
|
||||
* @param a первая матрица 'l×m'
|
||||
* @param b вторая матрица 'm×n'
|
||||
* @return результирующая матрица 'l×n'
|
||||
*/
|
||||
public static int[][] parallelMatrixMultiplication(int l, int m, int n, int[][] a, int[][] b) {
|
||||
// результирующая матрица
|
||||
int[][] c = new int[l][n];
|
||||
// обходим индексы строк матрицы 'a' в параллельном режиме
|
||||
IntStream.range(0, l).parallel().forEach(i -> {
|
||||
// обходим индексы общей стороны двух матриц:
|
||||
// колонок матрицы 'a' и строк матрицы 'b'
|
||||
for (int k = 0; k < m; k++)
|
||||
// обходим индексы колонок матрицы 'b'
|
||||
for (int j = 0; j < n; j++)
|
||||
// сумма произведений элементов i-ой строки
|
||||
// матрицы 'a' и j-ой колонки матрицы 'b'
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
});
|
||||
return c;
|
||||
}
|
||||
```
|
||||
|
||||
## Три вложенных цикла {#three-nested-loops}
|
||||
|
||||
Возьмём оптимизированный вариант алгоритма, который лучше прочих использует кеш среды выполнения.
|
||||
Внешний цикл обходит строки первой матрицы `L`, далее идёт цикл по *общей стороне* двух матриц `M`
|
||||
и за ним цикл по колонкам второй матрицы `N`. Запись в результирующую матрицу происходит построчно,
|
||||
а каждая строка заполняется слоями.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param l строки матрицы 'a'
|
||||
* @param m колонки матрицы 'a'
|
||||
* и строки матрицы 'b'
|
||||
* @param n колонки матрицы 'b'
|
||||
* @param a первая матрица 'l×m'
|
||||
* @param b вторая матрица 'm×n'
|
||||
* @return результирующая матрица 'l×n'
|
||||
*/
|
||||
public static int[][] sequentialMatrixMultiplication(int l, int m, int n, int[][] a, int[][] b) {
|
||||
// результирующая матрица
|
||||
int[][] c = new int[l][n];
|
||||
// обходим индексы строк матрицы 'a'
|
||||
for (int i = 0; i < l; i++)
|
||||
// обходим индексы общей стороны двух матриц:
|
||||
// колонок матрицы 'a' и строк матрицы 'b'
|
||||
for (int k = 0; k < m; k++)
|
||||
// обходим индексы колонок матрицы 'b'
|
||||
for (int j = 0; j < n; j++)
|
||||
// сумма произведений элементов i-ой строки
|
||||
// матрицы 'a' и j-ой колонки матрицы 'b'
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
|
||||
## Тестирование {#testing}
|
||||
|
||||
Для проверки возьмём две матрицы: `A=[900×1000]` и `B=[1000×750]`, заполненные случайными числами.
|
||||
Сначала сравниваем между собой корректность реализации двух алгоритмов — произведения матриц
|
||||
должны совпадать. Затем выполняем каждый метод по 10 раз и подсчитываем среднее время выполнения.
|
||||
|
||||
```java
|
||||
// запускаем программу и выводим результат
|
||||
public static void main(String[] args) {
|
||||
// входящие данные
|
||||
int l = 900, m = 1000, n = 750, steps = 10;
|
||||
int[][] a = randomMatrix(l, m), b = randomMatrix(m, n);
|
||||
// произведения матриц
|
||||
int[][] c1 = parallelMatrixMultiplication(l, m, n, a, b);
|
||||
int[][] c2 = sequentialMatrixMultiplication(l, m, n, a, b);
|
||||
// проверяем корректность результатов
|
||||
System.out.println("Результаты совпадают: " + Arrays.deepEquals(c1, c2));
|
||||
// замеряем время работы двух методов
|
||||
benchmark("Потоки", steps, () -> {
|
||||
int[][] c = parallelMatrixMultiplication(l, m, n, a, b);
|
||||
if (!Arrays.deepEquals(c, c1)) System.out.print("ошибка");
|
||||
});
|
||||
benchmark("Циклы", steps, () -> {
|
||||
int[][] c = sequentialMatrixMultiplication(l, m, n, a, b);
|
||||
if (!Arrays.deepEquals(c, c2)) System.out.print("ошибка");
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
// вспомогательный метод, возвращает матрицу указанного размера
|
||||
private static int[][] randomMatrix(int row, int col) {
|
||||
int[][] matrix = new int[row][col];
|
||||
for (int i = 0; i < row; i++)
|
||||
for (int j = 0; j < col; j++)
|
||||
matrix[i][j] = (int) (Math.random() * row * col);
|
||||
return matrix;
|
||||
}
|
||||
```
|
||||
```java
|
||||
// вспомогательный метод для замера времени работы переданного кода
|
||||
private static void benchmark(String title, int steps, Runnable runnable) {
|
||||
long time, avg = 0;
|
||||
System.out.print(title);
|
||||
for (int i = 0; i < steps; i++) {
|
||||
time = System.currentTimeMillis();
|
||||
runnable.run();
|
||||
time = System.currentTimeMillis() - time;
|
||||
// время выполнения одного шага
|
||||
System.out.print(" | " + time);
|
||||
avg += time;
|
||||
}
|
||||
// среднее время выполнения
|
||||
System.out.println(" || " + (avg / steps));
|
||||
}
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Вспомогательные методы" content=collapsed_md -%}
|
||||
|
||||
Вывод зависит от среды выполнения, время в миллисекундах:
|
||||
|
||||
```
|
||||
Результаты совпадают: true
|
||||
Потоки | 113 | 144 | 117 | 114 | 114 | 117 | 120 | 125 | 111 | 113 || 118
|
||||
Циклы | 1357 | 530 | 551 | 569 | 535 | 538 | 525 | 517 | 518 | 514 || 615
|
||||
```
|
||||
|
||||
## Сравнение алгоритмов {#comparing-algorithms}
|
||||
|
||||
На восьмиядерном компьютере Linux x64 создаём для тестов виртуальную машину Windows x64. При
|
||||
прочих равных условиях в настройках меняем количество процессоров. Запускаем вышеописанный
|
||||
тест 100 раз вместо 10 — получаем сводную таблицу результатов. Время в миллисекундах.
|
||||
|
||||
```
|
||||
ЦПУ | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|
||||
-------|-----|-----|-----|-----|-----|-----|-----|-----|
|
||||
Потоки | 522 | 276 | 179 | 154 | 128 | 112 | 110 | 93 |
|
||||
Циклы | 519 | 603 | 583 | 571 | 545 | 571 | 559 | 467 |
|
||||
```
|
||||
|
||||
Результаты: при большом количестве процессоров в системе многопоточный алгоритм отрабатывает
|
||||
заметно быстрее, чем три вложенных цикла. Время вычислений можно сократить более чем в два раза.
|
||||
|
||||
Все описанные выше методы, включая свёрнутые блоки, можно поместить в одном классе.
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.IntStream;
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Необходимые импорты" content=collapsed_md -%}
|
293
jekyll_site/ru/2022/02/10/winograd-strassen-algorithm.md
Normal file
293
jekyll_site/ru/2022/02/10/winograd-strassen-algorithm.md
Normal file
|
@ -0,0 +1,293 @@
|
|||
---
|
||||
title: Алгоритм Винограда — Штрассена
|
||||
description: Рассмотрим модификацию алгоритма Штрассена для умножения квадратных матриц с меньшим количеством сложений между блоками, чем в обычном алгоритме — 15 вместо...
|
||||
sections: [Многопоточность,Блочные матрицы,Сравнение алгоритмов]
|
||||
tags: [java,потоки,массивы,многомерные массивы,матрицы,рекурсия,циклы,вложенные циклы]
|
||||
canonical_url: /ru/2022/02/10/winograd-strassen-algorithm.html
|
||||
url_translated: /en/2022/02/11/winograd-strassen-algorithm.html
|
||||
title_translated: Winograd — Strassen algorithm
|
||||
date: 2022.02.10
|
||||
---
|
||||
|
||||
Рассмотрим модификацию алгоритма Штрассена для умножения квадратных матриц с *меньшим* количеством
|
||||
сложений между блоками, чем в обычном алгоритме — 15 вместо 18 и таким же количеством умножений как
|
||||
в обычном алгоритме — 7. Будем использовать потоки Java Stream.
|
||||
|
||||
Рекурсивное дробление матриц на блоки при перемножении имеет смысл до определенного предела, а дальше
|
||||
теряет смысл, так как алгоритм Штрассена не использует кеш среды выполнения. Поэтому для малых блоков
|
||||
будем использовать параллельный вариант вложенных циклов, а для больших блоков параллельно будем
|
||||
выполнять рекурсивное дробление.
|
||||
|
||||
Границу между двумя алгоритмами определяем экспериментально — подстраиваем под кеш среды выполнения.
|
||||
Выгода алгоритма Штрассена становится заметнее на больших матрицах — отличие от алгоритма на вложенных
|
||||
циклах становится больше и зависит от среды выполнения. Сравним время работы двух алгоритмов.
|
||||
|
||||
*Алгоритм на трёх вложенных циклах: [Оптимизация умножения матриц]({{ '/ru/2021/12/09/optimizing-matrix-multiplication.html' | relative_url }}).*
|
||||
|
||||
## Описание алгоритма {#algorithm-description}
|
||||
|
||||
Матрицы должны быть одинакового размера. Разделяем каждую матрицу на 4 равных блока. Блоки должны быть
|
||||
квадратными, поэтому если это не так, тогда сначала дополняем матрицы нулевыми строками и столбцами,
|
||||
а после этого разделяем на блоки. Лишние строки и столбцы потом уберём из результирующей матрицы.
|
||||
|
||||
{% include image_svg.html src="/img/block-matrices.svg" style="width:221pt; height:33pt;"
|
||||
alt="{\displaystyle A={\begin{pmatrix}A_{11}&A_{12}\\A_{21}&A_{22}\end{pmatrix}},\quad B={\begin{pmatrix}B_{11}&B_{12}\\B_{21}&B_{22}\end{pmatrix}}.}" %}
|
||||
|
||||
Суммируем блоки.
|
||||
|
||||
{% include image_svg.html src="/img/sums1.svg" style="width:101pt; height:148pt;"
|
||||
alt="{\displaystyle{\begin{aligned}S_{1}&=(A_{21}+A_{22});\\S_{2}&=(S_{1}-A_{11});\\S_{3}&=(A_{11}-A_{21});\\S_{4}&=(A_{12}-S_{2});\\S_{5}&=(B_{12}-B_{11});\\S_{6}&=(B_{22}-S_{5});\\S_{7}&=(B_{22}-B_{12});\\S_{8}&=(S_{6}-B_{21}).\end{aligned}}}" %}
|
||||
|
||||
Умножаем блоки.
|
||||
|
||||
{% include image_svg.html src="/img/products.svg" style="width:75pt; height:127pt;"
|
||||
alt="{\displaystyle{\begin{aligned}P_{1}&=S_{2}S_{6};\\P_{2}&=A_{11}B_{11};\\P_{3}&=A_{12}B_{21};\\P_{4}&=S_{3}S_{7};\\P_{5}&=S_{1}S_{5};\\P_{6}&=S_{4}B_{22};\\P_{7}&=A_{22}S_{8}.\end{aligned}}}" %}
|
||||
|
||||
Суммируем блоки.
|
||||
|
||||
{% include image_svg.html src="/img/sums2.svg" style="width:78pt; height:31pt;"
|
||||
alt="{\displaystyle{\begin{aligned}T_{1}&=P_{1}+P_{2};\\T_{2}&=T_{1}+P_{4}.\end{aligned}}}" %}
|
||||
|
||||
Блоки результирующей матрицы.
|
||||
|
||||
{% include image_svg.html src="/img/sums3.svg" style="width:240pt; height:33pt;"
|
||||
alt="{\displaystyle{\begin{pmatrix}C_{11}&C_{12}\\C_{21}&C_{22}\end{pmatrix}}={\begin{pmatrix}P_{2}+P_{3}&T_{1}+P_{5}+P_{6}\\T_{2}-P_{7}&T_{2}+P_{5}\end{pmatrix}}.}" %}
|
||||
|
||||
## Гибридный алгоритм {#hybrid-algorithm}
|
||||
|
||||
Каждую матрицу `A` и `B` делим на 4 *равных* блока, при необходимости дополняем недостающие части
|
||||
нулями. Выполняем 15 сложений и 7 умножений над блоками — получаем 4 блока матрицы `C`. Убираем
|
||||
лишние нули, если добавляли, и возвращаем результирующую матрицу. Рекурсивное дробление больших
|
||||
блоков запускаем в параллельном режиме, а для малых блоков вызываем алгоритм на вложенных циклах.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param n размер матрицы
|
||||
* @param brd минимальный размер матрицы
|
||||
* @param a первая матрица 'n×n'
|
||||
* @param b вторая матрица 'n×n'
|
||||
* @return результирующая матрица 'n×n'
|
||||
*/
|
||||
public static int[][] multiplyMatrices(int n, int brd, int[][] a, int[][] b) {
|
||||
// малые блоки перемножаем с помощью алгоритма на вложенных циклах
|
||||
if (n < brd) return simpleMultiplication(n, a, b);
|
||||
// серединная точка матрицы, округляем в большую сторону — блоки должны
|
||||
// быть квадратными, при необходимости добавляем нулевые строки и столбцы
|
||||
int m = n - n / 2;
|
||||
// блоки первой матрицы
|
||||
int[][] a11 = getQuadrant(m, n, a, true, true);
|
||||
int[][] a12 = getQuadrant(m, n, a, true, false);
|
||||
int[][] a21 = getQuadrant(m, n, a, false, true);
|
||||
int[][] a22 = getQuadrant(m, n, a, false, false);
|
||||
// блоки второй матрицы
|
||||
int[][] b11 = getQuadrant(m, n, b, true, true);
|
||||
int[][] b12 = getQuadrant(m, n, b, true, false);
|
||||
int[][] b21 = getQuadrant(m, n, b, false, true);
|
||||
int[][] b22 = getQuadrant(m, n, b, false, false);
|
||||
// суммируем блоки
|
||||
int[][] s1 = sumMatrices(m, a21, a22, true);
|
||||
int[][] s2 = sumMatrices(m, s1, a11, false);
|
||||
int[][] s3 = sumMatrices(m, a11, a21, false);
|
||||
int[][] s4 = sumMatrices(m, a12, s2, false);
|
||||
int[][] s5 = sumMatrices(m, b12, b11, false);
|
||||
int[][] s6 = sumMatrices(m, b22, s5, false);
|
||||
int[][] s7 = sumMatrices(m, b22, b12, false);
|
||||
int[][] s8 = sumMatrices(m, s6, b21, false);
|
||||
int[][][] p = new int[7][][];
|
||||
// перемножаем блоки в параллельных потоках
|
||||
IntStream.range(0, 7).parallel().forEach(i -> {
|
||||
switch (i) { // рекурсивные вызовы
|
||||
case 0: p[i] = multiplyMatrices(m, brd, s2, s6); break;
|
||||
case 1: p[i] = multiplyMatrices(m, brd, a11, b11); break;
|
||||
case 2: p[i] = multiplyMatrices(m, brd, a12, b21); break;
|
||||
case 3: p[i] = multiplyMatrices(m, brd, s3, s7); break;
|
||||
case 4: p[i] = multiplyMatrices(m, brd, s1, s5); break;
|
||||
case 5: p[i] = multiplyMatrices(m, brd, s4, b22); break;
|
||||
case 6: p[i] = multiplyMatrices(m, brd, a22, s8); break;
|
||||
}
|
||||
});
|
||||
// суммируем блоки
|
||||
int[][] t1 = sumMatrices(m, p[0], p[1], true);
|
||||
int[][] t2 = sumMatrices(m, t1, p[3], true);
|
||||
// блоки результирующей матрицы
|
||||
int[][] c11 = sumMatrices(m, p[1], p[2], true);
|
||||
int[][] c12 = sumMatrices(m, t1, sumMatrices(m, p[4], p[5], true), true);
|
||||
int[][] c21 = sumMatrices(m, t2, p[6], false);
|
||||
int[][] c22 = sumMatrices(m, t2, p[4], true);
|
||||
// собираем результирующую матрицу из блоков,
|
||||
// убираем нулевые строки и столбцы, если добавляли
|
||||
return putQuadrants(m, n, c11, c12, c21, c22);
|
||||
}
|
||||
```
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
// вспомогательный метод для суммирования матриц
|
||||
private static int[][] sumMatrices(int n, int[][] a, int[][] b, boolean sign) {
|
||||
int[][] c = new int[n][n];
|
||||
for (int i = 0; i < n; i++)
|
||||
for (int j = 0; j < n; j++)
|
||||
c[i][j] = sign ? a[i][j] + b[i][j] : a[i][j] - b[i][j];
|
||||
return c;
|
||||
}
|
||||
```
|
||||
```java
|
||||
// вспомогательный метод, получает блок матрицы
|
||||
private static int[][] getQuadrant(int m, int n, int[][] x,
|
||||
boolean first, boolean second) {
|
||||
int[][] q = new int[m][m];
|
||||
if (first) for (int i = 0; i < m; i++)
|
||||
if (second) System.arraycopy(x[i], 0, q[i], 0, m); // x11
|
||||
else System.arraycopy(x[i], m, q[i], 0, n - m); // x12
|
||||
else for (int i = m; i < n; i++)
|
||||
if (second) System.arraycopy(x[i], 0, q[i - m], 0, m); // x21
|
||||
else System.arraycopy(x[i], m, q[i - m], 0, n - m); // x22
|
||||
return q;
|
||||
}
|
||||
```
|
||||
```java
|
||||
// вспомогательный метод, собирает матрицу из блоков
|
||||
private static int[][] putQuadrants(int m, int n,
|
||||
int[][] x11, int[][] x12,
|
||||
int[][] x21, int[][] x22) {
|
||||
int[][] x = new int[n][n];
|
||||
for (int i = 0; i < n; i++)
|
||||
if (i < m) {
|
||||
System.arraycopy(x11[i], 0, x[i], 0, m);
|
||||
System.arraycopy(x12[i], 0, x[i], m, n - m);
|
||||
} else {
|
||||
System.arraycopy(x21[i - m], 0, x[i], 0, m);
|
||||
System.arraycopy(x22[i - m], 0, x[i], m, n - m);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Вспомогательные методы" content=collapsed_md -%}
|
||||
|
||||
## Вложенные циклы {#nested-loops}
|
||||
|
||||
Для дополнения предыдущего алгоритма и для сравнения с ним же будем использовать *оптимизированный*
|
||||
вариант вложенных циклов, который лучше прочих использует кеш среды выполнения — обработка строк
|
||||
результирующей матрицы выполняется независимо друг от друга в параллельных потоках. Для малых матриц
|
||||
будем использовать этот алгоритм — большие матрицы делим на малые блоки и используем этот же алгоритм.
|
||||
|
||||
```java
|
||||
/**
|
||||
* @param n размер матрицы
|
||||
* @param a первая матрица 'n×n'
|
||||
* @param b вторая матрица 'n×n'
|
||||
* @return результирующая матрица 'n×n'
|
||||
*/
|
||||
public static int[][] simpleMultiplication(int n, int[][] a, int[][] b) {
|
||||
// результирующая матрица
|
||||
int[][] c = new int[n][n];
|
||||
// обходим индексы строк матрицы 'a' в параллельном режиме
|
||||
IntStream.range(0, n).parallel().forEach(i -> {
|
||||
// обходим индексы общей стороны двух матриц:
|
||||
// колонок матрицы 'a' и строк матрицы 'b'
|
||||
for (int k = 0; k < n; k++)
|
||||
// обходим индексы колонок матрицы 'b'
|
||||
for (int j = 0; j < n; j++)
|
||||
// сумма произведений элементов i-ой строки
|
||||
// матрицы 'a' и j-ой колонки матрицы 'b'
|
||||
c[i][j] += a[i][k] * b[k][j];
|
||||
});
|
||||
return c;
|
||||
}
|
||||
```
|
||||
|
||||
## Тестирование {#testing}
|
||||
|
||||
Для проверки возьмём две квадратные матрицы `A=[1000×1000]` и `B=[1000×1000]`, заполненные случайными
|
||||
числами. Минимальный размер блока возьмём `[200×200]` элементов. Сначала сравниваем между собой
|
||||
корректность реализации двух алгоритмов — произведения матриц должны совпадать. Затем выполняем каждый
|
||||
метод по 10 раз и подсчитываем среднее время выполнения.
|
||||
|
||||
```java
|
||||
// запускаем программу и выводим результат
|
||||
public static void main(String[] args) {
|
||||
// входящие данные
|
||||
int n = 1000, brd = 200, steps = 10;
|
||||
int[][] a = randomMatrix(n, n), b = randomMatrix(n, n);
|
||||
// произведения матриц
|
||||
int[][] c1 = multiplyMatrices(n, brd, a, b);
|
||||
int[][] c2 = simpleMultiplication(n, a, b);
|
||||
// проверяем корректность результатов
|
||||
System.out.println("Результаты совпадают: " + Arrays.deepEquals(c1, c2));
|
||||
// замеряем время работы двух методов
|
||||
benchmark("Гибридный алгоритм", steps, () -> {
|
||||
int[][] c = multiplyMatrices(n, brd, a, b);
|
||||
if (!Arrays.deepEquals(c, c1)) System.out.print("ошибка");
|
||||
});
|
||||
benchmark("Вложенные циклы ", steps, () -> {
|
||||
int[][] c = simpleMultiplication(n, a, b);
|
||||
if (!Arrays.deepEquals(c, c2)) System.out.print("ошибка");
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
// вспомогательный метод, возвращает матрицу указанного размера
|
||||
private static int[][] randomMatrix(int row, int col) {
|
||||
int[][] matrix = new int[row][col];
|
||||
for (int i = 0; i < row; i++)
|
||||
for (int j = 0; j < col; j++)
|
||||
matrix[i][j] = (int) (Math.random() * row * col);
|
||||
return matrix;
|
||||
}
|
||||
```
|
||||
```java
|
||||
// вспомогательный метод для замера времени работы переданного кода
|
||||
private static void benchmark(String title, int steps, Runnable runnable) {
|
||||
long time, avg = 0;
|
||||
System.out.print(title);
|
||||
for (int i = 0; i < steps; i++) {
|
||||
time = System.currentTimeMillis();
|
||||
runnable.run();
|
||||
time = System.currentTimeMillis() - time;
|
||||
// время выполнения одного шага
|
||||
System.out.print(" | " + time);
|
||||
avg += time;
|
||||
}
|
||||
// среднее время выполнения
|
||||
System.out.println(" || " + (avg / steps));
|
||||
}
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Вспомогательные методы" content=collapsed_md -%}
|
||||
|
||||
Вывод зависит от среды выполнения, время в миллисекундах:
|
||||
|
||||
```
|
||||
Результаты совпадают: true
|
||||
Гибридный алгоритм | 196 | 177 | 156 | 205 | 154 | 165 | 133 | 118 | 132 | 134 || 157
|
||||
Вложенные циклы | 165 | 164 | 168 | 167 | 168 | 168 | 170 | 179 | 173 | 168 || 169
|
||||
```
|
||||
|
||||
## Сравнение алгоритмов {#comparing-algorithms}
|
||||
|
||||
На восьмиядерном компьютере Linux x64 запускаем вышеописанный тест 100 раз вместо 10. Минимальный
|
||||
размер блока берём `[brd=200]`. Изменяем только `n` — размеры обеих матриц `A=[n×n]` и `B=[n×n]`.
|
||||
Получаем сводную таблицу результатов. Время в миллисекундах.
|
||||
|
||||
```
|
||||
n | 900 | 1000 | 1100 | 1200 | 1300 | 1400 | 1500 | 1600 | 1700 |
|
||||
-------------------|-----|------|------|------|------|------|------|------|------|
|
||||
Гибридный алгоритм | 96 | 125 | 169 | 204 | 260 | 313 | 384 | 482 | 581 |
|
||||
Вложенные циклы | 119 | 162 | 235 | 281 | 361 | 497 | 651 | 793 | 971 |
|
||||
```
|
||||
|
||||
Результаты: выгода алгоритма Штрассена становится заметнее на больших матрицах, когда размер
|
||||
самой матрицы в несколько раз превышает размер минимального блока, и зависит от среды выполнения.
|
||||
|
||||
Все описанные выше методы, включая свёрнутые блоки, можно поместить в одном классе.
|
||||
|
||||
{% capture collapsed_md %}
|
||||
```java
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.IntStream;
|
||||
```
|
||||
{% endcapture %}
|
||||
{%- include collapsed_block.html summary="Необходимые импорты" content=collapsed_md -%}
|
73
jekyll_site/ru/index.md
Normal file
73
jekyll_site/ru/index.md
Normal file
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
title: Код с комментариями
|
||||
description: Заметки на тему программирования с примерами кода и комментариями. Решения задач и описания решений.
|
||||
sections: [Решения задач и описания решений]
|
||||
tags: [java,алгоритмы,реализация,массивы,многомерные массивы,матрицы,циклы,потоки]
|
||||
canonical_url: /
|
||||
url_translated: /en/
|
||||
title_translated: Code with comments
|
||||
---
|
||||
|
||||
{%- assign articles = "" | split: "" %}
|
||||
{%- assign articles = articles | push: "Алгоритм Винограда — Штрассена" %}
|
||||
{%- capture article_brief %}
|
||||
Рассмотрим модификацию алгоритма Штрассена для умножения квадратных матриц с *меньшим* количеством
|
||||
сложений между блоками, чем в обычном алгоритме — 15 вместо 18 и таким же количеством умножений как
|
||||
в обычном алгоритме — 7. Будем использовать потоки Java Stream.
|
||||
|
||||
Рекурсивное дробление матриц на блоки при перемножении имеет смысл до определенного предела, а дальше
|
||||
теряет смысл, так как алгоритм Штрассена не использует кеш среды выполнения. Поэтому для малых блоков
|
||||
будем использовать параллельный вариант вложенных циклов, а для больших блоков параллельно будем
|
||||
выполнять рекурсивное дробление.
|
||||
|
||||
Границу между двумя алгоритмами определяем экспериментально — подстраиваем под кеш среды выполнения.
|
||||
Выгода алгоритма Штрассена становится заметнее на больших матрицах — отличие от алгоритма на вложенных
|
||||
циклах становится больше и зависит от среды выполнения. Сравним время работы двух алгоритмов.
|
||||
{%- endcapture %}
|
||||
{%- assign articles = articles | push: article_brief %}
|
||||
{%- assign articles = articles | push: "Умножение матриц в параллельных потоках" %}
|
||||
{%- capture article_brief %}
|
||||
Рассмотрим алгоритм перемножения прямоугольных матриц с использованием потоков Java Stream. Возьмём
|
||||
*оптимизированный вариант* алгоритма на трёх вложенных циклах и заменим внешний цикл на поток. Сравним
|
||||
время работы двух алгоритмов.
|
||||
|
||||
Строки результирующей матрицы обрабатываем в параллельном режиме, а каждую строку заполняем слоями.
|
||||
За счёт параллельного использования кеша среды выполнения на многоядерных машинах время вычислений
|
||||
можно сократить более чем в два раза. Для проверки возьмём две прямоугольные матрицы: {`L×M`} и {`M×N`}.
|
||||
{%- endcapture %}
|
||||
{%- assign articles = articles | push: article_brief %}
|
||||
{%- assign articles = articles | push: "Поворот матрицы на 180 градусов" %}
|
||||
{%- capture article_brief %}
|
||||
Рассмотрим алгоритм разворота матрицы на 180 градусов. В отличие от алгоритма *транспонирования*,
|
||||
здесь в результирующей матрице строки и колонки не меняются местами, но отображаются зеркально.
|
||||
|
||||
Напишем метод на Java для поворота матрицы {`m×n`} на 180 градусов. Для примера возьмём
|
||||
прямоугольную матрицу {`4×3`}.
|
||||
{%- endcapture %}
|
||||
{%- assign articles = articles | push: article_brief %}
|
||||
{%- assign articles = articles | push: "Поворот матрицы на 90 градусов" %}
|
||||
{%- capture article_brief %}
|
||||
Рассмотрим алгоритм поворота матрицы на 90 градусов по часовой стрелке и против часовой стрелки.
|
||||
Этот алгоритм похож на *транспонирование матрицы* с тем отличием, что здесь для каждой точки
|
||||
одна из координат отображается зеркально.
|
||||
|
||||
Напишем метод на Java для поворота матрицы {`m×n`} на 90 градусов. Дополнительный параметр задаёт
|
||||
направление поворота: по часовой стрелке или против часовой стрелки. Для примера возьмём
|
||||
прямоугольную матрицу {`4×3`}.
|
||||
{%- endcapture %}
|
||||
{%- assign articles = articles | push: article_brief %}
|
||||
{%- assign articles = articles | push: "Оптимизация умножения матриц" %}
|
||||
{%- capture article_brief %}
|
||||
Рассмотрим алгоритм перемножения матриц с использованием трёх вложенных циклов. Сложность такого
|
||||
алгоритма по определению должна составлять `O(n³)`, но есть особенности, связанные со средой
|
||||
выполнения — скорость работы алгоритма зависит от последовательности, в которой выполняются циклы.
|
||||
|
||||
Сравним различные варианты перестановок вложенных циклов и время выполнения алгоритмов.
|
||||
Возьмём две матрицы: {`L×M`} и {`M×N`} → три цикла → шесть перестановок:
|
||||
`LMN`, `LNM`, `MLN`, `MNL`, `NLM`, `NML`.
|
||||
|
||||
Быстрее других отрабатывают те алгоритмы, которые пишут данные в результирующую матрицу *построчно слоями*:
|
||||
`LMN` и `MLN`, — разница в процентах к другим алгоритмам значительная и зависит от среды выполнения.
|
||||
{%- endcapture %}
|
||||
{%- assign articles = articles | push: article_brief %}
|
||||
{%- include main_page.html articles = articles -%}
|
5
package.sh
Executable file
5
package.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
echo "Подготовка архива для последующего развёртывания."
|
||||
cd _site || exit
|
||||
rm -rf ../pomodoro3.zip
|
||||
7z a ../pomodoro3.zip ./*
|
4
serve.sh
Executable file
4
serve.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
echo "Локальное развёртывание для проверки корректности сборки."
|
||||
jekyll serve --skip-initial-build --disable-disk-cache --host localhost
|
||||
echo "Адрес сервера: http://localhost:4000"
|
Loading…
Add table
Reference in a new issue