This document was generated with Documenter.jl version 0.27.25 on Tuesday 29 August 2023. Using Julia version 1.9.3.
+
Settings
This document was generated with Documenter.jl version 0.27.25 on Wednesday 30 August 2023. Using Julia version 1.9.3.
diff --git a/dev/LEQ_src/index.html b/dev/LEQ_src/index.html
index 8617eab..b425a44 100644
--- a/dev/LEQ_src/index.html
+++ b/dev/LEQ_src/index.html
@@ -7333,7 +7333,8 @@ a.anchor-link {
if (!diagrams.length) {
return;
}
- const mermaid = (await import("https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.2.3/mermaid.esm.min.mjs")).default;
+ const mermaid = (await import("https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.3.1/mermaid.esm.min.mjs")).default;
+ const parser = new DOMParser();
mermaid.initialize({
maxTextSize: 100000,
@@ -7349,39 +7350,52 @@ a.anchor-link {
let _nextMermaidId = 0;
function makeMermaidImage(svg) {
- const img = document.createElement('img');
- const maxWidth = svg.match(/max-width: (\d+)/);
- if (maxWidth && maxWidth[1]) {
- const width = parseInt(maxWidth[1]);
- if (width && !Number.isNaN(width) && Number.isFinite(width)) {
- img.width = width;
- }
+ const img = document.createElement("img");
+ const doc = parser.parseFromString(svg, "image/svg+xml");
+ const svgEl = doc.querySelector("svg");
+ const { maxWidth } = svgEl?.style || {};
+ const firstTitle = doc.querySelector("title");
+ const firstDesc = doc.querySelector("desc");
+
+ img.setAttribute("src", `data:image/svg+xml,${encodeURIComponent(svg)}`);
+ if (maxWidth) {
+ img.width = parseInt(maxWidth);
}
- img.setAttribute('src', `data:image/svg+xml,${encodeURIComponent(svg)}`);
- return img;
+ if (firstTitle) {
+ img.setAttribute("alt", firstTitle.textContent);
+ }
+ if (firstDesc) {
+ const caption = document.createElement("figcaption");
+ caption.className = "sr-only";
+ caption.textContent = firstDesc.textContent;
+ return [img, caption];
+ }
+ return [img];
}
async function makeMermaidError(text) {
- let errorMessage = '';
+ let errorMessage = "";
try {
await mermaid.parse(text);
} catch (err) {
errorMessage = `${err}`;
}
- const result = document.createElement('details');
- const summary = document.createElement('summary');
- const pre = document.createElement('pre');
- const code = document.createElement('code');
+ const result = document.createElement("details");
+ result.className = 'jp-RenderedMermaid-Details';
+ const summary = document.createElement("summary");
+ summary.className = 'jp-RenderedMermaid-Summary';
+ const pre = document.createElement("pre");
+ const code = document.createElement("code");
code.innerText = text;
pre.appendChild(code);
summary.appendChild(pre);
result.appendChild(summary);
- const warning = document.createElement('pre');
+ const warning = document.createElement("pre");
warning.innerText = errorMessage;
result.appendChild(warning);
- return result;
+ return [result];
}
async function renderOneMarmaid(src) {
@@ -7391,30 +7405,41 @@ a.anchor-link {
const el = document.createElement("div");
el.style.visibility = "hidden";
document.body.appendChild(el);
- let result = null;
+ let results = null;
+ let output = null;
try {
const { svg } = await mermaid.render(id, raw, el);
- result = makeMermaidImage(svg);
+ results = makeMermaidImage(svg);
+ output = document.createElement("figure");
+ results.map(output.appendChild, output);
} catch (err) {
parent.classList.add("jp-mod-warning");
- result = await makeMermaidError(raw);
+ results = await makeMermaidError(raw);
+ output = results[0];
} finally {
el.remove();
}
parent.classList.add("jp-RenderedMermaid");
- parent.appendChild(result);
+ parent.appendChild(output);
}
void Promise.all([...diagrams].map(renderOneMarmaid));
});
diff --git a/dev/asp.ipynb b/dev/asp.ipynb
index db011b2..b2c7466 100644
--- a/dev/asp.ipynb
+++ b/dev/asp.ipynb
@@ -65,33 +65,22 @@
"source": [
"### Floyd's sequential algoritm\n",
"\n",
- "The ASP problem can be solved with the [Floyd–Warshall algorithm](https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm). A sequential implementation of this algorithm is given in this function."
+ "The ASP problem can be solved with the [Floyd–Warshall algorithm](https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm). A sequential implementation of this algorithm is given in the following function:"
]
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "4fe447c5",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "floyd! (generic function with 1 method)"
- ]
- },
- "execution_count": 1,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"function floyd!(C)\n",
" n = size(C,1)\n",
" @assert size(C,2) == n\n",
" for k in 1:n\n",
- " for i in 1:n\n",
- " for j in 1:n\n",
+ " for j in 1:n\n",
+ " for i in 1:n\n",
" @inbounds C[i,j] = min(C[i,j],C[i,k]+C[k,j])\n",
" end\n",
" end\n",
@@ -110,25 +99,10 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "860e537c",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "4×4 Matrix{Int64}:\n",
- " 0 9 6 1\n",
- " 2 0 8 3\n",
- " 5 3 0 6\n",
- " 10 8 5 0"
- ]
- },
- "execution_count": 2,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"inf = 1000\n",
"C = [\n",
@@ -154,8 +128,8 @@
"```julia\n",
"n = size(C,1)\n",
"for k in 1:n\n",
- " for i in 1:n\n",
- " for j in 1:n\n",
+ " for j in 1:n\n",
+ " for i in 1:n\n",
" C[i,j] = min(C[i,j],C[i,k]+C[k,j])\n",
" end\n",
" end\n",
@@ -248,6 +222,69 @@
"
"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "c7027ac3",
+ "metadata": {},
+ "source": [
+ "### Serial performance\n",
+ "\n",
+ "This algorithm is memory bound, meaning that the main cost is in getting and setting data from the input matrix `C`. In this situations, the order in which we traverse the entries of matrix `C` has a significant performance impact.\n",
+ "\n",
+ "The following function computes the same result as for the previous function `floyd!`, but the nesting of loops over i and j is changed.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "75cac17e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "function floyd2!(C)\n",
+ " n = size(C,1)\n",
+ " @assert size(C,2) == n\n",
+ " for k in 1:n\n",
+ " for i in 1:n\n",
+ " for j in 1:n\n",
+ " @inbounds C[i,j] = min(C[i,j],C[i,k]+C[k,j])\n",
+ " end\n",
+ " end\n",
+ " end\n",
+ " C\n",
+ "end"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "399385e8",
+ "metadata": {},
+ "source": [
+ " Compare the performance of both implementations (run the cell several times)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "907bc8c9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "n = 1000\n",
+ "C = rand(n,n)\n",
+ "@time floyd!(C)\n",
+ "C = rand(n,n)\n",
+ "@time floyd2!(C);"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ad811b10",
+ "metadata": {},
+ "source": [
+ "The performance difference is significant. Matrices in Julia are stored in memory in column-major order (like in Fortran, unlike in C). It means that it is more efficient to access the data also in column-major order (like in function `floyd!`). See this section of [Julia's performance tips](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-column-major) if you are interested in further details."
+ ]
+ },
{
"cell_type": "markdown",
"id": "0c95ea88",
@@ -264,8 +301,8 @@
"```julia\n",
"n = size(C,1)\n",
"for k in 1:n\n",
- " for i in 1:n\n",
- " for j in 1:n\n",
+ " for j in 1:n\n",
+ " for i in 1:n\n",
" C[i,j] = min(C[i,j],C[i,k]+C[k,j])\n",
" end\n",
" end\n",
@@ -401,8 +438,12 @@
"source": [
"- Each process updates $N^2/P$ entries per iteration\n",
"- 1 process broadcasts a message of length $N$ to $P-1$ processes per iteration\n",
+ "- The send cost in this process is $O(N P)$ per iteration (if we use send/receive instead of broadcast)\n",
"- $P-1$ processes receive one message of length $N$ per iteration\n",
- "- The receive/computation ration is $O(P/N)$ which would be small if $P< threshold\n",
- " C[i,j] = rand(mincost:maxcost)\n",
- " end\n",
- " end\n",
- " C[j,j] = 0\n",
- " end\n",
- " C\n",
- "end"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "3116096c",
- "metadata": {},
- "outputs": [],
- "source": [
- "rand_distance_table(10)"
+ "## Parallel Implementation\n"
]
},
{
@@ -511,7 +509,9 @@
"id": "680e56cf",
"metadata": {},
"source": [
- "### Code"
+ "### Code\n",
+ "\n",
+ "We split the code in two functions. The first function is called on the main process (the process running this notebook). It splits the input matrix into blocks of rows. Then, we call `floyd_worker!` (see below) remotely on each worker using the corresponding block of rows.\n"
]
},
{
@@ -534,6 +534,14 @@
"end"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "9fc3be11",
+ "metadata": {},
+ "source": [
+ "The second function is the one run on the workers. Note that we considered MPI for communication in this case."
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -560,8 +568,8 @@
" else\n",
" MPI.Recv!(C_k,comm,source=MPI.ANY_SOURCE,tag=0)\n",
" end\n",
- " for i in 1:m\n",
- " for j in 1:n\n",
+ " for j in 1:n\n",
+ " for i in 1:m\n",
" @inbounds Cw[i,j] = min(Cw[i,j],Cw[i,k]+C_k[j])\n",
" end\n",
" end\n",
@@ -570,6 +578,39 @@
"end"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "c624722a",
+ "metadata": {},
+ "source": [
+ "### Testing the implementation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "09937668",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "function rand_distance_table(n)\n",
+ " threshold = 0.4\n",
+ " mincost = 3\n",
+ " maxcost = 10\n",
+ " infinity = 10000*maxcost\n",
+ " C = fill(infinity,n,n)\n",
+ " for j in 1:n\n",
+ " for i in 1:n\n",
+ " if rand() > threshold\n",
+ " C[i,j] = rand(mincost:maxcost)\n",
+ " end\n",
+ " end\n",
+ " C[j,j] = 0\n",
+ " end\n",
+ " C\n",
+ "end"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -654,21 +695,25 @@
"- Use synchronous send MPI_SSEND (less efficient). Note that the blocking send MPI_SEND used above does not guarantee that the message was received.\n",
"- Barrier at the end of each iteration over $k$ (simple solution, but synchronization overhead)\n",
"- Order incoming messages (buffering and extra user code needed)\n",
- "- Use a specific rank id instead of `MPI.ANY_SOURCE` (one needs to know which are the rows owned by the other ranks)"
+ "- Use a specific rank id instead of `MPI.ANY_SOURCE` or use `MPI.Bcast!` (one needs to know which are the rows owned by the other ranks)"
]
},
{
- "cell_type": "code",
- "execution_count": null,
- "id": "db2b586f",
+ "cell_type": "markdown",
+ "id": "c789dc7a",
"metadata": {},
- "outputs": [],
- "source": []
+ "source": [
+ "# License\n",
+ "\n",
+ "\n",
+ "\n",
+ "This notebook is part of the course [Programming Large Scale Parallel Systems](https://www.francescverdugo.com/XM_40017) at Vrije Universiteit Amsterdam and may be used under a [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) license."
+ ]
}
],
"metadata": {
"kernelspec": {
- "display_name": "Julia 1.9.1",
+ "display_name": "Julia 1.9.0",
"language": "julia",
"name": "julia-1.9"
},
@@ -676,7 +721,7 @@
"file_extension": ".jl",
"mimetype": "application/julia",
"name": "julia",
- "version": "1.9.1"
+ "version": "1.9.0"
}
},
"nbformat": 4,
diff --git a/dev/asp/index.html b/dev/asp/index.html
index 4c3f7c2..65da9e5 100644
--- a/dev/asp/index.html
+++ b/dev/asp/index.html
@@ -1,5 +1,5 @@
-- · XM_40017
This algorithm is memory bound, meaning that the main cost is in getting and setting data from the input matrix C. In this situations, the order in which we traverse the entries of matrix C has a significant performance impact.
+
The following function computes the same result as for the previous function floyd!, but the nesting of loops over i and j is changed.
The performance difference is significant. Matrices in Julia are stored in memory in column-major order (like in Fortran, unlike in C). It means that it is more efficient to access the data also in column-major order (like in function floyd!). See this section of Julia's performance tips if you are interested in further details.
We split the code in two functions. The first function is called on the main process (the process running this notebook). It splits the input matrix into blocks of rows. Then, we call floyd_worker! (see below) remotely on each worker using the corresponding block of rows.
@@ -8010,6 +8049,17 @@ a.anchor-link {
+
+
+
+
+
+
+
+
The second function is the one run on the workers. Note that we considered MPI for communication in this case.
The programming of this course will be done using the Julia programming language. Thus, we start by explaining how to get up and running with Julia. After studying this page, you will be able to:
Courses related with high-performance computing (HPC) often use languages such as C, C++, or Fortran. We use Julia instead to make the course accessible to a wider set of students, including the ones that have no experience with C/C++ or Fortran, but are willing to learn parallel programming. Julia is a relatively new programming language specifically designed for scientific computing. It combines a high-level syntax close to interpreted languages like Python with the performance of compiled languages like C, C++, or Fortran. Thus, Julia will allow us to write efficient parallel algorithms with a syntax that is convenient in a teaching setting. In addition, Julia provides easy access to different programming models to write distributed algorithms, which will be useful to learn and experiment with them.
Tip
You can run the code in this link to learn how Julia compares to other languages (C and Python) in terms of performance.
There are several ways of opening Julia depending on your operating system and your IDE, but it is usually as simple as launching the Julia app. With VSCode, open a folder (File > Open Folder). Then, press Ctrl+Shift+P to open the command bar, and execute Julia: Start REPL. If this does not work, make sure you have the Julia extension for VSCode installed. Independently of the method you use, opening Julia results in a window with some text ending with:
julia>
You have just opened the Julia read-evaluate-print loop, or simply the Julia REPL. Congrats! You will spend most of time using the REPL, when working in Julia. The REPL is a console waiting for user input. Just as in other consoles, the string of text right before the input area (julia> in the case) is called the command prompt or simply the prompt.
Curious about what the function println does? Enter into help mode to look into the documentation. This is done by typing a question mark (?) into the input field:
julia> ?
After typing ?, the command prompt changes to help?>. It means we are in help mode. Now, we can type a function name to see its documentation.
The REPL comes with two more modes, namely package and shell modes. To enter package mode type
julia> ]
Package mode is used to install and manage packages. We are going to discuss the package mode in greater detail later. To return back to normal mode press the backspace key several times.
To enter shell mode type semicolon (;)
julia> ;
The prompt should have changed to shell> indicating that we are in shell mode. Now you can type commands that you would normally do on your system command line. For instance,
shell> ls
will display the contents of the current folder in Mac or Linux. Using shell mode in Windows is not straightforward, and thus not recommended for beginners.
Real-world Julia programs are not typed in the REPL in practice. They are written in one or more files and included in the REPL. To try this, create a new file called hello.jl, write the code of the "Hello world" example above, and save it. If you are using VSCode, you can create the file using File > New File > Julia File. Once the file is saved with the name hello.jl, execute it as follows
julia> include("hello.jl")
Warning
Make sure that the file "hello.jl" is located in the current working directory of your Julia session. You can query the current directory with function pwd(). You can change to another directory with function cd() if needed. Also, make sure that the file extension is .jl.
The recommended way of running Julia code is using the REPL as we did. But it is also possible to run code directly from the system command line. To this end, open a terminal and call Julia followed by the path to the file containing the code you want to execute.
$ julia hello.jl
The previous line assumes that you have Julia properly installed in the system and that it's usable from the terminal. In UNIX systems (Linux and Mac), the Julia binary needs to be in one of the directories listed in the PATH environment variable. To check that Julia is properly installed, you can use
$ julia --version
If this runs without error and you see a version number, you are good to go!
Note
In this tutorial, when a code snipped starts with $, it should be run in the terminal. Otherwise, the code is to be run in the Julia REPL.
Tip
Avoid calling Julia code from the terminal, use the Julia REPL instead! Each time you call Julia from the terminal, you start a fresh Julia session and Julia will need to compile your code from scratch. This can be time consuming for large projects. In contrast, if you execute code in the REPL, Julia will compile code incrementally, which is much faster. Running code in a cluster (like in DAS-5 for the Julia assignment) is among the few situations you need to run Julia code from the terminal.
The programming of this course will be done using the Julia programming language. Thus, we start by explaining how to get up and running with Julia. After studying this page, you will be able to:
Courses related with high-performance computing (HPC) often use languages such as C, C++, or Fortran. We use Julia instead to make the course accessible to a wider set of students, including the ones that have no experience with C/C++ or Fortran, but are willing to learn parallel programming. Julia is a relatively new programming language specifically designed for scientific computing. It combines a high-level syntax close to interpreted languages like Python with the performance of compiled languages like C, C++, or Fortran. Thus, Julia will allow us to write efficient parallel algorithms with a syntax that is convenient in a teaching setting. In addition, Julia provides easy access to different programming models to write distributed algorithms, which will be useful to learn and experiment with them.
Tip
You can run the code in this link to learn how Julia compares to other languages (C and Python) in terms of performance.
There are several ways of opening Julia depending on your operating system and your IDE, but it is usually as simple as launching the Julia app. With VSCode, open a folder (File > Open Folder). Then, press Ctrl+Shift+P to open the command bar, and execute Julia: Start REPL. If this does not work, make sure you have the Julia extension for VSCode installed. Independently of the method you use, opening Julia results in a window with some text ending with:
julia>
You have just opened the Julia read-evaluate-print loop, or simply the Julia REPL. Congrats! You will spend most of time using the REPL, when working in Julia. The REPL is a console waiting for user input. Just as in other consoles, the string of text right before the input area (julia> in the case) is called the command prompt or simply the prompt.
Curious about what the function println does? Enter into help mode to look into the documentation. This is done by typing a question mark (?) into the input field:
julia> ?
After typing ?, the command prompt changes to help?>. It means we are in help mode. Now, we can type a function name to see its documentation.
The REPL comes with two more modes, namely package and shell modes. To enter package mode type
julia> ]
Package mode is used to install and manage packages. We are going to discuss the package mode in greater detail later. To return back to normal mode press the backspace key several times.
To enter shell mode type semicolon (;)
julia> ;
The prompt should have changed to shell> indicating that we are in shell mode. Now you can type commands that you would normally do on your system command line. For instance,
shell> ls
will display the contents of the current folder in Mac or Linux. Using shell mode in Windows is not straightforward, and thus not recommended for beginners.
Real-world Julia programs are not typed in the REPL in practice. They are written in one or more files and included in the REPL. To try this, create a new file called hello.jl, write the code of the "Hello world" example above, and save it. If you are using VSCode, you can create the file using File > New File > Julia File. Once the file is saved with the name hello.jl, execute it as follows
julia> include("hello.jl")
Warning
Make sure that the file "hello.jl" is located in the current working directory of your Julia session. You can query the current directory with function pwd(). You can change to another directory with function cd() if needed. Also, make sure that the file extension is .jl.
The recommended way of running Julia code is using the REPL as we did. But it is also possible to run code directly from the system command line. To this end, open a terminal and call Julia followed by the path to the file containing the code you want to execute.
$ julia hello.jl
The previous line assumes that you have Julia properly installed in the system and that it's usable from the terminal. In UNIX systems (Linux and Mac), the Julia binary needs to be in one of the directories listed in the PATH environment variable. To check that Julia is properly installed, you can use
$ julia --version
If this runs without error and you see a version number, you are good to go!
Note
In this tutorial, when a code snipped starts with $, it should be run in the terminal. Otherwise, the code is to be run in the Julia REPL.
Tip
Avoid calling Julia code from the terminal, use the Julia REPL instead! Each time you call Julia from the terminal, you start a fresh Julia session and Julia will need to compile your code from scratch. This can be time consuming for large projects. In contrast, if you execute code in the REPL, Julia will compile code incrementally, which is much faster. Running code in a cluster (like in DAS-5 for the Julia assignment) is among the few situations you need to run Julia code from the terminal.
Since we are in a parallel computing course, let's run a parallel "Hello world" example in Julia. Open a Julia REPL and write
julia> using Distributed
julia> @everywhere println("Hello, world! I am proc $(myid()) from $(nprocs())")
Here, we are using the Distributed package, which is part of the Julia standard library that provides distributed memory parallel support. The code prints the process id and the number of processes in the current Julia session.
You will probably only see output from 1 process. We need to add more processes to run the example in parallel. This is done with the addprocs function.
julia> addprocs(3)
We have added 3 new processes. Plus the old one, we have 4 processes. Run the code again.
julia> @everywhere println("Hello, world! I am proc $(myid()) from $(nprocs())")
Now, you should see output from 4 processes.
It is possible to specify the number of processes when starting Julia from the terminal with the -p argument (useful, e.g., when running in a cluster). If you launch Julia from the terminal as
$ julia -p 3
and then run
julia> @everywhere println("Hello, world! I am proc $(myid()) from $(nprocs())")
One of the most useful features of Julia is its package manager. It allows one to install Julia packages in a straightforward and platform independent way. To illustrate this, let us consider the following parallel "Hello world" example. This example uses the Message Passing Interface (MPI). We will learn more about MPI later in the course.
Copy the following block of code into a new file named "hello_mpi.jl"
You should get an error or a
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195"
Copy the contents of previous code block into a file called Project.toml and place it in an empty folder named newproject. It is important that the file is named Project.toml. You can create a new folder from the REPL with
julia> mkdir("newproject")
To install all the packages registered in this file you need to activate the folder containing your Project.toml file
(@v1.8) pkg> activate newproject
and then instantiating it
(newproject) pkg> instantiate
The instantiate command will download and install all listed packages and their dependencies in just one click.
In some situations it is required to use package commands in Julia code, e.g., to automatize installation and deployment of Julia applications. This can be done using the Pkg package. For instance
We have learned the basics of how to work with Julia. If you want to further dig into the topics we have covered here, you can take a look at the following links:
We have learned the basics of how to work with Julia. If you want to further dig into the topics we have covered here, you can take a look at the following links:
This page contains part of the course material of the Programming Large-Scale Parallel Systems course at VU Amsterdam. We provide several lecture notes in jupyter notebook format, which will help you to learn how to design, analyze, and program parallel algorithms on multi-node computing systems. Further information about the course is found in the study guide (click here) and our Canvas page (for registered students).
Note
Material will be added incrementally to the website as the course advances.
Warning
This page will eventually contain only a part of the course material. The rest will be available on Canvas. In particular, the material in this public webpage does not fully cover all topics in the final exam.
Download the notebooks and run them locally on your computer (recommended). At each notebook page you will find a green box with links to download the notebook.
You also have the static version of the notebooks displayed in this webpage for quick reference.
This page contains part of the course material of the Programming Large-Scale Parallel Systems course at VU Amsterdam. We provide several lecture notes in jupyter notebook format, which will help you to learn how to design, analyze, and program parallel algorithms on multi-node computing systems. Further information about the course is found in the study guide (click here) and our Canvas page (for registered students).
Note
Material will be added incrementally to the website as the course advances.
Warning
This page will eventually contain only a part of the course material. The rest will be available on Canvas. In particular, the material in this public webpage does not fully cover all topics in the final exam.
Download the notebooks and run them locally on your computer (recommended). At each notebook page you will find a green box with links to download the notebook.
You also have the static version of the notebooks displayed in this webpage for quick reference.
This page was created with the support of the Faculty of Science of Vrije Universiteit Amsterdam in the framework of the project "Interactive lecture notes and exercises for the Programming Large-Scale Parallel Systems course" funded by the "Innovation budget BETA 2023 Studievoorschotmiddelen (SVM) towards Activated Blended Learning".
Settings
This document was generated with Documenter.jl version 0.27.25 on Tuesday 29 August 2023. Using Julia version 1.9.3.
+julia> notebook()
These commands will open a jupyter in your web browser. Navigate in jupyter to the notebook file you have downloaded and open it.
This page was created with the support of the Faculty of Science of Vrije Universiteit Amsterdam in the framework of the project "Interactive lecture notes and exercises for the Programming Large-Scale Parallel Systems course" funded by the "Innovation budget BETA 2023 Studievoorschotmiddelen (SVM) towards Activated Blended Learning".
Settings
This document was generated with Documenter.jl version 0.27.25 on Wednesday 30 August 2023. Using Julia version 1.9.3.
This document was generated with Documenter.jl version 0.27.25 on Tuesday 29 August 2023. Using Julia version 1.9.3.
+
Settings
This document was generated with Documenter.jl version 0.27.25 on Wednesday 30 August 2023. Using Julia version 1.9.3.
diff --git a/dev/jacobi_2D_src/index.html b/dev/jacobi_2D_src/index.html
index 67081a3..9d258b0 100644
--- a/dev/jacobi_2D_src/index.html
+++ b/dev/jacobi_2D_src/index.html
@@ -7333,7 +7333,8 @@ a.anchor-link {
if (!diagrams.length) {
return;
}
- const mermaid = (await import("https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.2.3/mermaid.esm.min.mjs")).default;
+ const mermaid = (await import("https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.3.1/mermaid.esm.min.mjs")).default;
+ const parser = new DOMParser();
mermaid.initialize({
maxTextSize: 100000,
@@ -7349,39 +7350,52 @@ a.anchor-link {
let _nextMermaidId = 0;
function makeMermaidImage(svg) {
- const img = document.createElement('img');
- const maxWidth = svg.match(/max-width: (\d+)/);
- if (maxWidth && maxWidth[1]) {
- const width = parseInt(maxWidth[1]);
- if (width && !Number.isNaN(width) && Number.isFinite(width)) {
- img.width = width;
- }
+ const img = document.createElement("img");
+ const doc = parser.parseFromString(svg, "image/svg+xml");
+ const svgEl = doc.querySelector("svg");
+ const { maxWidth } = svgEl?.style || {};
+ const firstTitle = doc.querySelector("title");
+ const firstDesc = doc.querySelector("desc");
+
+ img.setAttribute("src", `data:image/svg+xml,${encodeURIComponent(svg)}`);
+ if (maxWidth) {
+ img.width = parseInt(maxWidth);
}
- img.setAttribute('src', `data:image/svg+xml,${encodeURIComponent(svg)}`);
- return img;
+ if (firstTitle) {
+ img.setAttribute("alt", firstTitle.textContent);
+ }
+ if (firstDesc) {
+ const caption = document.createElement("figcaption");
+ caption.className = "sr-only";
+ caption.textContent = firstDesc.textContent;
+ return [img, caption];
+ }
+ return [img];
}
async function makeMermaidError(text) {
- let errorMessage = '';
+ let errorMessage = "";
try {
await mermaid.parse(text);
} catch (err) {
errorMessage = `${err}`;
}
- const result = document.createElement('details');
- const summary = document.createElement('summary');
- const pre = document.createElement('pre');
- const code = document.createElement('code');
+ const result = document.createElement("details");
+ result.className = 'jp-RenderedMermaid-Details';
+ const summary = document.createElement("summary");
+ summary.className = 'jp-RenderedMermaid-Summary';
+ const pre = document.createElement("pre");
+ const code = document.createElement("code");
code.innerText = text;
pre.appendChild(code);
summary.appendChild(pre);
result.appendChild(summary);
- const warning = document.createElement('pre');
+ const warning = document.createElement("pre");
warning.innerText = errorMessage;
result.appendChild(warning);
- return result;
+ return [result];
}
async function renderOneMarmaid(src) {
@@ -7391,30 +7405,41 @@ a.anchor-link {
const el = document.createElement("div");
el.style.visibility = "hidden";
document.body.appendChild(el);
- let result = null;
+ let results = null;
+ let output = null;
try {
const { svg } = await mermaid.render(id, raw, el);
- result = makeMermaidImage(svg);
+ results = makeMermaidImage(svg);
+ output = document.createElement("figure");
+ results.map(output.appendChild, output);
} catch (err) {
parent.classList.add("jp-mod-warning");
- result = await makeMermaidError(raw);
+ results = await makeMermaidError(raw);
+ output = results[0];
} finally {
el.remove();
}
parent.classList.add("jp-RenderedMermaid");
- parent.appendChild(result);
+ parent.appendChild(output);
}
void Promise.all([...diagrams].map(renderOneMarmaid));
});
diff --git a/dev/jacobi_method.ipynb b/dev/jacobi_method.ipynb
index 5db4dff..2b68002 100644
--- a/dev/jacobi_method.ipynb
+++ b/dev/jacobi_method.ipynb
@@ -62,8 +62,7 @@
"gauss_seidel_1_check(answer) = answer_checker(answer,\"c\")\n",
"jacobi_1_check(answer) = answer_checker(answer, \"d\")\n",
"jacobi_2_check(answer) = answer_checker(answer, \"b\")\n",
- "jacobi_3_check(answer) = answer_checker(answer, \"c\")\n",
- "jacobi_4_check(anwswer) = answer_checker(answer, \"d\")"
+ "jacobi_3_check(answer) = answer_checker(answer, \"c\")"
]
},
{
@@ -332,13 +331,333 @@
"id": "513f1f7e",
"metadata": {},
"source": [
- "### Efficiency\n",
+ "### Communication overhead\n",
"- We update $N/P$ entries in each process at each iteration, where $N$ is the total length of the vector and $P$ the number of processes\n",
"- We need to get remote entries from 2 neighbors (2 messages per iteration)\n",
"- We need to communicate 1 entry per message\n",
"- Communication/computation ration is $O(P/N)$ (potentially scalable if $P<\n",
+ "\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0148f9b3",
+ "metadata": {},
+ "source": [
+ "Thus, the algorithm is usually implemented following two main phases at each iteration Jacobi:\n",
+ "\n",
+ "1. Fill the ghost entries with communications\n",
+ "2. Do the Jacobi update sequentially at each process"
+ ]
+ },
+ {
+ "attachments": {
+ "fig15.png": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAA6gAAAFACAYAAACxyVHuAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAewgAAHsIBbtB1PgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURBVHic7N15fFT19f/x12cmIQFkE1wANxBFrTu4b1itimQC2mKrtfrtRtXazbbWrmJba+1m7S5fv9Wqrbb5VWBugFatpe4buGvdFVcU2cISSOZ+fn+cO2QyTCAsmXszeT8fj/vIvXPv3Dm5M3PnnvvZ3DXXXOMRERERERERiVkq7gBEREREREREQAmqiIiIiIiIJERV0fLJzrlX4whEREREREREehbv/W7AP/PL7RJU59yrU6ZMeb7cQYmIiIiIiEjPM23aNLxv6xZJVXxFREREREQkEZSgioiIiIiISCIoQRUREREREZFEUIIqIiIiIiIiiaAEVURERERERBJBCaqIiIiIiIgkghJUERERERERSQQlqCIiIiIiIpIISlBFREREREQkEZSgioiIiIiISCIoQRUREREREZFEUIIqIiIiIiIiiaAEVURERERERBKhKu4ARDbGe+8aGxsvizuODbnuuusOWLhw4SCAhQsXvvbiiy++EndMRXYBRkbzy4H5McZSSn/g4ILlu4FcTLGUdPjhhx+dTqerAE455ZSnDjzwwEVxx9QR59yDdXV1s8rxWtls9nPOueHleK3NMXv27BFPPPHErgArV65c8thjjz0ed0xFBgEHFCzPjSmODRlXMP84sCSmODpyAHYcARYAL8cYy3pGjBixy7Bhw0YCbLvttsunTJmStPNvO+l0+vJTTz11TVe/ThAEewFndfXrbK7m5ub0L3/5y2Pyy88+++z8xYsXL48zphIOxn4/wT73C2KMpZQRwK7R/FLgsRhjWc/gwYMH7rXXXgfmly+55JK5MYazUWEY3jBx4sQX446jHJSgSuJddtllbsyYMd+NO44NeeGFF3jyySfzi8dsaNuEmBB3ABuRuGP4wAMPrJsfN25c4uIr5L3/LVCWBBX4DDC2TK+1yd577z3uvffewofq44qlkxL92SL58SXOK6+8wiuv2D3LUaNGQcLPv9XV1T8HujxB9d6Pds4l+re96NyR9M9+0uMDyMQdQKH333+/3XschuExqVRyK5em0+l7gR6RoCb3XRAREREREZEeRSWo0h09jlUVSYwwDI8i+j717j1w5erVPBdzSEVW7AGt/Ww+HUK/RFWzgTVDYPUubcsDnwRaYgunBOeWHey9B2DNmjULgKRV494XGBxzDK+QsCpma9euPYioClxNTZ/cmjW9ElbFt3lHaB7WtjwwgdU/lxZUv+/7JlQvjC+WUpYdAD5t871WQp9EnX/79HGjVq1a0h/Ae58D7ok5pGL9aN/EIg4h1rQjMcIw7AUckV+uqdl2wZo1YcKadiw7EHxU2FTTBL1fiDeeYk17Qm4bm0/noF+izr+1tentm5vf3ym/7L2/C/AxhlTKcXEHEAclqNLteO8vqq+vvzPuOAqNGjVqCTAQYOTII196+ulZ58ccUpHBv4bFh9p87RpYkrD4jq+Hud9uW/7zxXDq4vjiWV91de8H165tBuCpp576y89+9rNvxhxSO42NjTO89xNjDuN/M5nMFTHH0M6RRx55P3A4wHbbjVj1xhtPJeyzP+YTMP/CtuWkfTcB3INt80dOh9tujC+WUqpvh9aoHd6Or8BriTqGu+/+4auffPLWwwFaWlqaM5nMuJhDaiebzR7inHso5jASd1zOPPPMHYB38sujR3/xxieeuDQbY0glVM2FXG+b3+m/8OKFG9y87AZNg6VRG/u+q5N2fttrr0vOfOyxK7+cX/7zn//8oYaGhrVxxlQsCIIcPbDGa4/7h0VERERERCSZlKCKiIiIiIhIIihBFRERERERkURQgioiIiIiIiKJoARVREREREREEkEJqoiIiIiIiCSChpmRnqwa2A/YPVp+Bng6vnBERERERHo2JajS0+wBXASMAfYHagrWfR+4NI6gRERERKTbGhlNAK8Dz8UYS7enBFV6mn2B8wqWffTXxRCLiIiIiHQ/w4AvYAUeY4FBBeuuBr4cR1CVQgmq9DSLgZuBedE0H3iV9icWEREREZGO7AVcEncQlUoJqvQ0/4kmEREREZHNsRKYQ1uBxzzgLmC3GGOqGEpQRUREREREOu9B4NS4g6hUGmZGREREREREEkEJqoiIiIiIiCSCElQRERERERFJBCWoIiIiIiIikghKUEVERERERCQRlKBKpZiHjXFaPP0tzqBERERERKTzNMxM5doD+EfB8geA5o08pwZ4pmB5PPD8Vo6rqwwABpV4fJtyByIiIiIiIptHCWrl6gWMLFh2nXiOK3pOr60aUdcaQ+kaAS3lDkRERERERDaPElSpFMviDkBERERERLaM2qCKiIiIiIhIIqgEVXqig2h/cyYd/R2GVRXOewd4s1xBiYiIiIj0dEpQpSe6B+hT4vHPRFPelcAlZYlIRERERLqTXWkr5IC2vGoA7ft0WQ4sKldQlUAJqvRErwC9O7Hd4q4ORERERES6pQeAHUs8/j/RlPcH4PwyxFMxlKBKT7Rv3AGIiIiISLe2DBuicWNWdXUglUYJqoiIiIiIyKbZK+4AKpV68RUREREREZFEUIIqIiIiIiIiiaAEVQp1ph69iIiIiIhIl1CCWrlWFy13ptfaUj2RiYiIiIiIlIUS1Mq1rGh5eCeec0RXBCIiIiIiItIZSlAr1/u0HxT46I1s74DPdV04IiIiIiIiG6YEtbLdVzD/eaB6A9teBBzeteGIiIiIiIh0TAlqZftTwfwHgFuAIUXbDAGuBn4KLC1TXCIiIiIiIuupijsA6VIzgLuAY6Pl04EJwCNYG9XtgQOxz0ELcBYwu/xhbhrn3KFBENTGHUehr33ta+u+S8uXv90PPnZknPGsb+3AtvlcKnnxvT6q/fLvDoEbmuKJpbQwDNfNb7/99iODIDg1xnDW473fIe4YgNFJOy5XXXXVus9+c3NTOnmf/UW7tl9OWnzFXt81eTH6gmuZVYk7/y5b9vqg/HwqlUon7TsC7BF3AEDijsvChQsH3nzzzeuWFy26f1TSPlvgCwqaVg5IXnxr+7fNtybu/Lto0ZIRhcuTJ08+5ZxzzmmNKx5powS1soXAGcA/gQOix2qAo4q2WwKcC/y7fKFtkSviDqBYr1691s2//vqjI+DRq2IMZyOaa+CvCY4PYNb3446gWGvBT9bOO+98BvbdkvbOjabE2HbbbdfNL1q0oA8sSPhnP+nfzf9mbEqqRbsm7RguWNA2X1VVVQvMii2Y5KohYcdlwIAB7ZbfeuufHwU+Gk80nfHOnkn77Le3qnfS4nvjjfbLNTU1M+OJRIopQa18C4HDsDaoZwIHY1W7Q2AB8Dfgt9F8NTCt4LmLyxqpiIiIiIj0aEpQe4Y1wC+iKQUMBJqwar2FWkhgT76XXnqpb2xsXBN3HBuSTqerq6urUwC5XC4XhmHSqoikafu+h6z/3sfNAb0KlhP3fldXV/fC4gQ7fuEGNo+V975s728qlVrjvU/c+5WXSqWqqqur0wDe+7C1tTVpn/0U7TuwS+KxrCmYT+Jnv5q2PjVyQKLOv6lUKp1Op6sAqqqqknj+baelpcWX6aVyJPPzvk51dfW6z35ra+ta7325jk1nFX72W7FjmiSJvvZwzqWqqqqqC5YT/Xkkee9vl3HXXHPNui+bc270lClTno8zIBEREREREekZpk2btqf3/rn8snrxFRERERERkURQgioiIiIiIiKJoARVRCqZ2/gmIiLr6JwhIhIzJagiUomqgG8Ac9B5TrrGsLgDkK3uKOBRYP+4A5GKNAjoHXcQIt2BLtxEpBLtBnwXOBn4WryhSAUaDTwP3AD0jTkW2XouwcYMvwmojTkWqSwO+1w9jg39JyIboARVRCrRi8AXo/kfAofHGItUlhrgZiwx3QlYHW84shV9Cngb2A/4acyxSGX5CnAqsDM6Z4hslBJUEalUfwRuwcaJuwVVyZSt4yfAQcBC4CySNyaobL73gHOw9/TzwP/EGo1UijHAFdH8F4EnYoxFpFtQgioilew84FlgV2A2MCDecKSbm4JdYHrgM8A78YYjXeAO4AdYlcxpwPh4w5FubjgwHegF/B3433jDEekelKCKSCVbhl1gvoW1LbsVq6IpsqkmAr+L5r8LNMYYi3Sty4DrsNoXfwMOiTcc6aa2BW7DqvU+AXw63nBEug8lqCJS6V7DOktaCnwQK0ntF2tE0t0cg7U7TQN/AC6PNxzpYh74LDAT2AZLMo6ONSLpbvpin599gAVY+9NlsUYk0o0oQRWRnuApYBKwAktSb8PubotszAnYTY3eWKnpF+INR8okB5wN3A0MBP6JqvtK5wyi7abGMiADvBlrRCLdjBJUEekp/oMlp+9jvfreBewea0SSdGdiY+lugyWnZwCtsUYk5bQCq33RCPTBSsQ+GWtEknTDsN+WI4FFwIdQp0gim0wJqoj0JA9j1TVfBz4AzAc+EmtEkkQO+DY2bmF19Pd0NDxET7Qaq32Rb5P6R2z82z5xBiWJNBa4H9gXeAM4FvvNEZFNpARVRHqaZ4EjgHuB/lgnKFdivSyKbAsE2Pi5KeBq4FygJc6gJFY5rIOb70TznwDuAfaIMyhJlPOwz8QuwH+x6r3PxhqRSDemBFVEeqI3gXFYYgpwMVaaenhcAUkiHAM8AkzASs4+CXwZjXUq1nHS5Vib5HewsXAfB6ZiJavSM20L/Bn4PdZD/N+Bw7DO+URkMylBFZGeqhW4BPgwsBCr8nsv8Es0XmpPMwArKZ0LjABexG5WXB9fSJJQ/8Gqct6GdZx1KfBA9Jj0LBmsfelZtP2eTAaWxxmUSCVQgioiPd10YC9gGtb28EvAy8A3gNoY45Ku57COkJ4Bvhgt/wEYgzo2kY69iXWedAbWEc7BwENYc4FRMcYl5TEa6zgrCwwHngSOwmrk+BjjEqkYSlBFRGyM1M8BJwFPY9W2foy1Ifo0VnVLKsuJWHXev2A9b74UPXY+KgGRzmnAal78CUtMJmPnj98Au8UXlnSR7bCaFk9hzQBasKT0EOwGhYhsJUpQRUTa3AHsj5WMvIpdZF6LDbQ+FY2d2t05rOTrTuB2rORrGfAtrOfNO+MLTbqpd4H/AfbDEtZewOexGx4B1h5RurddgJ8Dr2A1LaqAGdh7fgmwJr7QRCqTElQRkfZC7EJzH+Cr2JA022NtzV7DEtYjYotONkcN1vPqo8A/gOOxi8qrsLFwrwCaY4tOKsEz2I2tcVj71BRQh7VPvRtLYvvGFJtsnoOAG7E26Rdh7999WA+9pwHPxReaSGVTgioiUtpq4BdYAnMO1mPnNliV3/uwqnwXY53qSDJ9AHsP38DGrjwAaMIS0z2xi873Y4tOKtF/sFL6A7Hxc1uwhOY64C2srfsHgXRcAcoGDcCaezyE9ex+NtZL8x3AKVhb03tji06kh1CCKiKyYS3YXfQDsWFIrgdWYiWsV2IdKj0CfDN6TOK1G/A14EGsrdhXgCFYSfglWHW9i7Bq2yJd5XGs1H5X7HP3PDbu8meBfwFvY8nqyagztrj1wXpz/wt2E+EPWLvSNdgQMgcDHwL+GVeAIj1NVdwBiIh0I/dE0xex6nwfw6r0jYmmH2FtV+dE039QhztdzWE3D8Zj1e4Kh/tYi7UDvBardqnxTKXc3sZuZP0Eu8H1cexzuh2WrH4WWIW1f56NtY1+MZZIe5btsZsDGeBU2le/fgo7Z9yEaliIxEIJqojIpmsC/i+ahgCTsDvw47ASvPOjKYcNV3I3bcnt22WPtvLshF3sn4glpkML1uWw8Uz/H/B34L1yBydSggfuiqYLsHPFR7B2qjtFf+uibd+m/TnjCexzLZuvD9Zh1TjsnDGG9rUIX8bOGQ1YjRgRiZESVBGRLbMIu9t+LXYRNA67ADoJa+d4UDR9Mdr+Jeyi827gYaxzldayRty9OKzq9NHRdAxWbbLQCqyN2Gysd00lpZJkOaya77+wG1n7Y+0bxwOHYzdczogmsFoY92FtH+/D2kYuLW/I3c4QrL3oMdHfMVhb0jwPzMM6TZsezYtIQihBFRHZelZhSdLsaHkH4FDsAulorF3T7tF0brRNC/ACdoGUn+ZH++qJdqAtqT8cO3aDi7ZpxY7T3dgF5t1YdV6R7uiJaPoJdl12AHa+OArrUGkwlsCeUvCct2l/zniEnls7owYb8uVgrIr/UcDe2M2tQguwc8UdWBOMhWWMUUQ2gRJUEZGusxBrAxlEy/2AI2lLWA8CBmIlhPtgnaqAJWDPYsOiPIl1uPIElXdBNYy29rv7YL3ulupoaiXwGFbyfC9WTXJZmWIUKaf8zZd5wNVYNdR9gWOx88ahwEislLWwWjBYBz+PYueK/DnjBSqrhsY2WJvz/PliDJaU1pTY9mXsfHEPlpS+XKYYRWQLKUEVESmfJqwnyMLeIEdid/4PKvi7A1YisF/R89+l7cIzPz2NlcImWW/sYvIA7H/aP5rftsS2OazH00exUqF7ovlKusgW6ayQtu/6b6LHBtJWyyB/3hiN3fAZBkwoeH4zdo7I7+NJ7GZP0jv/cdgQXvsXTAdg58tSI1C8i50n5gP3Y4np4rJEKiJbnRJUEZF4vUxbBx15w7CLzvxF2f7AHljPkx+Kpry12AXoY9H0KJbExtV78DDsovkA2sdeatzHfOzzsbjzsa8sS6Qi3dNS4N/RlNcH+64dSNv3bl9saJt8LYVCC2h/zngM64E8DvnYD8Di3x+7kdWvg+0X0Ha+yJ873uj6MEWkXJSgiogkz1vR1FjwWL4UMl+asB92MbctbSUpeR5Leh/FBpy/D6sy2LwVY0xhnUAdGE0HRX+372D7hbRVPXwymn8GtR0V2RpWAQ9EU15hKWRhzYXdsfGAdwHqC7ZfgiWq87ESyPuBd7ZynENoO1/lzx17UvoGVnHpb/78kfTSXxHZQkpQRUS6h9VYldfiIRDy7TgL22TtTVtnTB+JtmvFLu7uxZLVu9i0EpPtgCOwdnBHYheYfUtsl28/+xjt28JVWvtZ6YSJEyfuAwxLpVJPTJ8+/d244ylWV1c3Lp1OV82cOfOOuGPpAvkbVS9jvVvnbYNVCc6fL8ZgNTYGAcdH01ejbd+mre33POyGV2dvKvWK9ps/bxyGDalTylvYOeNxKrf9rIh0khJUEZHuLV/aGhQ8Noi2UoojsIRyKOtX9XsF69XydqwTkcLSkpG09SR6NKV7xVwBPIeVhKoHYllPLpf7qnPuU7lcbjLtq7EngnNuRhiGA7AaAT7ueMpkBW3f1xuix6qxm1wHYh0xHYmVug4FJkcTWDv6B7Ahcu7AammE0boBWE/l+fPGUVjNj2LqgVhENkgJqohI5VkC3BlNebthF4z5hHV/rPrfCOAc7OL8Laz951DWb/8VYtXt8mMxPoiVcISIJFB9ff3F3vsrvfdTGxsbL4s7noRroa308k/RY/1oGybriGgaQPt28MuxG1t9geEl9vsubZ0W3Y+Vkq7okv9ARCqGElQRkZ7h1Wj6c7TcH/gkcCZWalJD+wvMfML6b+AvWFKqoV1kk4Rh+H3n3O9I6BAfuVxunHMuTc8pPd0UTVhJ6b+i5RQ2LutnsWrA22Hnkf4Fz1mB1aL4K3Ab8GK5ghWRyqEEVUSk53BY9buPA5Ow4Wzy1mIXky1Y9d5+WMJ6NnZRejNwI1bCItIps2fPfg14Le44OjJ79uzH4o6hGxgOnAWcgTURKKzq/wZWSrodsDPWvvVY4HDgH9g5o5Gt20GbiFQ4JagiIpVvD6wa79lYVd+8xdjF40xsbNb88C7VwHHAaVjbs2FYpylfxdqM/Qa4hW520Tlx4sR9wjC8ELuA3h7rDfQVYGZra+v1c+bMWVOwuauvr/+o9/6TWPvbKu/9K865W1pbW6cVbUtdXd2hzrmvOefu8t7/EbgEuwmwHfAS8LMgCGYATJgwYe90Ov1t7/3hQB/n3APe+28FQfDfwn3W19eP9t7/wHv/xLBhw658++23LwI+ilXBft17/5vGxsYbACZNmrRba2vrt5xzx2LVMB8Lw/A7s2bNmle4zwkTJuybSqW+BzwUBMHPio9RJpM5GLjEOXdvNpu9uuDxk4DPOOeyNTU1M1avXv0t51wmOo4LvPfXNzY2/o6iksj6+vrzvffHA78IgqCwh9n8+uO9958FxmLje74LPOW9/2tjY+P0/Hbjx4/fqaqq6jSsaulI7ObK+8DD3vtfNzY2PlS03997748EcM5NzmQyH8ivc841ZLPZhuj/uh7oEwTBR0vEvkMYhhc558YDO2LVWR/03l9d/HrR9lO99/uk0+lvhmHY13v/LSxRqwYe9d5f3tjYeH/x8xKqL9bB2iew0tL82KM5rLruDOy8UVgyPgr7zH8Uez/ro2kx8H/A77Hvm4jIBpUa7FhERLq/FDAemIN1ZPQdLDldClwLnIBd5J8L3Er7sUdbsA5QPo+VntQDf8MS0jHAddhYhJfTvhQ2sTKZzGfCMHwcOB/rROoJrGfkY4E/1NTUDM1vO3Xq1FQmk7nJe38zdpzecs696Jw7GPhVVVXVv8ePH9+/6CWGA5O998di1aK/iZU0rcba8N2ayWTOzGQyR6dSqQe99xnn3FKg1nt/GnB3fX39sMIdOucGA5Odc8e//fbbWex493bOLQfGOuf+VF9f/6UJEybsm8vlHnbOnYV1UJUGTkmlUnMnTJiwd9E+d4j2eWSp4+ScGxr9H4cVrRoVPX5Mc3Pzfc65r0f/3zLgYOfcb+rr668s3l8YhmOj5xX33urq6up+7b2/E6tmHnrvH8c+t6c756YVblxVVfV14FdYDYA1WHvoPsDZzrl7M5nMmYXbe+/3o63H2HxP1/mp8DhPoq0DoHUmTpy4n/f+CefcxVhy+iT2vTjLOXd/XV3decXP8d4fB0xubW093Xv/ADDeObcIS1AnOOf+PXHixJLHPUFGAD8FXgeuxz7/AP/BqvbuiN28uor1q22/CPwM6yhpH+BHWOn5tsDXsTbrM6Lni4h0SAmqiEhl6QWcB/wXmA2cgpUMNWJV9IZiF5p30rkhHFqwHoI/il2cfhlry7od8C3sAvQa2l/0J0p9ff2xwB+wJOhTQRDsFATBiUEQHNzU1DTEe3+uc64pv/28efMuxKo0vgUcEATB4dls9tjq6uqRzrl5wBHpdPqqDl7udMCnUqmRQRDsFwTBSOfcFwDnnPsJcJNz7g9NTU2Ds9ns2Nra2uHOuX8CQ7z3X+tgn8cBuzjnPhAEwd7ZbHa0c+6jAN77y1Kp1M3AzNra2u2CIDh46NChw7Eq2dukUqnvbPkRbOdTwJtVVVU7BkGwbxAEezrn6rFje1Fxkt2RTCbzJefchcC7zrnjgiDYq7Gx8UNBEOxTVVU11Dn37aKn3OW9PzEIgsFBEIwJgmBcEAS7Ouc+gSXKfyi8aRAEwdHOuSsBvPdXB0Gwe34qLBkuZcqUKdVhGDZgpcPXtra27hwEwQlBEOztnDsTCJ1zv45Km9fjnPuR9/47Y8aMGZTNZsc2NTUNw24K1YRheHlnjk8MDsWSxxeBr2E3cV4Avo0lreOw/2FRJ/f3bPTckVipd0P0+ERgLjZ0TWarRC4iFUcJqohIZeiFVeN9FqtKtwdWJXEaNt5hBrtI3JJqucuAq7HStNOwjpNqgCnYhe0vsGqaieK9vwwrVby0sbHxOgqqcs6dO7e5sbHxhunTp78PVnoalQ7ivT8vCIKn89veeuutbwMfA3LOuXM6SMZawzA8a+bMma/nH8hms78FXvXe7+ScW5DNZi+eO3duK0BDQ8Nq59yl0aYdla6lnHPnZrPZ5wr22YCNSTkASDc1NZ3X0NCwGmDatGkt6XT6WxvZ5+ZaWVVVdW7+eEWxzMJuYqS994dubAfjxo2rxUr0SaVSH89ms3cVrp8+ffr72Wy2XQlqEAR/b2xs/Bftq+H6bDZ7U1Slun9VVdUpm/9vtXnrrbcmYuOEPjd06NALCqtzZ7PZW7Aq7lVYIldKY2Nj4y+mTp0aAsydO7c1nU5/HfvuHc76wzXF6XDsvXsASx4dVnviDKxq+4+w2hKbKyzY3z7A77DjcBSQxcZjPnwL9i8iFUgJqohI9+awTo9ewoaHGIlVz7sQKy39HFaaujXlsNKWo4BjsNLY3sBXoji+hCWEsZs8efIALEbCMLxmY9s//PDDo6PqqEvGjh07q3h9Npt9ERsuoyqq0lls/qxZs4qrPnrsxgHe+1uLn+C9fyaa3aWDsF7JZrOPlHg8/7xsPuHNmzFjxqtYdd/hkydP3prvxdzp06e/W+LxJ6K/u21sB/379z8CGAy8OHPmzDs25cXHjRtXW19fP6q+vv7YiRMnnjhx4sQTnXPNAM65fTZlXx1xzp0Yzf512rRpLcXrwzC8KZo9oXhd5G/FD8yYMWMpVtug9vTTT99xa8S5hfbEalXcD9RhtSn+COxFW4lnbiu/5vNYs4FdgSux6u/HYDe6/oZ1siQiogRVRKQbOwgrgbgJa2+3AKuCuyfwWyxB6Wr3YBfqJwFPYe3NfomNk1qyCmQ5rVy5cgSWLL87a9asJRvbPpVK7RbNvpovASvhpejviOIV3vuOeqxd2tH6bDbbhCUDgzp4bskSLO/9MgDn3IZesxrrWXVr6aiTmyVRLBstQffe7x7NPt/ZFz355JO3zWQyf+zXr99i7/0L3vv/hGF4exiGtwNfiPZb3C54szjndov+lhwap0+fPvmhU7afPHlyqWO7wWOUy+U6ep/LoS8wFbuhMAGrwn8jVsvi02zCe7IF3sU6Edsd6zzJY+2An8LOX7o2FenhdBIQEel+emNVbR/BOo1ZgV3w7RE9HkfvurdjCfPnsHZqY7Ak9cdYkhSLdDpdCxB1SLRR3vvaaHZDY77m19UWr3DOrVfiVqQz7X6Lldync84DhGG4OfvcLJ34/zqzj/xx69R7Mnny5HSvXr3mYOP2PoGV0E9yzh0XhuFY59wP87vehyroNQAAIABJREFU0tgAvPc1AGEYLu9gkxVEpYvNzc2lPgNlez82UR3WrvRSrGp+I1bt9pzo8XJ7G/gM1qnSvdh4qlcBd2PJq4j0UEpQRUS6l0OA+cAXsXN4I1b6cSU2lmmcWrE2r/tgVfaqgG9gPYCOiiMg7/370d9dpk6d2pnfvHwpa3Gvs+s45/JVERdvYXhll0+evPcd3TTo8tK9/HuCVfXcqObm5uOwTnzmDx069JggCH4VBMHMbDZ7VzSMzlatiuqcy5cGl/wMrF27dhhWKp9ramrqVJIdsz7YjassVu3/BSxZzWBtx+M2H6vqOwVowtpNz8MSZxHpgZSgioh0Dw7rWOY+rJ3Ym8DJ2EXmlnRi0hXew3r9PQNL4o7ALjjryh3I2LFjX8JKdGvnz5+/0SrH0VAnIbDraaedNrh4/dSpU1Pe+zHR4qNbNdgycM69Gc121Nvu/l0dQxiGD+Zfq4Mqsu147/eMZu8q1Sa04P0ofjzfNnWTSvC9949FsyU/L977Q6L9Plnc9jeBDsWGyPkiVpX2p8B+wHrtq2Pmgf/FPn//wTr/+hPWQ3hsNTBEJB5KUEVEkq8v1mnJD7BSyQbgAOC2OIPqhHycd2PV96Zj1TPLZurUqWHUyyve+5+NHz++poPtUgBRO9V/ANUtLS3fLd5u/vz5n8E6M1rQ1NR0b9dF3jVaWlpex0raD5g0aVK7apTjx4/fyXu/3vieW1vUidSdQL/Vq1dfUWqbwtLuVCr1VjS7XidI0RBC40vtwzn3RvR3t02JL5fL3YIlTB8rHkd23Lhxtd77bwOEYfiXTdlvDD6KDekyEuug6QTgYmwc2aR6FfggNkRNDitV/QfWqZaI9BBVcQcgIiIbtAswEzgQa1t6Hlay0F28AZyIddr0GawDpb2BC7CSyi6XSqV+6L3PAMdVVVXdV19ff1XUc+5AYF/n3Ccfe+yx07CLY1Kp1NfDMBznnPtSJpPZJgzDP1VVVa0NwzDjvb8YS16+1A1Kz9YzZ86cNZlM5m/A2blc7h/19fWXee/fwkquvoKVxn+gq+Nwzl3gvX/QOXdhJpPZ1Xv/v1Hp7g7AIfPmzZtEVILZ0tJyf1VV1SrgpEwm81vv/U2pVKrZe39ylCy+iA0L005ra+u8dDqd895PrqurW+Wcy3dudW8QBPd0FNvs2bOfz2QyVwNfTqVSd9bV1X3POTfPOTfUe//NKK5ne/fu/Zute1S2mhRwOVa93mE9bp+DVZ/tDkJseJvHgb9gCeuDWI2RlzbwPBGpECpBFRFJrg9gF2YHAu8Ax9O9ktO8tcBnsQ6UWqK/f6ZMN0mz2WxTa2vrcd77LHCw9/5GrMrxv4CrvffDwzBcnd9+5syZzzjnTgJeBj6dSqXuCsPwAaxUZznw8SAIZpQj9i7yFayDrVHRsfgX1jnNv4nGJ+1q2Wz2uVQqdQzW/jDjnMti78ls4LLCbefMmfOe9/5srHOiC5xz93nv5wNXOOf+BPys1GvMnj37Ne/9Z4ElzrnPYh12/bhgGJkO1dbWfs0591NgsHNuGjDPe9+IDa10Z3V19Qn5cWcTphq4Ges0DSxR/TDdJzktNAtrc/8c1mnSPZQoRReRyqMSVBGRZNofG+B+O+AxrK3pG7FGtOWmAW8B/w/4GFa6czale7b9OZYQbpUeiefMmfMeMPHUU0/ds6qq6mjv/XbAIu/9SytWrLinuDQ0m83eO2XKlL0WLlx4XBiG+2Cd4rxSW1t7R0NDw4ri/VdVVf27paVlbHV19fvF6yLfDsPw5wVDlBQ7NN8rb16vXr2eWLVq1VjnXMneZFOp1C9aW1tvSqVSJYeZcc6Nz+Vy1Vhit04QBIsmT558+KpVq050zn3AOdecSqXunjlz5pOTJ08esGrVqrGpVKpdB1Ctra0NqVTqwTAMF3bwWn/J5XJ3pdPptwsfD8Pw+86532HJfjszZ858EhibyWTGOufGhmG4DbDQOfdUEATzC7dtbGycnslkRgDHASOcc4tzudzcWbNmvZzJZIaEYfhoVVXVeuOzNjY2XgdcN2nSpIEtLS07ATWFMeZyuXHOuTRWKr5OQ0NDDrj49NNPv6q1tfUEYFgYhstTqdSD2Wy2ZNvjXC73Oedcv1Qq9Wyp9cAnwzDs29TUVHL4mq2gF3ALcBpWjfd/ouXu7HngWKyX8P2xquEnAE+X2PYUrHfimWWLTkS6hLvmmmvWnZSdc6OnTJlSjjGwRER6uv2AHwITS6w7CGtfOgQrVTqJbthj7AZMAv6KXVDfCJxL+wThbKykeB+s9EREzJ3Ap4iqoxfohX2nJmHJ6WQgKGtkXWsIdsPuAKw2yZG0H292EFbVeyZ2fESkG5k2bdqe3vt1v/eq4isiEo/RwKlYclZoFFZaMAS4HystqKTkFKxN3Iexqr+fAAo7IzoWq266gk4OQyLSg4zBOh8rrAHngJuw5HQV1lt2JSWnYD1xfxDrOXtH7P8bEK3bBqumPgCNnypSEZSgiojEYzR2kfkz7MISrBSgEeuxMt8pyLJYout6jVhbVICpWKnp14G/Y8n5NsBucQQmklD9gNVYJ2PTsVJTsN69J2Mlp3VYSWMlWozd1Hsda5//VyxhfxTYF6uGPzy26ERkq1EbVBGReOSHrxgCXAvUY8NBjMaGhJhI9+zYpDO2BXoD/8Sq+H4CuAG7+O4TbZOiDL3JinQju2KlpX2xmhVPYN+bb0XrP4t1dFWJqrAezZcCX8Da1p6M1bjoXbBdbflDE5GtTQmqiEg8RhXMD8aq+qawsf9uBcZhJSSPAU+WO7gudj5W6vMuVurhsQvvPkXb7VnmuESSbFfakrHe2M2sH2LfnYejv2dg36eGOALsQrtgCXkr1nFaOnq8d9F2VViSulU6VxOReChBFRGJx/ZFy/kmF2lsGJC12HiAx5UzqDK5HLuI/DxWrbkju5QnHJFuYSRW9b2Qi/4eAvwRO2dcS+UlqC8Dh2G1LjZWjXdn4IUuj0hEuozaoIqIxGNjVdEWA4cDD5Uhljh8FytFXbqBbfqVKRaR7mBf2hLSUlZgQzldUJ5wyu5p4BisCUTYwTa1qHM1kW5PCaqISPltQ8fnX491AnIM8HjZIorHVVhHSYs6WN+Ltqp8Ij3dHhtYtxi4FLiwTLHE5RXsxt1LlB4/uR8woqwRichWpwRVRKT88p2dFAuxqmxHYGP69QR/A84C3utg/dAyxiKSZDt18Pj7wJeAq8sYS5zeAQ4FnsGaQhRKYeMni0g3pgRVRKT8Cjs7yWsB/ou1s3qz7BHF63ZseIyFRY/XoHaoInnFnYiB3dj5H2wc1J5kKXYj7xGs9+9Co8sfjohsTUpQRUTKb3fad3ayGuut9zCsNKQnegj4IPBGwWP9UXsyEbDq7r2KHnsXOB0bU7gnWgUcD8ylfa+9o0puLSLdhhJUEZHyO4i2Kr5rsAuso7BOTnqyZ7C2t29Fy6quJ2J2ov1NrXeADwH3xBNOYqwFJgA3Y0N0wfo9HYtIN6MEVUSk/PKlgi3YmKd10bzAq8DB2DAROWCvWKMRSYZdaWtv+S5wNDYuqFjHcp8CfgoswYZQVOdqIt2YElQRkfLbGVgG/BbrIKijIRN6qoVYJyj/RVV8RcC+B9tgN272x3qxlfa+iSWpQ1DnaiLdWlXcAYiI9EADgB9Hk5S2FEtSPxR3ICIJsAfwLFZyuizmWJLsCuBJdIxEujUlqCIi5XcYVpVVNmwVMDPuIEQS4NfAD2jfGZCU1lM7jRKpGEpQRUTK79W4AxCRbuWduAMQESkXJaiSeN5719jY+ELccWzIFVdcscMLL7xQA7B69erlK1asWBp3TEX6AwOj+bUk72KnBtihYPl1rOOLxBgyZMjOzjkHcP755793yCGHFI+9lyQ3ZjKZy8rxQtls9u/Ouf3L8Vqb469//evA2267bQBAS0tL85IlS4rHWo1bLe0/+6/FFcgGFLYDXkjySvF2wI4jWNXORJ1/+/btO6Bv374DAXbZZZc1l156adLOv+2sXr36oDPOOKPLexQPguAE4A9d/Tqba82aNe6CCy5YNw7z0qVL31m7du2aOGMqYUfs9xPsc5+0qs0DsSYtYOeNRJ1/a2pqagcMGLDu/Hvttde+Fv3MJ9UnM5lMj+i5WwmqJN5ll13mxowZs3vccWxIU1MT7733Xn5xcDQlVTU2DmeSjYw7gGKLFi1aN5/L5YbFGEpnbFfG19qFBI872NzczLvvvptfrAb6xRhOZyT2WEZ2ijuAjRgSTYmxcuVKVq5cCUD//v2rSfh7PGjQoLL0gOu938Y5l9hj4b0vPHeAdW6XZNtR3nP/pkrc+XfNmjXt3mPv/agkJ6jOub5xx1Au6sVXREREREREEkElqNId/cI591zcQRRqaWm5CugDsMMOe725cOF2N8QcUpF5H4NVI2y+Zi0c+vN44yn2ygHwxqlty4f9CnqtjC+e9VVVPfDN1lYbqnTBgpX39+8/eG68EbU3YsTSD/fundszzhiWLq35z5tv9rk/zhiKLVmy+hPAcIBBg4Y1L1ky8pcxh1TkxcPgnePblo9OYM/O91zSNr/bv2GnB+OLpZT7vgxhVMV30FvwgUSdf3fccelH33nnqREAuVzY8vTTgxJ1/u3fv2XozjuvODfeKFzL008PTNRxaW5e2hf4Qn55yJDD5yxaVPV4jCGVcN9FEPay+cGvwt63xBrOeuadDaujWhe1a2DsVfHG097226895N13Hzohv9za2vr56urqXJwxFXPO/R5IbrFuF1GCKt2O935WJpO5M+44Co0aNepKogR1yJCR7y9cOGtGzCEVGXxCW4JalYO7Ehbf8WH7BPV7c+DUxfHFs75Uqvc3wRLUV19d9fyNNx6RqGN40023HxV3grp4cc3Tl1xy1PQ4Yyh22GFrJhAlqH37DmpZsuTuRMUHY7Zpn6AmLT4AV5Cg7vE03JawGKsvaEtQ+y1O2jHcbrsPjytIUHNJ+4586lPP7BN3guo9iTsuO+10/7YUJKjDhp0yf9GiS7MxhlRC1Zfa5ge+l7TPPgwa35ag9mpJWnzDhl1SW5ig3nLLLdc2NDSsjTOmYkEQ/I4emKCqiq+IiIiIiIgkghJUERERERERSQQlqCIiIiIiIpIISlBFREREREQkEZSgioiIiIiISCIoQRUREREREZFE0DAzItAX2ANIA6uBZ+INR0REREQSLAWMBsYCY4C9sOvIZ4AvbeB50glKUKWnupC2k8re2EkF4Elg/7iCEhEREZFE6wMsBLYpsa5vmWOpSEpQpaf6ddwBiIiIiEi3k6ItOV0AzAP2wUpUZStQG1TpqZ4ArsNKUo8Abo43HBERERHpBtYApwI7ALsCpwOPxxpRhVEJamX6GtamEqABuKMTzzkXODKa/w/wly6IK0kOKFr+bCxRiIiIiEh30gLMiTuISqYEtTJlgGOj+efoXIJ6HPDJaL6Vyk9QRUREREQkYVTFV0RERERERBJBCaqIiIiIiIgkghJUERERERERSQQlqCIiIiIiIpIISlBFREREREQkEdSLr1SSbwGf7mDdPti4VSIiIiIiklBKUKWSbAuM7GCdK2cgIiIiIiKy6ZSgSiW5AvhdB+tUeioiIiIiknBKUKWSvB9NIiIiIiLSDamTJMlLxx2AiIiIiIj0bCpBrUytBfPVnXzOtl0RSIL1BXoVLNdEf9PAoILHc8DycgUlIiIiIol3AXBAwfLY6O9I4JqCxxcAl5crqEqhBLUyFSZUnU089+uKQBJsGnBWicf3ARYXLD8PjC5LRCIiIiLSHZwM1Jd4fAdgSsHyfJSgbjIlqJXp9YL5gzqx/eHArl0Ui4iIiIhIJbkBuK8T2y3s6kAqkRLUyvRIwfw4YGfaJ62FqoCfd3VACfTxaBIRERER2RR/jzuASqZOkipTI9AczVcDf8LaXBbbFmgAjgR8eUITEREREREpTSWolWkx8HvgK9Hy8cB/gb8ALwK1WMPuDwMDgeeAZ4DTyh6piIiIiIhIRAlq5foucBhWOgqwE3Bxie1eATLAN8sUl4iIiIiISEmq4lu5VgInAT8DVnew/lrgYOAFYBWwJJpWlSlGERERERGRdVSCWtlWAl8HvoeVpA4HHNZh0iO0H47mwmgSERERERGJhRLUnmE18K+4gxAREREREdkQVfEVERERERGRRFCCKiIiIiIiIomgBFVEREREREQSQW1Qpdtxzn0zCIJPxR1HoW984xt98vOvvz5/Zxh+WZzxrK9p97b5Nb2SF9/Kndovf/rrkFobTyyltba2rJvff/+Bx3z603fsEGM46+nTp2WvuGMYOnTliddff8fIuOMo1NDQd+f8/OLFb9Qm77O/fET75aTFV+yRD8HwUXFH0V6ud9v8op2Sdgxfe82vO15VVenq66+/I1HxVVfnBsQdA/heSTsuq1ev6HX++W3LL730fxNh2pj4Iiol7NU2/87IpH32YcUubfOrE3f+ffHF1G6Fy2efffb155xzThhTOB1xcQcQByWo0h2dGHcAxdLp9Lr55cvfGQScEl80G9OahrcSHB/AOx+MO4JiYcFP1sCB1SMHD25OVCKWBL175/bs3Tu3Z9xxFOrdu3rd/KpVy6phWcI/+0n/bi4ZbVNSrRoIqxJ1DJcX9JefSrn04MHNiYovCZwjlbTj0tzc2m555crX9wX2jSeazlg5GFYm6hi211KVtPPbihXrPXRmDGFICariKyIiIiIiIomgElRJvEsvvdQ3NjZeF3ccG7LLLrvsV1VVNQDg/ffff33BggWvxR1TkZ2BXaP5JuDxGGMppR9wQMHy/UAuplhKOuCAA45IpVJpgJqammeB92MOaUPuL9cLpVKprPf+yXK93qYaNGjQbgcddNBOAKtWrVr63HPPPRV3TEUG0r5U5p64AtmAowvmnwKWxhVIB/bFjiPAG8Cr8YWyvuHDh++0/fbb7waw/fbbrwAeizeiDVuyZElZmlek0+nXwjBM8m97+qCDDjoiv/Diiy8+3tTU1BRnQCUcgP1+AryGjXOfJLti1x8Ay4BE/VYMHDhwwIgRI/bLL6dSqSSef9cJw/CNuGMoF3fNNdf4dQvOjZ4yZcrzcQYkIiIiIiIiPcO0adP29N4/l19WFV8RERERERFJBCWoIiIiIiIikghKUEVERERERCQRlKCKiIiIiIhIIihBFRERERERkURQgioiIiIiIiKJoARVREREREREEkEJqoiIiIiIiCSCElQRERERERFJBCWoIiIiIiIikghKUEVERERERCQRlKCKyJb4CfAI8JG4AxGRLrMf9j2fEXcgItKtjMHOHQ1xByLdS1XcAYhIt7Yr9gO0Y9yBiEiX6Y19zwfHHYiIdCt9sHPHNnEHIt2LSlBFZEvkor/pWKMQka7UGv3VTW0R2RQ6d8hmUYIqIlsin6Dqx0ekculGlIhsDp07ZLMoQRWRLZG/O6ofH5HKpRtRIrI5dO6QzaIEVUS2hO6OilQ+3YgSkc2hc4dsFiWoIrIl1L5EpPLpRpSIbA6dO2SzKEEVkS2hHx+RyqcbUSKyOXTukM2iBFVEtoSq74hUPt2IEpHNoXOHbBYlqCKyJdQBgkjlUymIiGwOnTtksyhBFZEtobujIpVP33MR2Rw6d8hmUYIqIltCd0dFKl/+ItOh6wYR6TzVspLNoh8aEdkSujsqUvlaC+b1XReRzlI/FbJZlKCKyJbQj49I5csVzKskREQ6S7UvZLPoh0ZEtoSq74hUPpWgSrdXV1d3aDqd7h+G4X1BEKyKO54eovjcEcYViHQvupshIltCVXxFKp9KUKXbS6VSvwvD8Hbv/fC4Y+lBdO6QzaIPi4hsCXWSJFL5VIIqW0UmkxkCvAe8GgTBiK213/r6+tO99393zt2QzWbP3Vr7lS2mc4dsFl1UisiWUAmqSOULAY+1I9N3XbqlXC73CaBPGIYL4o6lByksQdW5QzpNCaqIbAklqCI9Qw67ZtB1g3RLs2bNejbuGHogVfGVzaIPi4hsCVXxFekZ8gmqbkZtwNSpU1OPPPLIZOfc2cD+QG/gHeAR59yN2Wz234Xbn3zyydv26tXrIqAOGA6sBh4GfhsEwZ3F+89kMl8DDvXeX55Op8MwDL8DHAHgvb+3qqrqOzNmzHgJoK6u7uPOufOAPYAVwP9ramqaOnfu3OaifX4GOMk595tcLvdeKpX6DnAUUAM84b3/SWNj479KxPJnwAVBcFapY5HJZP7mnFudr3KbyWTO8N5/3DkHsF0mk/lbweaLgiC4INpuiPf+NOfcKcBoYAdgJfCY935aY2Pj7MLXqa+vnxqG4Yecc3jvjyva791BEPw62u+PgZHV1dVfuvXWW98u3Mf48eP7V1dXf8l7PwnY2Tm3xnv/mHPuD9lsdlbx/1ZXV3eec+6DqVTqly0tLW+m0+mpwHHANs65/3rvfx4EwcxSx6WHyaHaF7IZ1EmSiGwJlaCK9Ay6GbURkydP7j1v3rzAOXcLcCqwCHjSOVcDnOO9/37h9pMmTdq9V69ejwLfBnYGnnDOvQ+cDvwrk8l8u8TLHAFMBiaFYfggcBLwNpYYfSyXy91dX1+/QyaT+YVz7iYs6X0r2v83+vfvf2OJfR4U7XNiKpV6GDgeeNo59w6WuN6eyWQ+XeJ5pwMf3tAhiRK+vGHOuQOi+RpgTMG0X8F2ZznnpgEnYp+7J5xza4CJzrlZxcfFe7+nc273aHFw0X7XtXN1zp0ITF67du02hc+vq6sbXl1d/XD0/uwOPAUsBCZ47xszmczPiv8x59zY6P87KZ1OzwcmO+cWA6u998cA0zOZTMnEvQdSb/+yyZSgisiW0EWrSM+gm1Eb0dzc/EssMX3aObdPEARjgiA4IZvNjvbe7wr8rmBzl8vlbgZ2Af4K7Bxte5BzbjxWkvqDurq6E0q9lnPuu8BlY8aMGRwEwWFr1qzZGfgXMNR7Px04BzghCIKRQRAcHIbhGGCp9/4jEyZMGFNqn977LwMNtbW1uwVBMCGbzR4EfAR7739TX18/akuOTxAEvwTGRotvBEGwe8F0TMH/9qRzbmJTU9PgIAgOLDiGxwPLgO+feuqpexbs9yzn3AXRc28t2u9FnQjteu/9nsDs1tbWXYIg+GA2mx0LHAssB76ayWRKJuLe++8Af2ptbR2czWbHBkGwK/BNrMTwx1OnTtV1ts4dshn0xRGRLaEfHpGeQaUgG5DJZEYAnwbWAJlsNvtc4frGxsY3gyC4uWD744FDgDeBTxWOy5nNZv8B/ApwzrmLO3jJ/wRBcOXUqVNDgNtuu21lGIaXReuOcM59q7CK8KxZs54CbgRIp9NHdLDPpWvWrLmwoaFhbf6BIAj+7r3/A1Drvf98Jw7FFstms//OZrPZuXPnFvYAS2Nj41zv/eVAKp1Of2RrvFYmkzk4KlldEobh2XPmzFmeXxcEwT3AFdHiNzrYxRO1tbVfnzNnzpr8A7W1tT8FXgN2fvjhh0dvjTi7OZ07ZJMpQRWRLaEfHpGeIZ8s6GZUCVF7yTRwexAEr3TiKSdEz5tRmJzmhWE4LZo9dvLkyb1KPP/vxQ+kUql1nQB5728tXu+9fyaa3aWDmG657bbbVpbY7y2FMZfL5MmTe02YMGHkhAkTjpk4ceKJEydOPNE5lwZwzu2zNV4jSk5xzs2eNWvWkuL1YRheE82OnTRp0sDi9d77WxsaGgo7AiJafgoglUrtvDXi7OZ07pBNpgRVRLaEfnhEegbVltgA7/3uAM655zv5lN0AwjB8udTKQw455FVgLVC7cuXKocXrnXOvFT/W1NS0NJpdGQTBohLPWRa95nqJVrS+ZCxAPuHeauOWbsi4ceNqM5nML5qbm99LpVIvpVKpu8IwvD0Mw9uJSjS99/23xmtFVa/x3pf836Ok9T3ArV27drfi9alU6tUOdr0EwDlXuzXi7OZ07pBNpgRVRLaEfnhEega1N9+AgkRk6QY3bFMTPW95qZVR1d0VhdsWyuVyrcWPFdjQug1pKvVgTU1NPsayJFv9+vW7AfgK8Ib3/mJgknPuuDAMxwLnRZu5rfRyNQDe+5L/e2Q5QHV19XrvA5t/rHsSnTtkk+nDIiJbQj88Ij2DbkZt2CKAMAx36+T2+RK2nUqtPOmkk/oC2wKk0+nFWyG+jfLeDy/1eHNzcz7G94tWtQK9p06dmsq3hc2bMGHCoM2JYdKkSbvlcrnJwFu1tbVHNjQ0LCtcn8lktkrV3gL5ar0l/3csER4OkMvlyvI+VCCdO2STqQRVRLaEfnhEegbdjNoA7/2DAM65I+hc6d6j0fNK9qjbq1ev46LZ10tV1+0K3vsjO1h1VPT30aLH3wTSDz300PbFTygYTqad2tra/BispdrV4r3P9xQ8rzg5jdaXPF5hGDZHf0vudwMejeIdW2plXV3d4VjJ8bKxY8e+tIn7FqNzh2wyJagisiX0wyPSM+hm1AYMHTr0DuBVYO9MJvOFUtsUDjninPs71uPv+Lq6una96o4bN67KOfedaPHPXRPx+pxzJ2YymcOLYqkFvhqtv6XoKS8BpFKpdkOwTJkypdo5dxklNDQ0rMCGihlSX1/fr8Qmb0V/9xo3bly735VTTz11T+dcqfFYSaVSb0Qxjiy1viOtra2NWNXmo+rr608pXDd16tSUc+570eItxaXE0mk6d8gm00WliGwJ/fCI9AzqsXsDpk2b1pLJZD4N/AP4ZSaTOdA59zfv/ftYFdHj5s2bNwrIAGSz2YWZTOYHwA+dc7Pq6+svC8PwfmCIc+4i4AhgQTqdvrKM/8ZLQLa+vv47wAPAMO/9t4G9gUeWL1/eLll2zt3ovT/VOffT+vr6/mEYPgTs8vbbb1/Ahtur3g+c4r2fU1dXd7tzrtk5tzybzf5+2bJlz/fr128BsEe/fv0a6uvrf9Xa2roklUodFSWLbwB7Fe9w+fLlz/fr128xcGhdXd12xFpHAAAVo0lEQVTNzrmnsBuoTwdB0NhRIHPmzFmeyWQuAX7rvW/IZDI/TKVSc8MwHDBv3rwLgVOAd3O53NROH0UppnOHbDKVoIrIltAPj0jPoB67NyIad/QU4GXgk977OcBDwHTgy0Sd7RRs/yPg29gYo790zj3onJuFDedyfzqdPm7GjBmd7XRpa7gCuMN7f433/vEo/qO99/c55+qKxyXNZrO3AL+O4v+Rc+4O59wfnXNVuVyurqMXCcPw8865u4EjnXNTgR977y8BmDt3bmsqlToDWAhM8t7fmU6nH3XO/Qa42zn3lVL7nDt3brNz7mPAi9HfHwI/Bj66sX86CILfAV+MFn8chuEDwD+xmwmPhmE4bvbs2e9sbD/SIZ07ZJMpQRWRTVGciHb0w7N9iW1FpPso/v6Wqi2RAnYsTzjdQxAEdzY1Ne3lnDsO+DzwFefcmc65PYIg+HjR5j4Igh9VVVXt7L3/mPf+q977851zhwRBcNSMGTNeLd5/Op2+OAzDsVFpaztz587NhWE4NpfLjSsV29q1a/8ZPffyUuu99y1BEJzlvT8QOM8592Xn3HFjx449JpvNLuzg//1iGIb7R3F/OZVKfaimpmbs7NmzX4tiOa74ObNmzXo5m80eW1tbW5tOp0eEYTg2lUqtS2hnzpz54Jo1a3b33k/AevP9TBiG+wVB8JGampp7wjAcWypRzWaztwdBsAfQF9g76vU3X0WXXC73iSimBSX+j1+n0+mdgY8AF2Hv3RFjxowZO2vWrGeLt8/lcj8Iw3DsmjVrbit1XIDvRf/XXR2sr2SdOXek0blDNsBdc801ft2Cc6OnTJnS2TG8RKTn+QlwLHA+1rnEOODfwDPAB4BBWHulr2LVwl6NI0gR2SLbAi8AlwH/C6wGHgbGAhOAOcAHgd8DdwMl2wVK95DJZH4LXOC9P7exsfGGuOORbm1H4GngO8B1QDPwBLAfcCJwJ/Ah7NzxT+CCeMKUpJk2bdqe3vvn8ssq4RCRTfFn7Afln9hF69vR4ztjP0LbYxe3r6Hk9P+3d+9Rctb1Hcffs5ck5LZgkAYwgDXREBOMiEhpvWCItVYkeOmRHoxaFeyBY1UsJV6jHEtVopYeL8mxogVKKZATslC1hFBPhMZCIiRcAkFNsoZLICHJJiHZS57+8ZvJPjM7M3thd57f7L5f58zZ32+eZ2e+2cw+O5/5/Z7fI9WrXUAb8A1gEeG8v1Py2/6REFqPApqBn9S+PEmRegbYASwBvgJsAwqXKbqG8B7hKEL++GkWBao+GFAlDcRDhFBauBTASfmvkwifkAJ0Aj+qcV2ShtY1hFGOqRRPxUtfPmQ78KtaFiUpekuA7wJ/lL8VzE212wjnZ0tleQ6qpIH6AeHyCJXswk9GpXp3K3CgyvYEaM1/VX37DXBLQ0PDlqwL0YhwE7C/yvYEuA2PHarCEVRJA/Vj4AqKPxlN206Y5iOpfh0EVgF/XWH788D3a1eOhktra+uPcNaLhs5+4F7gggrbnyOcJiBV5AiqpIHaDTxcYduL+KZVGim+Qwii5ewHNtawFkn14xpgZ4VtewgLK0oVGVAlDcY3gBfK3L+XMDVQUv17gJJrd+Z1A/9e41ok1Y/7gH1l7u8Crq9xLapDBlRJg3E3YbS01CbCp6OSRoal9D7nfCdOCZVU3XVAR8l9O/P3S1UZUCUNxmHCCEp36r524NpsypE0TP6VMK0/7Tng9xnUIql+/JDex45nCJetkqoyoEoarO9R/MdnH3BnRrVIGh47gcdS/S5CaJWkap4FfpvqdxJmZEh9MqBKGqwthD9ABb+m+uVnJNWnbxHeXEKY2n9jhrVIqh/fpPjYcXOGtaiOGFAlvRTXEFbz3IPTe6WR6ueE6xsDbAZ2ZFiLpPpxBz0LKj5Kz3FEqsqAKuml+A/C1N4DwC8zrkXS8DhMGPnoxA+iJPVfF7CCcOz454xrUR0xoEp6KV4EVgP/RXgTK2lkupawKNptWRciqa58hxBQb8+6ENWPpqwLkFT3/g7DqTTS/RZ4PeWvbShJlWwC3kD5S9NJZRlQJb1Uz2VdgKSa2JR1AZLqkscODYhTfCVJkiRJUTCgSpIkSZKiYECVJEmSJEXBgCpJkiRJioIBVZIkSZIUBQOqJEmSJCkKXmZGdWHFihXTsq6hmhUrVkxpb28fC7Bjx472NWvWtGddU4mJwOR8+xCwM8NaymkGXp7qPw0kGdVS1nvf+96puVyuAWDu3Lm75syZczDrmipJkqT9ggsu2F2L51q+fPlxDQ0NY2vxXIOxZs2aSVu2bJkEsHfv3kN33XVXbK/9McCxqf5TWRVSxQmp9vNAR1aFVDAFKLwG2/O3aJx++ukTX/nKV04GmDRp0qEFCxbE9hoscv755/8hl8sN+/H3nnvuGbdnz56X971nNjo6OnI333zz8YX+/fff/9y2bds6s6ypjPRrfy/xXac46vce06ZNG3PmmWceOf5+6EMfivH4e0RLS8tz55xzTrTvPYaSAVXRW7x4cUNjY+O2rOuoZt26dWzcuDHrMjSMli9ffqQ9c+ZMGhsbM6ymT98DLqvFEzU1Nd2Zy+XOqMVzDcbWrVu57bbbsi5Do9j69etZv349ANOnT+d973tfxhVVt2rVqqOBPcP9PO3t7X/e2Ni4YrifZ7ByuZzHjhGura2Ntra2I/2FCxfS0BDv5NL9+/e/E/hF1nXUQrz/C5IkSZKkUcWAKkmSJEmKglN8VXeSJFmYy+XWZl1H2sGDBx8gf57F9Olv2fTkk+d/MeOSSnzty7DntNAefxCuuijbekpdPw8e/Nue/mc/BicO+xSzgWhu/vytnZ2HANi8efPSs846a0nGJZX6PnBuxjUsAZZmXEORHTt2/CcwF2Dq1Ffve+aZSz6SbUWlli2Ax1O/j0ven10tlVx+a0/7jTfAByOblnnFT6B7Ymif8ARc/vlMyykxY8bPvrh586q5AJ2dnS8Cr8u4pCK5XG5OkiRZz2WN7ueyZcuWY4H7Cv3p0z/6gyefnH13hiWV8fc3wOFxoT1tI3z6q9nWU2rxVdB+amhP2A9f+3C29RSbMePBd2/efP1HCv329vbXtrS0xHae8SZG4YCiAVX1aPt55523Oesi0qZPn3640B47dmIHfLat2v619/XUSfW5JL76WncV9+c9Be/aVX7fbORyXzjS3rt37wuxvQbvuOOO/UmS+bpSO2P7uZx99tlHXvtNTc0RvvZvLFnMKrb6AC5PtY/eHV+N/3C4p90U3fF33Lh7j7wGkyQ5HNvvyMqVK4/O5XJZl5HE9nO58MIL96b748efvCu21xZckTrojzkYX31XpRZUa4zu+DthwpVFx9/bb7/9yVtuuSWqReBaW1uzLiEToy6RS5IkSZLiZECVJEmSJEXBgCpJkiRJioIBVZIkSZIUBQOqJEmSJCkKBlRJkiRJUhS8zIxGo0bgNcAbgDPyX4/Pb/sEsDqjuiRJklR/JhFyVRfQnnEtdc+AqtHoKmBRhW3ja1mIJEmS6koDMJ/igY6T8tv+Fzg7o7pGDAOqRrOtwHqgDfhUxrVIkiQpfuOBn2ddxEhmQB2cBuCPgROBHPA08PgQPOargOOAo4AXgCeBPS/xcdXbUuDbwPP5/gwMqJIkSeqffcCDwLr87SPA27MsaCTJKqCeCXw/394HvK0f33MCsDLVPxfYPbRlFfkCcEG+fQewGBgDfA64GDi5ZP82YAnwPcL88/56NXAl8JeEcJrWBdwHXAO0VnmMqwlTDQBuAL5bZd9vAeek+ldQ/ZzLXwBT8u3PAGuq7FsvtmZdgCRJkurSfuBooDt137szqmVEyiqgTibM14b+jxCOTX0PDH/tJ6WebyNhEZ0VhHBdzjRCMDwH+ADQ2cfj54CvEIJwpX9LE/CW/O0WYCFwsMx+21O1dlE5oObyj5EOwudROaBOB96Rbx8GHq2wnyRJkjQaJBSHUw0xp/j2zzhgOSGcdgD3EELrAcL00POAifl9zyeMSn69j8dcBnw81d8N3EkIgfsJI8bvBmblt3+AMPX3PYRfjLR0wDyD8KlOudHlOfQepZ1Xpcb0tgeBnVX2lSRJkqSXxIDaP+8n/Kx+RRiB/H3J9hMJJ0vPzvevAL5DCLDlfJLicPovhJHU0mWpFwGXEc6XbCQE1svy+6c9CjxFCLWNwFuB28s8bzpwHiKMSs8GpgLP9LH/3RX+LZIkSZI0JBqyLqBONBFGTN9B73AKYYrthfSMbE4G3lXhsY4hnAda8G3CAj3lrpl0GLgW+FLqvkWEYFkqPYp6boXnTgfOpfmvOcqf1N1A8bmqBlRJkiRJw8qA2n+fBl6ssv1hYG2qf0aF/S6mZzpwG2HktC9LCCEYwrmw7yyzTzpAlpu220w4lxXCisM/7WP/1wHH5tsdhNFjSZIkSRo2TvHtn6eovtJtwQPAn+Tbp1TYZ0GqfSPlFz0q1UFYxfeT+f6b6T2FNx1QTyVMO96euu9MYFJq38I5pVMoH1DT960lnBcbs6OofKmYezFgS5IkSdEzoPbPb/q5345Uu6XM9vEUr0R83wBq2JRqzyqzvQ14gnDZGgjTdq9PbS89n/QwYbGn9xMumTMD2Jza5+0l+8duPPBPFbZ9FQOqJEmSFD0Dav+80M/9OlLtMWW2v4Iw1bbgm4RLzfTHlFT7ZRX2WU1PQJ1H+YDaTQimAKsIAbWwvRBQx9AzHRjqI6C+CHyjwrZ7a1mIJEmSpMExoPZP1xA9zjEl/ZmDfJwJFe6/m55pwOkR0wnAWfn2enoCd3ra8jzgh/n2m1LP0Q783yDrrKUDwJVZFyFJkiRp8AyotdVY0r+D6gsvVdJW4f57CFN3GwijtTMJU4P/jJ4R3fRo6GZgK2GK7zn57ztMcbhdA3QOokZJkiRJGpB6CqjlpszWm+dL+l+m/+e39sdOwuJHp+f78wgBtdr1TFcDHyVMIZ5LGGH1+qeSJEmSai6ry8ykV64d18/vOW44CqmxpyieLjxnGJ6jdNpu+utBep+Puapk/4mEKb4FIzGgngLclbr9JLXtqpJtb65xbZIkSdKoldUI6p5Ueyxh0Z9dfXzPm/rYXg/2AffTcyma84F/G+LnuBv4XL79NkKwn5vv30fvKcWrgQTIAecCj9CzkNNzwIYhri8GEwn/1nLmlvR/WHYvSZIkjVY3An+R6hfWbnkjxZlmA+H9uAYgq4D6O8JqsoVzMs8mnI9ZSQPwN8NdVI3cSnFAnQ08PISPv4awmvAYwqJMl9MzUr6qzP7PEELpbMK5qk+kthXC60jzFHBJP/ddP5yFSJIkqe5MpPfipxCyVfr+SbUpZ2TJKqDuJ3yi8Pp8/1KqB9QvAacOd1E1spSw2uzLCQH9ZsI00r5GkAumEa63eqjC9v3AWnouE3NZalul6bp3EwLqeIo/CFhdfve6twtYlnURkiRJqkuXAJ/px36V3q+riqzOQYXia3S+E7iGMN037RjgWmAxYXrsSLAf+DhhtVyAWYRRuguo/P8xhjCN4Abgt/T9aUw6iI7Pf30BWDeA/UvvlyRJkhRmIP6uH7ftWRVYz7JcxXcpYeT0Vfn+5cCHCedJ7iNcJuVNhNB6EPgEcFPtyxwWKwn/3iWEUHoysJwwMvpr4A+EAHs0MB04DThqAI+/GvhqyX3/Q5hWXc4vCYs3pV8PWwhhWJIkSZJqIsuAegBYAPw3cHz+vmOB95Tstwu4iHC5lJHku8DjhEV4TsrfdxxwXh/ft4W+pwv8mhDyJ6buqzYaupfixZv62l+SJEmShlzW10F9mDA6uAj4AOH8yoJnCSOm3wbaCOHtltT24Z7TvS71fPf383s2pb6nPwsf/QyYQQjgCwjnjbaU7LMHeJQwyvkzwiJIfS1c1EkYnZ1V8lzVLCWM3BaMlNFqSZIkSXUi64AK8DxhuuvlwBRCQNtJ8aVoIEx//asa1rWMgS+kszJ/G4gO4Mf5G8Bkws8hRxg93j3AxytYPMD9f5q/SZIkSVImYgioaTvzt9Fsb/4mSZIkSaNKlqv4SpIkSZJ0hAFVkiRJkhQFA6okSZIkKQqxnYM6GI3Ax4bosdYDDwzRY2mY5HK5m1tbWzuyriNt0aJFkwvtTZtWnQrj7syynt46J/e0D4yLr76uscX9BTdAQ1+rVddUZ2fPS27u3LmXXXrppQszLKeXJElelnUNwJWtra2XZV1E2nXXXXdsof30049PjPC1X3KN69jqK3XPR2HcB7OuolhX6pJq22fG9jN87DGOHH/HjBkzvrW1dXuW9ZTRnHUBwFGx/VwOHDjQcNNNPRc0eOSRqz8FV1+SYUlldI/raW+ZG9trHzpSV6ZonxBbfRs3Mi7dv+iii36/cGFUf9phlA4mjoSA2ky4RMpQ+BoG1HpwbN+71FYulzvS7u7uaCbCGnskOTgUcX0AnVOyrqBUkorLjY2NEym+zrCCyflbNBoaev62d3d35aAr8td+7L+bXRPCLVbdTdAd1c+wq6uomwNOyKaSqEX3c0kfOwC6uw9NAiZlU01/dDfH9tovFt97j+7uXndF9RoczUZlKpckSZIkxWckjKAeAoZqatvBIXocDaHFixcfXrlyZXRzLtJmzZo1e+rUqS0Azz77bNuGDRu2ZV1TiVcAJ+fb7cCGDGspZxJwWqq/Fuj92WaG5s2bd1ZDQ0MjwPjx4x9LkmRX1jVVsamGz/XlJEmi+lQ87fjjjz95/vz5rwBob2/fvXbt2keyrqlECzA71b83q0Kq+NNU+2F6X6c8a68Fjs63/wBszbCWXmbOnHnitGnTTgFoaWlpT5IktuNvkWOOOeZALZ6nqalpXVdXV7R/25MkaZw/f/5Zhf5DDz20YceOHe1Z1lTGafSM6m4D2jKspZyTgGn59h7C8SMaU6dObZkzZ86R42+SJPclSRLV6UVpzc3NUR87hlJu6dKlR/4jcrncay6++OInsixIkiRJkjQ6LFu27NVJkjxe6DvFV5IkSZIUBQOqJEmSJCkKBlRJkiRJUhQMqJIkSZKkKBhQJUmSJElRMKBKkiRJkqJgQJUkSZIkRcGAKkmSJEmKggFVkiRJkhQFA6okSZIkKQoGVEmSJElSFAyokiRJkqQoGFAlSZIkSVEwoEqSJEmSomBAlSRJkiRFwYAqSZIkSYqCAVWSJEmSFAUDqiRJkiQpCgZUSZIkSVIUDKiSJEmSpCgYUCVJkiRJUTCgSpIkSZKiYECVJEmSJEXBgCpJkiRJioIBVZIkSZIUBQOqJEmSJCkKBlRJkiRJUhQMqJIkSZKkKBhQJUmSJElRMKBKkiRJkqJgQJUkSZIkRcGAKkmSJEmKQlO6kyTJKcuWLcuqFkmSJEnSKJIkySnpflPJ9l8kSVK7aiRJkiRJynOKryRJkiQpCgZUSZIkSVIU/h9zhnJWRK6mqgAAAABJRU5ErkJggg=="
+ }
+ },
+ "cell_type": "markdown",
+ "id": "baccd833",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8ed4129c",
+ "metadata": {},
+ "source": [
+ "### Code\n",
+ "\n",
+ "\n",
+ "Take a look at the implementation below and try to understand it. Note that we have used MPIClustermanagers and Distributed just to run the MPI code on the notebook. When running it on a cluster, MPIClustermanagers and Distributed are not needed.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e15082fb",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "] add MPI MPIClusterManagers"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a66cbf9a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "using MPIClusterManagers \n",
+ "using Distributed"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e0d63c6b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "if procs() == workers()\n",
+ " nw = 3\n",
+ " manager = MPIWorkerManager(nw)\n",
+ " addprocs(manager)\n",
+ "end"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d7fb9177",
+ "metadata": {},
+ "source": [
+ "First, we implement the function to be called on the MPI ranks."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d39c7bb2",
+ "metadata": {
+ "code_folding": []
+ },
+ "outputs": [],
+ "source": [
+ "@everywhere workers() begin\n",
+ " using MPI\n",
+ " comm = MPI.Comm_dup(MPI.COMM_WORLD)\n",
+ " function jacobi_mpi(n,niters)\n",
+ " nranks = MPI.Comm_size(comm)\n",
+ " rank = MPI.Comm_rank(comm)\n",
+ " if mod(n,nranks) != 0\n",
+ " println(\"n must be a multiple of nranks\")\n",
+ " MPI.Abort(comm,1)\n",
+ " end\n",
+ " n_own = div(n,nranks)\n",
+ " u = zeros(n_own+2)\n",
+ " u[1] = -1\n",
+ " u[end] = 1\n",
+ " u_new = copy(u)\n",
+ " for t in 1:niters\n",
+ " reqs = MPI.Request[]\n",
+ " if rank != 0\n",
+ " neig_rank = rank-1\n",
+ " req = MPI.Isend(view(u,2:2),comm,dest=neig_rank,tag=0)\n",
+ " push!(reqs,req)\n",
+ " req = MPI.Irecv!(view(u,1:1),comm,source=neig_rank,tag=0)\n",
+ " push!(reqs,req)\n",
+ " end\n",
+ " if rank != (nranks-1)\n",
+ " neig_rank = rank+1\n",
+ " s = n_own+1\n",
+ " r = n_own+2\n",
+ " req = MPI.Isend(view(u,s:s),comm,dest=neig_rank,tag=0)\n",
+ " push!(reqs,req)\n",
+ " req = MPI.Irecv!(view(u,r:r),comm,source=neig_rank,tag=0)\n",
+ " push!(reqs,req)\n",
+ " end\n",
+ " MPI.Waitall(reqs)\n",
+ " for i in 2:(n_own+1)\n",
+ " u_new[i] = 0.5*(u[i-1]+u[i+1])\n",
+ " end\n",
+ " u, u_new = u_new, u\n",
+ " end\n",
+ " return u\n",
+ " end\n",
+ "end"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6eab32d0",
+ "metadata": {},
+ "source": [
+ "In order to check the result, we will compare it against the serial implementation. To this end, we need to define the serial implementation also in the workers."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f1a8db8f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "@everywhere workers() function jacobi(n,niters)\n",
+ " u = zeros(n+2)\n",
+ " u[1] = -1\n",
+ " u[end] = 1\n",
+ " u_new = copy(u)\n",
+ " for t in 1:niters\n",
+ " for i in 2:(n+1)\n",
+ " u_new[i] = 0.5*(u[i-1]+u[i+1])\n",
+ " end\n",
+ " u, u_new = u_new, u\n",
+ " end\n",
+ " u\n",
+ "end"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d2b04c67",
+ "metadata": {},
+ "source": [
+ "Finally, we call the parallel function on the workers, gather the results on the root rank, and compare against the sequential solution."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "68851107",
+ "metadata": {
+ "code_folding": []
+ },
+ "outputs": [],
+ "source": [
+ "@everywhere workers() begin\n",
+ " # Call jacobi in parallel\n",
+ " niters = 10\n",
+ " load = 4\n",
+ " nranks = MPI.Comm_size(comm)\n",
+ " n = load*nranks\n",
+ " u = jacobi_mpi(n,niters)\n",
+ " # Gather results in root process and check\n",
+ " rank = MPI.Comm_rank(comm)\n",
+ " n_own = div(n,nranks)\n",
+ " if rank == 0\n",
+ " results = zeros(n+2)\n",
+ " results[1] = -1\n",
+ " results[n+2] = 1\n",
+ " rcv = view(results, 2:n+1)\n",
+ " else\n",
+ " rcv = nothing\n",
+ " end\n",
+ " MPI.Gather!(view(u,2:n_own+1),rcv,comm;root=0)\n",
+ " if rank == 0\n",
+ " @show results ≈ jacobi(n,niters)\n",
+ " end \n",
+ "end"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "eff25246",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "Question: In function jacobi_mpi, how many messages per iteration are sent from a process away from the boundary?\n",
+ "
\n",
+ "\n",
+ " a) 1\n",
+ " b) 2\n",
+ " c) 3\n",
+ " d) 4\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "98bd9b5e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "answer = \"x\" # replace x with a, b, c or d\n",
+ "jacobi_2_check(answer)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "075dd6d8",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "Question: At the end of function jacobi_mpi ...\n",
+ "
\n",
+ "\n",
+ " a) each rank holds the complete solution.\n",
+ " b) only the root process holds the solution. \n",
+ " c) the values of the ghost cells of u are not consistent with the neighbors\n",
+ " d) the ghost cells of u contain the initial values -1 and 1 in all ranks"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c3b58002",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "answer = \"x\" # replace x with a, b, c or d\n",
+ "jacobi_3_check(answer)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c9aa2901",
+ "metadata": {},
+ "source": [
+ "### Latency hiding\n",
+ "\n",
+ "Can our implementation above be improved? Note that we only need communications to update the values at the boundary of the portion owned by each process. The other values (the one in green in the figure below) can be updated without communications. This provides the opportunity of overlapping the computation of the interior values (green cells in the figure) with the communication of the ghost values. This technique is called latency hiding, since we are hiding communication latency by overlapping it with communication that we need to do anyway.\n",
+ "\n",
+ "The modification of the implementation above to include latency hiding is leaved as an exercise (see below).\n"
+ ]
+ },
+ {
+ "attachments": {
+ "fig16.png": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAA6gAAADyCAYAAABAvOgkAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAewgAAHsIBbtB1PgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURBVHic7d17nFxlYf/xz9lssrmQm4EQkhACRi7h1kD4YSIqEGuwEi4tSNEakWpqBWuLtEKrgtW2WkDbn1VJ0Cra3w8lKMKGCIJc1BBCxCgghGsSJIEk5ELut93pH89s9uxkZnez7M7z7O7n/XqdV84zc2bmm9nNZL97Lk82e/bsApIkSZIkRVYTO4AkSZIkSWBBlSRJkiQlorZkPD3LsuUxgkiSJEmSepdCoTAeuKdp3KKgZlm2fNasWc9WO5QkSZIkqfeZM2cOhULzZZE8xFeSJEmSlAQLqiRJkiQpCRZUSZIkSVISLKiSJEmSpCRYUCVJkiRJSbCgSpIkSZKSYEGVJEmSJCXBgipJkiRJSoIFVZIkSZKUBAuqJEmSJCkJFlRJkiRJUhIsqJIkSZKkJFhQJUmSJElJsKBKkiRJkpJgQZUkSZIkJcGCKkmSJElKggVVkiRJkpQEC6okSZIkKQkWVEmSJElSEiyokiRJkqQkWFAlSZIkSUmwoEqSJEmSkmBBlSRJkiQlwYIqSZIkSUqCBVWSJEmSlAQLqiRJkiQpCRZUSZIkSVISLKiSJEmSpCTUxg4gSZKSMxB4b3F9PrA1YpZq619cdtO7/t6SlAQLqiRJKjUCuLW4/mbgxQ4+Tw3wFmAyMAkYXLz9CtItf/8IfBa4H5gWOYsk9ToWVEmS1BW+AXwAGFLmvqtJt6DG8F7gHOA54PrIWSQpKguqJEkqtRG4qri+voPPMZHmcrqy+JzHvsFc1fAH4BHg91V8zZOAWcAvqF5BPRQ4ObccCWTAbcCnq5RBkvZhQZUkSaU2A19+g89xE3Ad8BjwKvAh4Ltv8Dmr4abi0pNNBhZXuO+gagaRpFIWVEmS1BX+X+wA3ci3CBej2rKfjzsSOJFwjvBjHXjdDcCvi4/9IDCmA88hSZ3KaWYkSVKpQ4FCcTkicpZquwR4ALihiq/5CqEkPrOfj5tBuJjVrP183LOEi1+9CXg34Zzgdfv5HJLUJdyDKkmS1OwI4HSgsYqvOZFwfu5a4MEqvN6m4iJJyXEPqiRJUlx/RtgT+vnYQSQpNguqJEmSJCkJFlRJkiRJUhI8B1WSJLXXUGBChfuew/MaJUlvkAVVkiS11zuAOyvcdxZwTxWz9BZHUPkqvacU/zwV+FKFbW4HFnV2KEnqKhZUSZLUXhupPN+me0+7xjjg021sc2JxKWcZFlRJ3YgFVZIktdcvgcmxQ/QyLwFfrnDfKcCZwO+Auyts89uuCCVJXcWCKkmSlK4Xgasq3PcpQkFd1Mo2ktSteBVfSZIkSVIS3IMqSZK6womECyc1OSm3/klgW278dWBLNUJJktJmQZUkSV2htSvLfq5k/D0sqJIkLKiSJGlfW4A5xfWOXp336dxztGVb25tUzRZgLbAhdpAu9h1gbG58RPHP6cC9udsfAP61WqEkyYIqSZJKbQD+6g0+xy+LS3fz78Wlp5sCHFXm9tHFpcna6sSRpMCCKkmSFNePgKXsfxmsJ0xD82IHXvNvgcHt2O6lDjy3JHWYBVWSJCmup4rL/nq2uHREpXlTJSkqp5mRJEmSJCXBgipJkiRJSoIFVZIkSZKUBAuqJEmSJCkJFlRJkiRJUhIsqJIkSZKkJDjNjJJXKBSy+vr6R2PnaM0NN9xw2LJlywYCbN68+bUNGzakNrH5gcBBxfXtwPJ4UcoaAIzPjZ8BGuNEKW/cuHFHUfyl3iWXXPLy5MmTN0eOVFGWZbfOmDHjumq8Vn19/XcLhcKx1Xitjpg7d+7Ihx56aATAzp07t65evTq1OR0HAeNy46djBWnFMbn1l4CtsYJUMI7wPgKsA9ZEzLKPYcOGjRgyZMhIgDFjxuy4+uqrl8XO1Jq6urrTp0+f3uVf47vuuusdDQ0NN3T163TUjh07aq688sqjmsarV69evnPnzu0xM5UxnvD/J4Q5dF+LF6Wsgwg/fwBsA1ZEzLKP/v37Dxw5cuRhTeOvfe1rT2dZFjNSWz5xzjnnPBI7RDVYUJW8z3/+89nJJ588OXaO1qxfv56XXtr7c+8g4LBWNo9tEM3/YaTqpNgBSuW+vjQ0NByV+H9ii6r1QoVC4dgsy5L997l9+/bSf5sjI8Zpj2Tfy6Jj2t4kqtLCH93GjRvZuHEjAP369RuUZdmIyJFaVVNTU5WfDRsaGoan/NmRZVmLz30g2V/EFQ2i5S96UzOI5l+UJ2HHjh2lX+PJKf/fnmXZ0NgZqsVDfCVJkiRJSXAPqrqjm7IseyF2iLzdu3dfQ/EwmzFjxqxYuXLlNyNHKvWXwFuK67uAz0XMUs5k4ILc+IvAlkhZyqqtrf3Snj17AFi/fv0DWZbdEzlSqZmFQmFizACFQuHumpqaB2NmKLV58+aPU9yjNmLEiO3r1q37fORIpd4B/ElufFWsIK34UvPqmIfh4CXxopTz249AY11x8BLwjZhpSo0dO/bSl19++UiAhobCnhdfHDY7dqa8gQN3jRw1atuFkWPszrLss5EztLBz584DgM80jUeNGnXbq6+++uuIkcr5Z6Bfcf154FsRs5TzMZr36u4Aro2WpIzRo0eftmrVqrObxnv27Pmnurq6hpiZShUKhX8D0t2t20UsqOp2CoXCD2bMmHF/7Bx5EyZMuIpiQR03btzKlStXfjlypFLvormg7gZSy/cRWhbU/wJWR8pSVp8+ffYW1GXLli06++yzk3oP582bNwWIWlCzLPtFau/L1KlTz6NYUIcPH75j3bp1SeUjnGudL6j/DhQiZakkV1An/gZ+9v14Ucrp+8FcQV1FYp9vRxxxxJnNBbVhzyc/edr3YmfKu/TSpyaef/6L0Qtqap8dF1988cHkCuopp5xyT319fWoF8DM0F9QVJPa9D8ygZUFNKt/JJ5+8K19Qf/CDH1w/d+7cXTEzlaqvr/9XemFB9RBfSZIkSVISLKiSJEmSpCRYUCVJkiRJSbCgSpIkSZKSYEGVJEmSJCXBgipJkiRJSoIFVZIkSZKUBAuqJEmSJCkJFlRJkiRJUhIsqJIkSZKkJFhQJUmSJElJsKBKkiRJkpJgQZUkSZIkJcGCKkmSJElKggVVkiRJkpQEC6okSZIkKQkWVEmSJElSEmpjB1CXGQBMzI2XAI1tPKYGmJQbPwVs7+RckiRJklSWBbXnOgL4dW48kLbLZr+SxxwPPNnJuSRJkiSpLA/xlSRJkiQlwYIqSZIkSUqCBVWSJEmSlAQLqiRJkiQpCRZUSZIkSVISLKiSJEmSpCRYUCVJkiRJSbCgSpIkSZKSYEGVJEmSJCXBgipJkiRJSoIFtecqlIyzdjzmgK4IIkmSJEntYUHtubaUjNtTPg/riiCSJEmS1B4W1J5rPS33ok5ox2Pe1UVZJEmSJKlNFtSeawuwLDc+q43tBwOXdV0cSZIkSWqdBbVnuy+3fjkwpsJ2dcDNwKFdnkiSJEmSKrCg9mxzgMbi+nDgl8AFNJ+PeiBwEfAocD7w22oHlCRJkqQmtbEDqEs9BnwN+GRxfDgwt7jeSMtfUKwilNfnq5au4y6rr68/N3aIvH/4h38Y0LT+/PPPHw78Z8Q45RyZW+9HevmOLRl/EdgWI0gle/bs2bs+YcKEd9XX1w+MGGcfhULh+NgZgLPr6+tHxQ6R981vfnN80/ratWsHkN73/kkl4/+IkqLdfvt2mHBQ7BQtNfbPDcaT2Nd46dKlRzet9+lT0/emmx64ImaeUn377nlT7AxAv/r6+qS+bps2bRp4yy237B0vWLDgz4EUPmfz6nLrR5LY9z7h584myX3+Lly4cFJ+/P73v/+GmTNnNlbaPpL2zMLR41hQe75PFf/8BC0LaX79V8CfA+uqFeqNyLLsT2NnKFVb2/xPae3atYcAfxMvTZv6knY+gI/EDlCqoaFh7/rQoUMnA5PjpUnW1OKSjIEDm3+P8Prrr/cn/e/9xPOtPTEsyRpFYu/hmjVr9q736VPTZ9SorRdFjJOqWhL7uvXr16/FeP369dOAaXHStMuhJPYelqgjsXyvvfZai3FNTc3lkaKohAW152sA/hb4NqGEngQcRLjK7wuEPaoPEvao9gHel3vsH6oZVJIkSVLvZkHtPZ4oLq1poPkQ4GRcc801hXnz5t0ZO0drRo8efXRDQ8MQgA0bNqx65ZVXXo6dqcRoYGxxfQvwVMQs5RwATMyNf03z+dNJOProo0+uqanpA9C3b9/ngA2RI1WUZdnjVXythwinCCRpyJAhYydOnDgaYPv27ZuWLVu2NHamEkOAo3PjR2MFacX/ya0vBTbFClLBUcDQ4vorJPbL1YMPPviQESNGHFpc3wr8PnKkVu3YsWN3NV4ny7JXgJT/b6+ZOHHi3iNlVqxY8dTWrVtL55iPbSLN1xV5mfQ+i8cSfv6A8LmR1OfvAQccMHjcuHHHNI0LhcJiWk7RmJSGhoY1bW/VM2SzZ8/e+4XIsuyoWbNmPRszkCRJkiSpd5gzZ86RhULhmaaxV/GVJEmSJCXBgipJkpS+fm1vIkndnwVVkiQpfZ8lXHX/wNhBJKkrWVAlSZLS9yvgZOBx4MzIWSSpy1hQJUmS0rcQeB04BLiLMH1c36iJJKkLWFAlSZLStwnYU1zvD7wfWAIcES2ROqp/7ABSyiyokiRJ3cP9ufX+hHkwFwJ/ESeOOqA/4XDt2XjhK6ksC6okSVL3cCewNTfOgJHAfxXvGxgjlPbLjYRzic8DDoqcRUqSBVWSJKl7eAjYUub2ocB04GnghKom0v74OPAhoIGw13tl3DhSmiyokiRJ3cNaYFeF+/oB4wiHAV9VtURqrynAV4vrnwbujZhFSpoFVZIkqft4rI373wR8Abi9ClnUPgcDtxJ+iXA78JW4caS01cYOIEmSpHa7A3gPUFdy+zbgeeA24DfA+irnUnm1wA+BscAzwCVAIWYgKXUWVEmSpO7jQWAjYa8chEN++xGuDnslHjqamn8H3glsBs4nTBckqRUe4itJktR9LCdcZAfCFX1/Cnyb8DPd94FD4sRSGRcBf0fYY/phwkWsJLXBgipJktS9PEUoPU8DfwZ8AniCsFf1Zvz5LgWTCb84APg34EcRs0jdih9gkiRJ3cudhL2oFxT/3E7YW7cV+GPgc/GiCRgP1AODgPn49ZD2iwVVkiSpe5kP/F9gRe62p4GPFdc/B5xX7VACYDjh6zOKcLGqi2g+JFtSO1hQJUmSupcXgE+Vuf1/CHNtZoTzUY+rZijRD5gLHAOsBM4FtkRNJHVDFlRJkqSe4++Bu4EDgB8Dw+LG6TUy4FvANMKVev8EeDlqIqmbsqBKkiT1HA3AXwDLgLcA/x9/3quGLwIfJEz7cz7weNw4UvflB5YkSVLPso5wDupW4D3A1+PG6fH+EvhHwpWVZwH3x40jdW8WVEmSpJ7nceASoJFw8aQroqbpud4L3Fhcv4YwzY+kN8CCKkmS1DPdRjgnFeA64MKIWXqiMwnvcS3w38AX4saRegYLqiRJUs/1FeA/CT/zfQ84LW6cHmMKcAfQH/gJ8Fdx40g9hwVVkiSpZ7uCcEXfpjLl9DNvzCnATwlXSp5PmOt0T9REUg9iQZUkSerZGglX9n0YGAHcCxwVNVH3dSrwM2Ao4WJIFxCu3Cupk1hQJUmSer7thAv6/AYYBfwceHPURN3PaYRyOgz4BXAu4X2V1IksqJIkSb3DRmAasAQYAzwAjI8ZqBt5B+Fw3iHAQ4SyvyVqIqmHsqBKkiT1HhsJc6MuBQ4F7sOS2pZzgLuBwcU/34PlVOoyFlRJkqTeZTXwLuB5wmG+vwSOiZooXR8lXGBqAOGqvefhYb1Sl7KgSpIk9T4rgbcDvwPGAguAt0ZNlJ5PA3OAPsB3CRdE2hkzkNQbWFAlSZJ6p1eBM4FFwHDCBYDOiJooDXXAd4AvFcf/DHwYp5KRqsKCKkmS1HutJxzuex/N51heGjVRXAcTpo+5hFBIPwZcEzOQ1NtYUCVJknq3LcDZwA+BfsC3gRsIh7b2JpOAR4GphOL+HmB21ERSL2RBlSRJ0k7gYuAqoBG4AriLMOdnbzAT+BUwDngOeBthr7KkKrOgSpIkCaAAfBl4H7AVmA4sBibHDNXFhgFzgZuBgcCdwCmEaXgkRWBBlSRJUt6PCFf4XQ5MIFzh91NAFjFTVzgd+A3h6ry7gL8jTCPzesRMUq9nQZUkSWr2l4R5Qb8aO0hkSwjnZN5KOC/1emA+cEjMUJ3kAOC/CBdDOhx4gXBI738Q9iJLisiCKkmS1Oww4DTghNhBErARuAj4KLANOAt4Gvg43fdnyLOAx4HLiuMbCUX819ESSWqhu364SJIkqTq+RTgP9RFgKPB1wgWFjo8Zaj+9hXB+6U8Je02XEabX+Wtgc8RckkpYUCVJkpp9CTgIOD92kMQ8TTgM9jLCOZpTCOdv3gSMjZirLQcRDk9+EphBONf0esIe8vsj5pJUgQVVkiSp2TbgNWBT7CAJagS+AUwkXPm2FvgIYVqW64ED40Xbx0jgOsKe0k8RzqO9i7DX9+8Jc79KSpAFVZIkqdkUwhygF8QOkrBVhKlopgAPAP0JJXAFMIe4h/5OKmZYBlwJDAIWEabMORt4Nl40Se1hQZUkSWr2HuAGwrmJat0jwJnAuwklcCDhgkqPE4rrTMI8o13toOLrPkI47PijxSyPAH8CvBX4WRVySOoEtbEDSJIkqVu7t7hMBf4G+FPCHKOnE875vBf4MfAQYUqXN6oGOBY4g3Cu8NuBPsX7dhLmcb2RMF2QpG7GgipJkqTO8HBxGU2YT/YCwsWI3ltcAF4FFhD2dL5QXFYA6wjnuOb1J1w1+AjgSMKVeCcRinB+z2wBWEyYs/VmYG3n/rUkVZMFVZIkSZ1pFfCF4nIUoaieRZiqZhTwZ8Wl1G6aL140mNZ/Tt0MLCRMG/Nj4KXOCC4pPguqJEmSusozwL8UlzpCSZ0KHAO8mbB3dAyQAX2B4SWPbySUz+cJVwt+irAH9nGgoevjS6o2C6okSZKqYSehXC4oub0PMAQYQDisNyNM97MVp/uReh0LqiRJkmJqADYUF0m9nNPMSJIkSZKSYEGVJEmSJCXBgipJkiRJSoIFVZIkSZKUBAuqJEmSJCkJFlRJkiRJUhKcZkaSJKnZbOAu4PXYQSSpN7KgSpIkNVtZXCRJEXiIryRJkiQpCRZUSZIkSVISLKiSJEmSpCRYUCVJkiRJSbCgSpIkSZKSYEGVJEmSJCXBaWbULdx+++3jY2doTX19/YEbNmzoD7Bq1apNixYt2hQ7U4nBwNDi+i5gTcQs5fQDRubGK4FCpCxlnXvuuaNrampqAE466aR1xx133PbYmSoZMGDAprPOOmt9NV7r7rvvPmT79u111XitjliwYMGQF154YQjAli1bdtx7772vxc5Uog44KDd+OVaQVozNra8FdsYKUsGBQP/i+mYSm790ypQpg0eNGjUUYPDgwbvOP//81D5/WzjvvPNWZFnW5Z+/t95664C+ffse3NWv01G7d+/ObrnlljFN40cffXTNypUrd8XMVMZIwv+fEL7vN0fMUs6Q4gLhc2NtxCz7OPzww+v+6I/+aO/n78yZM1P8/N1r2LBhr55xxhk7YueoBguqknfttdfW1NbWLoudozWLFy/miSeeiB1DXeiOO+7Yuz5x4kRqa9P9+Ny9e/fXgcur8Vq7du26s7a2dnI1Xqsjli9fzu233x47hnqxhQsX7l2fMGECF154YcQ0bbvvvvuGUYWS379//3dnWfaTrn6djtqzZ4+fHT3csmXLWLas+cfLSy65hOLvoZO0devWs4B7YueohnS/CpIkSZKkXsWCKkmSJElKQrrHqEmV/UWWZQvb3qx6duzYsYTieRaTJk16bMmSJe+LHKnUzcBpxfVtwPERs5TzPuDfcuNTgaTOFayrq3th585w6t3SpUtvnDJlynWRI5W6sVAo/HHkDNdlWXZj5AwtrFmz5jZgEsD48eNfX758+UmRI5X6KHDV3tFX+Xi8KBX8Hd/Yu/5Obuc87o2YZl9/z/XsYWAYjH4W/vGq1h9QXUceecdnn3323kkAu3fv3p5l2XGxM+U1NjYen8Chtsm9L3/4wx8OBBY1jU899dSrFy1adGvESOU8AU3f+ywAZkbMUs4PgaZTQDZR/CxOxdSpUy99+OGH/6lpvGnTpqOHDx++O2amUoVC4Tl64Q5FC6q6nUKh8MqMGTNejJ0jb8KECY1N6/37998JJJUPyJ9UXyC9fKVldAWwOkaQ9ti6devGs88+O6n3cN68edtiZwA2pPa+TJ06de8FfWpraxtJ73u/5cWszmQNNWldIKyFYWzhXYn928xobB7U7oLLVsYLs6+6uvv3fg8WCoXG1P6N3HnnnSNiZwAKqb0vF1988db8eOTIka+R3udH7nufHaSXL39BteQ+f0eMGLEuP77zzjuXzZ07N6kLYdXX18eOEEWva+SSJEmSpDRZUCVJkiRJSbCgSpIkSZKSYEGVJEmSJCXBgipJkiRJSoIFVZIkSZKUBAuqJEmSJCkJFlRJkiRJUhIsqJIkSZKkJNTGDtDDZcCbgTFAf2AV8BTQ8AafdwIwChgMbAReBFa/weeUJEmSpKhS34M6Hnghtwxux2NqSh5zXFeFK/p47rX+J5fhE8BS4DngQeBu4HFCkfwCMHA/X+dQ4OvAy8Xn/CUwH3gYeAX4DfBhWv+aXp3L+p9tvN4/0fJ9/HAb29+W2/ZDbWwrSZIkSftIfQ9qX+CI3Li9hTr/mLrOi1PW8NzrLSeU6NuAd1fYfgTwGeBdwHRgUzte4zLgesJe2HIyYBLw34QieR6wvsx2T+ayXgj8LVCo8Jzn0/J9PAf4ToVtDwBmAP2K499W2E6SJEmSKkq9oHY3NYS9qO8mHMb7EPAYsBk4DDgXOLC47VsJpXNWG895LXBNbrwV+CmwBHgdGEkouqcW7387cC/wNmBHyXM9BOwhfN0PASYCvy/zmm8iFN6804E+lD88+e00l9O1hD3FkiRJkrRfLKid6zTCe/p74CL2LX9XAj8CziyOLwW+CLxU4flmAJ/LjX9I2Ju6rmS7awh7RL9LOHT4JOBfgE+VbLcJWAxMKY6nlckIcAbNe6t3EvZCDwNOBh4ts/203Pr9VN4rK0mSJEkVpX4OandTC6wkFLxyxW8j8H5gS3Hch1Asy+lLOOc0K45vBS5m33LaZC7wkdz444S9q6V+nlufVub+0ttv3M/t76+wjSRJkiS1yoLa+T5DOMy1ktXAvNx4coXtLiRcGAlCob2ctvdM3kLYQwrhfNWLy2yTL5DvpPxe9KbCuRn4MtBYcnvegcAJufHPy2wjSZIkSW2yoHau3YQ9nW15LLc+vsI25+TW76D10pv349z628vc/zCwvbg+FDil5P5DgSOL678gXCH4d8Xx24ABJdufSfP30XLCVXwlSZIkab9ZUDvXM8C2dmy3Jrc+tMI2+XK5cD8yLM2tH1Pm/p3Ar3Lj0r2i+fF9xT+b9or2B6a2sr17TyVJkiR1mBdJ6lzlpnYpZ1duvV+Z++uA0bnxFbQ9DymEKW7yc8W+qcJ2Pwf+uLg+jXChJnLj/HZNf16Zuz9fRM8ss70kSZIk7TcLaufa00nPM7xkfETZrdo2qMLt+fNQpxCu/Nu057epcL5KmDcV4JeEUt2PlgX2MGBCcb0APNDBnJIkSZJkQU1Un5Lx/VS+em9rtle4/TfABkIRriNMj/MzwryoTXtu89PFbAUeAd5BmGpmePHx+bL6e0KplSRJkqQO6YkFtdwhs91NaRn9GvCTTnz+BuBB4PzieBqhoLZ2uO7PCQW1D3A6cDuefypJkiSpE6V+kaQdJeP+7XhMubk/u5sdtNwbeUKlDd+AcvOhlrtAUqXtMzz/VJIkSVInSr2gbioZH9KOx5zaFUEiyJ/PeU7FrTouXygnEYr96cXxc8BLJds/SpgXFUJBPRYYVRzvAR7qgoySJEmSepHUC+rrwOrc+G3teMxHuyhLtf0ot34y8J5Ofv6lwMrieg3hKr3DiuNye0N3E+ZFBTgamJm779fs+8sESZIkSdovqRdUaDkH6Mdo/bzZD9M8fUp3dzvhwkNN/hs4fD8efxDNhbOS/NV8L8+tlx7e2yRfXC+vcLskSZIkdUh3KKjfz60fB9wMDCnZZiDwOeAmYEuVcnW1RkLhbjoPdxSwGLiUyheC6gOcAcwBVgBvbuM18sVyQO51K00XU2770tslSZIkqUO6w1V8fwIsoPnw3vcDZxdve52wp/CthDk/GwmHnv64+jG7xGLC3+d7hAtEjQC+DXyFsGf5ZUKBHUbYu3oicMB+PH+5YrkEWF9h+yeANbS8ENV2Wu7lliRJkqQO6Q4FtRG4CLgXOKZ42xD2PSdzG/DXwB3Vi1YVc4HlwHcJ85QCDAXOauNxrwAb29jmZeAZ4KjcbZUO74UwL+r9wJ/nblvAvldbliRJkqT91h0KKoSL+ZwCXAF8gJaFagOhxP0H8DRh+pO5Jfd3padzr/dkOx/zUu4xq1vbsGgxcDzwp8AFhMN4S6fT2UK48NEvgHsIe0cb2vHcXwPemRv/qNKGRd8nHErc3u0lSZIkqV26S0EF2Ap8obgMJhzau5F9D0ctAO+rYq4fs/+HFD9cXPZHI3BbcYFwSPMIwtew3PvQXl8vLu01v7hIkiRJUqfqTgU1bzPNc3L2VluLiyRJkiT1CN3hKr6SJEmSpF7AgipJkiRJSoIFVZIkSZKUhO56DmpHfRAY0AnP8yzwYCc8jzogy7Jb6+vrd8XOkXf11VcPaVpfvHjxZGBVxDjlvCm3PpD08pX+u/wd4cJgydi1q/lbbtKkSZdfdtllH4oYZx+FQmF47AzA1fX19Z+IHSLvO9/5zoim9WXLlg0lve/9QS1Gp3JTpBztM58LuIezY8doYU/+PVx5NPS7K16YfT31VDa0ab1fv34D6+vrNL98BAAAAwFJREFUU/se7Bs7ADAgtfdl27ZtNbfccsve8fz5868D/jleorLynx+nkd7n24jcenKfv/Pnz2/x+fuBD3xg+cyZM2PFqaRX7kzsbQX1OuDgTniem7GgxjSi7U2qK8uyvet79uzpBxwSL02bMtLOB53z77RTFQqFves1NTUHAAfES5OswcUlGTU1zf+3NzQ01JD69/4OUvhFQ2W7GcDuTvlFbxdpqIWGA2OnyGtoOeFbd/j8jSG59yX/2QHQ0NAwDBgWJ0271JHYe1giua9xQ+k/zixLKl9v1itbuSRJkiQpPb1tD+pRdE4pT+rw0p7u2muvbZw3b94HY+dozQknnHDs6NGjhwGsXLnyD08++eRLsTOVGAscVlzfDDweMUs5g4ETcuNHgIYK20Yxbdq0t9bW1vYBGDRo0NNZlnV07uEu19jY+Ey1XqtPnz6fLRQKSe2xyhs9evRh06dPHwuwadOmjQsXLvx97EwlhgLH5cYLYgVpxdty608Cr8cKUsGxNO/ZehlYETHLPo455pgx48aNGw8wbNiwzVmWpfb528Lw4cO3VeN1amtrH2tsbEz2//Ysy/pMnz79rU3jJUuWPL5mzZrUpjg8Hmg6xWgF4fs/JeOAQ4vrrxM+P5IxatSooSeeeOLez98syx7OsqzQ2mNiqq2tTfqzozNls2fP3vuFyLLsqFmzZj0bM5AkSZIkqXeYM2fOkYVCYe8v1z3EV5IkSZKUBAuqJEmSJCkJFlRJkiRJUhIsqJIkSZKkJFhQJUmSJElJsKBKkiRJkpJgQZUkSZIkJcGCKkmSJElKggVVkiRJkpQEC6okSZIkKQkWVEmSJElSEiyokiRJkqQkWFAlSZIkSUmwoEqSJEmSkmBBlSRJkiQlwYIqSZIkSUqCBVWSJEmSlAQLqiRJkiQpCRZUSZIkSVISLKiSJEmSpCRYUCVJkiRJSajNDwqFwvg5c+bEyiJJkiRJ6kUKhcL4/Li25P57CoVC9dJIkiRJklTkIb6SJEmSpCRYUCVJkiRJSfhf0SSRmgN3HHEAAAAASUVORK5CYII="
+ }
+ },
+ "cell_type": "markdown",
+ "id": "7d66b1a2",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "\n",
+ "
\n"
+ ]
+ },
{
"cell_type": "markdown",
"id": "9d4de5a9",
@@ -497,7 +816,7 @@
"source": [
"### 1D block partition\n",
"\n",
- "The following figure shows the portion of vector `u_new` is updated at each iteration by a particular process (CPU 3) left picture, and which entries of `u` are needed to update this data, right picture. We use analogous figures for the other partitions below.\n"
+ "The following figure shows the portion of vector `u_new` that is updated at each iteration by a particular process (CPU 3) left picture, and which entries of `u` are needed to update this data, right picture. We use analogous figures for the other partitions below.\n"
]
},
{
@@ -594,6 +913,20 @@
"- Communication/computation ratio is $O(1)$"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "3d0693a7",
+ "metadata": {},
+ "source": [
+ "### Summary\n",
+ "\n",
+ "|Partition | Messages per iteration | Communication per worker | Computation per worker | Ratio communication/ computation |\n",
+ "|---|---|---|---|---|\n",
+ "| 1d block | 2 | O(N) | N²/P | O(P/N) |\n",
+ "| 2d block | 4 | O(N/√P) | N²/P | O(√P/N) |\n",
+ "| 2d cyclic | 4 |O(N²/P) | N²/P | O(1) |"
+ ]
+ },
{
"cell_type": "markdown",
"id": "850b1848",
@@ -601,6 +934,8 @@
"source": [
"### Which partition is the best one?\n",
"\n",
+ "\n",
+ "\n",
"- Both 1d and 2d block partitions are potentially scalable if $P<\n",
- "\n",
- "
"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "0148f9b3",
- "metadata": {},
- "source": [
- "Thus, the algorithm is usually implemented following two main phases at each iteration Jacobi:\n",
- "\n",
- "1. Fill the ghost entries with communications\n",
- "2. Do the Jacobi update sequentially at each process"
- ]
- },
- {
- "attachments": {
- "fig15.png": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAAA6gAAAFACAYAAACxyVHuAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAewgAAHsIBbtB1PgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURBVHic7N15fFT19f/x12cmIQFkE1wANxBFrTu4b1itimQC2mKrtfrtRtXazbbWrmJba+1m7S5fv9Wqrbb5VWBugFatpe4buGvdFVcU2cISSOZ+fn+cO2QyTCAsmXszeT8fj/vIvXPv3Dm5M3PnnvvZ3DXXXOMRERERERERiVkq7gBEREREREREQAmqiIiIiIiIJERV0fLJzrlX4whEREREREREehbv/W7AP/PL7RJU59yrU6ZMeb7cQYmIiIiIiEjPM23aNLxv6xZJVXxFREREREQkEZSgioiIiIiISCIoQRUREREREZFEUIIqIiIiIiIiiaAEVURERERERBJBCaqIiIiIiIgkghJUERERERERSQQlqCIiIiIiIpIISlBFREREREQkEZSgioiIiIiISCIoQRUREREREZFEUIIqIiIiIiIiiaAEVURERERERBKhKu4ARDbGe+8aGxsvizuODbnuuusOWLhw4SCAhQsXvvbiiy++EndMRXYBRkbzy4H5McZSSn/g4ILlu4FcTLGUdPjhhx+dTqerAE455ZSnDjzwwEVxx9QR59yDdXV1s8rxWtls9nPOueHleK3NMXv27BFPPPHErgArV65c8thjjz0ed0xFBgEHFCzPjSmODRlXMP84sCSmODpyAHYcARYAL8cYy3pGjBixy7Bhw0YCbLvttsunTJmStPNvO+l0+vJTTz11TVe/ThAEewFndfXrbK7m5ub0L3/5y2Pyy88+++z8xYsXL48zphIOxn4/wT73C2KMpZQRwK7R/FLgsRhjWc/gwYMH7rXXXgfmly+55JK5MYazUWEY3jBx4sQX446jHJSgSuJddtllbsyYMd+NO44NeeGFF3jyySfzi8dsaNuEmBB3ABuRuGP4wAMPrJsfN25c4uIr5L3/LVCWBBX4DDC2TK+1yd577z3uvffewofq44qlkxL92SL58SXOK6+8wiuv2D3LUaNGQcLPv9XV1T8HujxB9d6Pds4l+re96NyR9M9+0uMDyMQdQKH333+/3XschuExqVRyK5em0+l7gR6RoCb3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"
- }
- },
- "cell_type": "markdown",
- "id": "baccd833",
- "metadata": {},
- "source": [
- "
\n",
- "Question: How many messages per iteration are sent from a process away from the boundary?\n",
- "
\n",
- "\n",
- " a) 1\n",
- " b) 2\n",
- " c) 3\n",
- " d) 4\n",
- "\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "98bd9b5e",
- "metadata": {},
- "outputs": [],
- "source": [
- "answer = \"x\" # replace x with a, b, c or d\n",
- "jacobi_2_check(answer)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "075dd6d8",
- "metadata": {},
- "source": [
- "
\n",
- "Question: After the end of the for-loop (line 43), ...\n",
- "
\n",
- "\n",
- " a) each worker holds the complete solution.\n",
- " b) the root process holds the solution. \n",
- " c) the ghost cells contain redundant values. \n",
- " d) all ghost cells contain the initial values -1 and 1. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "c3b58002",
- "metadata": {},
- "outputs": [],
- "source": [
- "answer = \"x\" # replace x with a, b, c or d\n",
- "jacobi_3_check(answer)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "4537661d",
- "metadata": {},
- "source": [
- "
\n",
- "Question: In line 35 of the code, we wait for all receive and send requests. Is it possible to instead wait for just the receive requests?\n",
- "
\n",
- "\n",
- " \n",
- " a) No, because the send buffer might be overwritten if we don't wait for send requests.\n",
- " b) No, because MPI does not allow an asynchronous send without a Wait().\n",
- " c) Yes, because each send has a matching receive, so all requests are done when the receive requests return. \n",
- " d) Yes, because there are no writes to the send buffer in this iteration."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "e16ea5eb",
- "metadata": {},
- "outputs": [],
- "source": [
- "answer = \"x\" # replace x with a, b, c or d.\n",
- "jacobi_4_check(answer)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "c9aa2901",
- "metadata": {},
- "source": [
- "### Latency hiding\n",
- "\n",
- "Can our implementation above be improved? Note that we only need communications to update the values at the boundary of the portion owned by each process. The other values (the one in green in the figure below) can be updated without communications. This provides the opportunity of overlapping the computation of the interior values (green cells in the figure) with the communication of the ghost values. This technique is called latency hiding, since we are hiding communication latency by overlapping it with communications that we need to do anyway.\n",
- "\n",
- "The modification of the implementation above to include latency hiding is leaved as an exercise (see below).\n"
- ]
- },
- {
- "attachments": {
- "fig16.png": {
- "image/png": "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"
- }
- },
- "cell_type": "markdown",
- "id": "7d66b1a2",
- "metadata": {},
- "source": [
- "
\n",
- "\n",
- "
\n"
- ]
- },
{
"cell_type": "markdown",
"id": "47643bf6",
@@ -917,72 +959,7 @@
"source": [
"### Exercise 1\n",
"\n",
- "Transform the following parallel implementation of the 1d Jacobi method (it is copied from above) to use latency hiding (overlap between computation of interior values and communication)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "66db180d",
- "metadata": {},
- "outputs": [],
- "source": [
- "@mpi_do manager begin\n",
- " using MPI\n",
- " comm = MPI.Comm_dup(MPI.COMM_WORLD)\n",
- " nw = MPI.Comm_size(comm)\n",
- " iw = MPI.Comm_rank(comm)+1\n",
- " function jacobi_mpi(n,niters)\n",
- " if mod(n,nw) != 0\n",
- " println(\"n must be a multiple of nw\")\n",
- " MPI.Abort(comm,1)\n",
- " end\n",
- " n_own = div(n,nw)\n",
- " u = zeros(n_own+2)\n",
- " u[1] = -1\n",
- " u[end] = 1\n",
- " u_new = copy(u)\n",
- " for t in 1:niters\n",
- " reqs = MPI.Request[]\n",
- " # Exchange cell values with neighbors\n",
- " if iw != 1\n",
- " neig_rank = (iw-1)-1\n",
- " req = MPI.Isend(view(u,2:2),comm,dest=neig_rank,tag=0)\n",
- " push!(reqs,req)\n",
- " req = MPI.Irecv!(view(u,1:1),comm,source=neig_rank,tag=0)\n",
- " push!(reqs,req)\n",
- " end\n",
- " if iw != nw\n",
- " neig_rank = (iw+1)-1\n",
- " s = n_own+1\n",
- " r = n_own+2\n",
- " req = MPI.Isend(view(u,s:s),comm,dest=neig_rank,tag=0)\n",
- " push!(reqs,req)\n",
- " req = MPI.Irecv!(view(u,r:r),comm,source=neig_rank,tag=0)\n",
- " push!(reqs,req)\n",
- " end\n",
- " MPI.Waitall(reqs)\n",
- " for i in 2:(n_own+1)\n",
- " u_new[i] = 0.5*(u[i-1]+u[i+1])\n",
- " end\n",
- " u, u_new = u_new, u\n",
- " end\n",
- " u\n",
- " @show u\n",
- " # Gather results in root process\n",
- " results = zeros(n+2)\n",
- " results[1] = -1\n",
- " results[n+2] = 1\n",
- " MPI.Gather!(view(u,2:n_own+1), view(results, 2:n+1), root=0, comm)\n",
- " if iw == 1\n",
- " @show results\n",
- " end \n",
- " end\n",
- " niters = 100\n",
- " load = 4\n",
- " n = load*nw\n",
- " jacobi_mpi(n,niters)\n",
- "end"
+ "Transform the parallel implementation of the 1d Jacobi method (function `jacopi_mpi`) to use latency hiding (overlap between computation of interior values and communication)."
]
},
{
@@ -992,18 +969,10 @@
"source": [
"# License\n",
"\n",
- "TODO: replace link to website\n",
"\n",
- "This notebook is part of the course [Programming Large Scale Parallel Systems](http://localhost:8000/) at Vrije Universiteit Amsterdam and may be used under a [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) license."
+ "\n",
+ "This notebook is part of the course [Programming Large Scale Parallel Systems](https://www.francescverdugo.com/XM_40017) at Vrije Universiteit Amsterdam and may be used under a [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) license."
]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "3d72ff47",
- "metadata": {},
- "outputs": [],
- "source": []
}
],
"metadata": {
diff --git a/dev/jacobi_method/index.html b/dev/jacobi_method/index.html
index 7840fc6..39e61df 100644
--- a/dev/jacobi_method/index.html
+++ b/dev/jacobi_method/index.html
@@ -1,5 +1,5 @@
-- · XM_40017
We consider the implementation using MPI. The programming model of MPI is generally better suited for data-parallel algorithms like this one than the task-based model provided by Distributed.jl. In any case, one can also implement it using Distributed, but it requires some extra effort to setup the remote channels right for the communication between neighbor processes.
A usual way of implementing the Jacobi method and related algorithms is using so-called ghost cells. Ghost cells represent the missing data dependencies in the data owned by each process. After importing the appropriate values from the neighbor processes one can perform the usual sequential Jacobi update locally in the processes.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Thus, the algorithm is usually implemented following two main phases at each iteration Jacobi:
Take a look at the implementation below and try to understand it. Note that we have used MPIClustermanagers and Distributed just to run the MPI code on the notebook. When running it on a cluster, MPIClustermanagers and Distributed are not needed.
In order to check the result, we will compare it against the serial implementation. To this end, we need to define the serial implementation also in the workers.
Finally, we call the parallel function on the workers, gather the results on the root rank, and compare against the sequential solution.
+
+
+
+
+
+
+
+
+
In [ ]:
+
+
+
@everywhereworkers()begin
+# Call jacobi in parallel
+niters=10
+load=4
+nranks=MPI.Comm_size(comm)
+n=load*nranks
+u=jacobi_mpi(n,niters)
+# Gather results in root process and check
+rank=MPI.Comm_rank(comm)
+n_own=div(n,nranks)
+ifrank==0
+results=zeros(n+2)
+results[1]=-1
+results[n+2]=1
+rcv=view(results,2:n+1)
+else
+rcv=nothing
+end
+MPI.Gather!(view(u,2:n_own+1),rcv,comm;root=0)
+ifrank==0
+@showresults≈jacobi(n,niters)
+end
+end
+
+
+
+
+
+
+
+
+
+
+
+
+
+Question: In function jacobi_mpi, how many messages per iteration are sent from a process away from the boundary?
+
+
a) 1
+b) 2
+c) 3
+d) 4
+
+
+
+
+
+
+
+
+
In [ ]:
+
+
+
answer="x"# replace x with a, b, c or d
+jacobi_2_check(answer)
+
+
+
+
+
+
+
+
+
+
+
+
+
+Question: At the end of function jacobi_mpi ...
+
+
a) each rank holds the complete solution.
+b) only the root process holds the solution.
+c) the values of the ghost cells of u are not consistent with the neighbors
+d) the ghost cells of u contain the initial values -1 and 1 in all ranks
+
+
+
+
+
+
+
+
+
In [ ]:
+
+
+
answer="x"# replace x with a, b, c or d
+jacobi_3_check(answer)
+
Can our implementation above be improved? Note that we only need communications to update the values at the boundary of the portion owned by each process. The other values (the one in green in the figure below) can be updated without communications. This provides the opportunity of overlapping the computation of the interior values (green cells in the figure) with the communication of the ghost values. This technique is called latency hiding, since we are hiding communication latency by overlapping it with communication that we need to do anyway.
+
The modification of the implementation above to include latency hiding is leaved as an exercise (see below).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7985,7 +8384,7 @@ d) The inner, but not the outer
The following figure shows the portion of vector u_new is updated at each iteration by a particular process (CPU 3) left picture, and which entries of u are needed to update this data, right picture. We use analogous figures for the other partitions below.
The following figure shows the portion of vector u_new that is updated at each iteration by a particular process (CPU 3) left picture, and which entries of u are needed to update this data, right picture. We use analogous figures for the other partitions below.
@@ -8099,6 +8498,50 @@ d) The inner, but not the outer
We consider the implementation using MPI. The programming model of MPI is generally better suited for data-parallel algorithms like this one than the task-based model provided by Distributed.jl. In any case, one can also implement it using Distributed, but it requires some extra effort to setup remote channels right for the communication between neighbor processes.
A usual way of implementing the Jacobi method and related algorithms is using so-called ghost cells. Ghost cells represent the missing data dependencies in the data owned by each process. After importing the appropriate values from the neighbor processes one can perform the usual sequential Jacobi update locally in the processes.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Thus, the algorithm is usually implemented following two main phases at each iteration Jacobi:
Take a look at the implementation below and try to understand it. Note that we have used MPIClustermanagers and Distributed just to run the MPI code on the notebook. When running it on a cluster, MPIClustermanagers and Distributed are not needed.
-Question: How many messages per iteration are sent from a process away from the boundary?
-
-
a) 1
-b) 2
-c) 3
-d) 4
-
-
-
-
-
-
-
-
-
In [ ]:
-
-
-
answer="x"# replace x with a, b, c or d
-jacobi_2_check(answer)
-
-
-
-
-
-
-
-
-
-
-
-
-
-Question: After the end of the for-loop (line 43), ...
-
-
a) each worker holds the complete solution.
-b) the root process holds the solution.
-c) the ghost cells contain redundant values.
-d) all ghost cells contain the initial values -1 and 1.
-
-
-
-
-
-
-
-
-
In [ ]:
-
-
-
answer="x"# replace x with a, b, c or d
-jacobi_3_check(answer)
-
-
-
-
-
-
-
-
-
-
-
-
-
-Question: In line 35 of the code, we wait for all receive and send requests. Is it possible to instead wait for just the receive requests?
-
-
a) No, because the send buffer might be overwritten if we don't wait for send requests.
-b) No, because MPI does not allow an asynchronous send without a Wait().
-c) Yes, because each send has a matching receive, so all requests are done when the receive requests return.
-d) Yes, because there are no writes to the send buffer in this iteration.
-
-
-
-
-
-
-
-
-
In [ ]:
-
-
-
answer="x"# replace x with a, b, c or d.
-jacobi_4_check(answer)
-
Can our implementation above be improved? Note that we only need communications to update the values at the boundary of the portion owned by each process. The other values (the one in green in the figure below) can be updated without communications. This provides the opportunity of overlapping the computation of the interior values (green cells in the figure) with the communication of the ghost values. This technique is called latency hiding, since we are hiding communication latency by overlapping it with communications that we need to do anyway.
-
The modification of the implementation above to include latency hiding is leaved as an exercise (see below).
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -8460,76 +8576,7 @@ d) Yes, because there are no writes to the send buffer in this iteration.
Transform the following parallel implementation of the 1d Jacobi method (it is copied from above) to use latency hiding (overlap between computation of interior values and communication)
Transform the parallel implementation of the 1d Jacobi method (function jacopi_mpi) to use latency hiding (overlap between computation of interior values and communication).
@@ -8540,22 +8587,7 @@ d) Yes, because there are no writes to the send buffer in this iteration.
This document was generated with Documenter.jl version 0.27.25 on Tuesday 29 August 2023. Using Julia version 1.9.3.
+Search · XM_40017
Loading search...
Settings
This document was generated with Documenter.jl version 0.27.25 on Wednesday 30 August 2023. Using Julia version 1.9.3.
diff --git a/dev/search_index.js b/dev/search_index.js
index 89edebc..1baf642 100644
--- a/dev/search_index.js
+++ b/dev/search_index.js
@@ -1,3 +1,3 @@
var documenterSearchIndex = {"docs":
-[{"location":"getting_started_with_julia/#Getting-started","page":"Getting started","title":"Getting started","text":"","category":"section"},{"location":"getting_started_with_julia/#Introduction","page":"Getting started","title":"Introduction","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The programming of this course will be done using the Julia programming language. Thus, we start by explaining how to get up and running with Julia. After studying this page, you will be able to:","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Use the Julia REPL,\nRun serial and parallel code,\nInstall and manage Julia packages.","category":"page"},{"location":"getting_started_with_julia/#Why-Julia?","page":"Getting started","title":"Why Julia?","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Courses related with high-performance computing (HPC) often use languages such as C, C++, or Fortran. We use Julia instead to make the course accessible to a wider set of students, including the ones that have no experience with C/C++ or Fortran, but are willing to learn parallel programming. Julia is a relatively new programming language specifically designed for scientific computing. It combines a high-level syntax close to interpreted languages like Python with the performance of compiled languages like C, C++, or Fortran. Thus, Julia will allow us to write efficient parallel algorithms with a syntax that is convenient in a teaching setting. In addition, Julia provides easy access to different programming models to write distributed algorithms, which will be useful to learn and experiment with them.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"tip: Tip\nYou can run the code in this link to learn how Julia compares to other languages (C and Python) in terms of performance.","category":"page"},{"location":"getting_started_with_julia/#Installing-Julia","page":"Getting started","title":"Installing Julia","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"This is a tutorial-like page. Follow these steps before you continue reading the document.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Download and install Julia from julialang.org;\nFollow the specific instructions for your operating system: Windows, MacOS, or Linux\nDownload and install VSCode and its Julia extension;","category":"page"},{"location":"getting_started_with_julia/#The-Julia-REPL","page":"Getting started","title":"The Julia REPL","text":"","category":"section"},{"location":"getting_started_with_julia/#Starting-Julia","page":"Getting started","title":"Starting Julia","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"There are several ways of opening Julia depending on your operating system and your IDE, but it is usually as simple as launching the Julia app. With VSCode, open a folder (File > Open Folder). Then, press Ctrl+Shift+P to open the command bar, and execute Julia: Start REPL. If this does not work, make sure you have the Julia extension for VSCode installed. Independently of the method you use, opening Julia results in a window with some text ending with:","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia>","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You have just opened the Julia read-evaluate-print loop, or simply the Julia REPL. Congrats! You will spend most of time using the REPL, when working in Julia. The REPL is a console waiting for user input. Just as in other consoles, the string of text right before the input area (julia> in the case) is called the command prompt or simply the prompt.","category":"page"},{"location":"getting_started_with_julia/#Basic-usage","page":"Getting started","title":"Basic usage","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The usage of the REPL is as follows:","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You write some input\npress enter\nyou get the output","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"For instance, try this","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> 1 + 1","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"A \"Hello world\" example looks like this in Julia","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> println(\"Hello, world!\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Try to run it in the REPL.","category":"page"},{"location":"getting_started_with_julia/#Help-mode","page":"Getting started","title":"Help mode","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Curious about what the function println does? Enter into help mode to look into the documentation. This is done by typing a question mark (?) into the input field:","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> ?","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"After typing ?, the command prompt changes to help?>. It means we are in help mode. Now, we can type a function name to see its documentation.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"help?> println","category":"page"},{"location":"getting_started_with_julia/#Package-and-shell-modes","page":"Getting started","title":"Package and shell modes","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The REPL comes with two more modes, namely package and shell modes. To enter package mode type","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> ]","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Package mode is used to install and manage packages. We are going to discuss the package mode in greater detail later. To return back to normal mode press the backspace key several times.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"To enter shell mode type semicolon (;)","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> ;","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The prompt should have changed to shell> indicating that we are in shell mode. Now you can type commands that you would normally do on your system command line. For instance,","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"shell> ls","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"will display the contents of the current folder in Mac or Linux. Using shell mode in Windows is not straightforward, and thus not recommended for beginners.","category":"page"},{"location":"getting_started_with_julia/#Running-Julia-code","page":"Getting started","title":"Running Julia code","text":"","category":"section"},{"location":"getting_started_with_julia/#Running-more-complex-code","page":"Getting started","title":"Running more complex code","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Real-world Julia programs are not typed in the REPL in practice. They are written in one or more files and included in the REPL. To try this, create a new file called hello.jl, write the code of the \"Hello world\" example above, and save it. If you are using VSCode, you can create the file using File > New File > Julia File. Once the file is saved with the name hello.jl, execute it as follows","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> include(\"hello.jl\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"warning: Warning\nMake sure that the file \"hello.jl\" is located in the current working directory of your Julia session. You can query the current directory with function pwd(). You can change to another directory with function cd() if needed. Also, make sure that the file extension is .jl.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The recommended way of running Julia code is using the REPL as we did. But it is also possible to run code directly from the system command line. To this end, open a terminal and call Julia followed by the path to the file containing the code you want to execute.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"$ julia hello.jl","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The previous line assumes that you have Julia properly installed in the system and that it's usable from the terminal. In UNIX systems (Linux and Mac), the Julia binary needs to be in one of the directories listed in the PATH environment variable. To check that Julia is properly installed, you can use","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"$ julia --version","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"If this runs without error and you see a version number, you are good to go!","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"note: Note\nIn this tutorial, when a code snipped starts with $, it should be run in the terminal. Otherwise, the code is to be run in the Julia REPL.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"tip: Tip\nAvoid calling Julia code from the terminal, use the Julia REPL instead! Each time you call Julia from the terminal, you start a fresh Julia session and Julia will need to compile your code from scratch. This can be time consuming for large projects. In contrast, if you execute code in the REPL, Julia will compile code incrementally, which is much faster. Running code in a cluster (like in DAS-5 for the Julia assignment) is among the few situations you need to run Julia code from the terminal.","category":"page"},{"location":"getting_started_with_julia/#Running-parallel-code","page":"Getting started","title":"Running parallel code","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Since we are in a parallel computing course, let's run a parallel \"Hello world\" example in Julia. Open a Julia REPL and write","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> using Distributed\njulia> @everywhere println(\"Hello, world! I am proc $(myid()) from $(nprocs())\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Here, we are using the Distributed package, which is part of the Julia standard library that provides distributed memory parallel support. The code prints the process id and the number of processes in the current Julia session.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You will probably only see output from 1 process. We need to add more processes to run the example in parallel. This is done with the addprocs function.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> addprocs(3)","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"We have added 3 new processes. Plus the old one, we have 4 processes. Run the code again.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> @everywhere println(\"Hello, world! I am proc $(myid()) from $(nprocs())\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Now, you should see output from 4 processes.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"It is possible to specify the number of processes when starting Julia from the terminal with the -p argument (useful, e.g., when running in a cluster). If you launch Julia from the terminal as","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"$ julia -p 3","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"and then run","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> @everywhere println(\"Hello, world! I am proc $(myid()) from $(nprocs())\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You should get output from 4 processes as before.","category":"page"},{"location":"getting_started_with_julia/#Installing-packages","page":"Getting started","title":"Installing packages","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"One of the most useful features of Julia is its package manager. It allows one to install Julia packages in a straightforward and platform independent way. To illustrate this, let us consider the following parallel \"Hello world\" example. This example uses the Message Passing Interface (MPI). We will learn more about MPI later in the course.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Copy the following block of code into a new file named \"hello_mpi.jl\"","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"# file hello_mpi.jl\nusing MPI\nMPI.Init()\ncomm = MPI.COMM_WORLD\nrank = MPI.Comm_rank(comm)\nnranks = MPI.Comm_size(comm)\nprintln(\"Hello world, I am rank $rank of $nranks\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"As you can see from this example, one can access MPI from Julia in a clean way, without type annotations and other complexities of C/C++ code.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Now, run the file from the REPL","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> incude(\"hello_mpi.jl\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"It probably didn't work, right? Read the error message and note that the MPI package needs to be installed to run this code.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"To install a package, we need to enter package mode. Remember that we entered into help mode by typing ?. Package mode is activated by typing ] : ","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> ]","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"At this point, the prompt should have changed to (@v1.8) pkg> indicating that we are in package mode. The text between the parentheses indicates which is the active project, i.e., where packages are going to be installed. In this case, we are working with the global project associated with our Julia installation (which is Julia 1.8 in this example, but it can be another version in your case).","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"To install the MPI package, type","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> add MPI","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Congrats, you have installed MPI!","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"note: Note\nMany Julia package names end with .jl. This is just a way of signaling that a package is written in Julia. When using such packages, the .jl needs to be omitted. In this case, we have isntalled the MPI.jl package even though we have only typed MPI in the REPL.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"note: Note\nThe package you have installed it is the Julia interface to MPI, called MPI.jl. Note that it is not a MPI library by itself. It is just a thin wrapper between MPI and Julia. To use this interface, you need an actual MPI library installed in your system such as OpenMPI or MPICH. Julia downloads and installs a MPI library for you, but it is also possible to use a MPI library already available in your system. This is useful, e.g., when running on HPC clusters. See the documentation of MPI.jl for further details.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"To check that the package was installed properly, exit package mode by pressing the backspace key several times, and run it again","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> incude(\"hello_mpi.jl\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Now, it should work, but you probably get output from a single MPI rank only.","category":"page"},{"location":"getting_started_with_julia/#Running-MPI-code","page":"Getting started","title":"Running MPI code","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"To run MPI applications in parallel, you need a launcher like mpiexec. MPI codes written in Julia are not an exception to this rule. From the system terminal, you can run","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"$ mpiexec -np 4 julia hello_mpi.jl","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"But it will probably not work since the version of mpiexec needs to match with the MPI version we are using from Julia. You can find the path to the mpiexec binary you need to use with these commands","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> using MPI\njulia> MPI.mpiexec_path","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"and then try again","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"$ /path/to/my/mpiexec -np 4 julia hello_mpi.jl","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"with your particular path.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"However, this is not very convenient. Don't worry if you could not make it work! A more elegant way to run MPI code is from the Julia REPL directly, by using these commands:","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> using MPI\njulia> mpiexec(cmd->run(`$cmd -np 4 julia hello_mpi.jl`))","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Now, you should see output from 4 ranks.","category":"page"},{"location":"getting_started_with_julia/#Package-manager","page":"Getting started","title":"Package manager","text":"","category":"section"},{"location":"getting_started_with_julia/#Installing-packages-locally","page":"Getting started","title":"Installing packages locally","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"We have installed the MPI package globally and it will be available in all Julia sessions. However, in some situations, we want to work with different versions of the same package or to install packages in an isolated way to avoid potential conflicts with other packages. This can be done by using local projects.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"A project is simply a folder in the hard disk. To use a particular folder as your project, you need to activate it. This is done by entering package mode and using the activate command followed by the path to the folder you want to activate.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> activate .","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The previous command will activate the current working directory. Note that the dot . is indeed the path to the current folder.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The prompt has changed to (lessons) pkg> indicating that we are in the project within the lessons folder. The particular folder name can be different in your case.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"tip: Tip\nYou can activate a project directly when opening Julia from the terminal using the --project flag. The command $ julia --project=. will open Julia and activate a project in the current directory. You can also achieve the same effect by setting the environment variable JULIA_PROJECT with the path of the folder you want to activate.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"note: Note\nThe active project folder and the current working directory are two independent concepts! For instance, (@v1.8) pkg> activate folderB and then julia> cd(\"folderA\"), will activate the project in folderB and change the current working directory to folderA.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"At this point all package-related operations will be local to the new project. For instance, install the DataFrames package.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(lessons) pkg> add DataFrames","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Use the package to check that it is installed","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> using DataFrames\njulia> DataFrame(a=[1,2],b=[3,4])","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Now, we can return to the global project to check that DataFrames has not been installed there. To return to the global environment, use activate without a folder name.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(lessons) pkg> activate","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The prompt is again (@v1.8) pkg>","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Now, try to use DataFrames.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> using DataFrames\njulia> DataFrame(a=[1,2],b=[3,4])","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You should get an error or a warning unless you already had DataFrames installed globally.","category":"page"},{"location":"getting_started_with_julia/#Project-and-Manifest-files","page":"Getting started","title":"Project and Manifest files","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The information about a project is stored in two files Project.toml and Manifest.toml.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Project.toml contains the packages explicitly installed (the direct dependencies)\nManifest.toml contains direct and indirect dependencies along with the concrete version of each package.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"In other words, Project.toml contains the packages relevant for the user, whereas Manifest.toml is the detailed snapshot of all dependencies. The Manifest.toml can be used to reproduce the same envinonment in another machine.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You can see the path to the current Project.toml file by using the status operator (or st in its short form) while in package mode","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> status","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The information about the Manifest.toml can be inspected by passing the -m flag.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> status -m","category":"page"},{"location":"getting_started_with_julia/#Installing-packages-from-a-project-file","page":"Getting started","title":"Installing packages from a project file","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Project files can be used to install lists of packages defined by others. E.g., to install all the dependencies of a Julia application.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Assume that a colleague has sent to you a Project.toml file with this content:","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"[deps]\nBenchmarkTools = \"6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf\"\nDataFrames = \"a93c6f00-e57d-5684-b7b6-d8193f3e46c0\"\nMPI = \"da04e1cc-30fd-572f-bb4f-1f8673147195\"","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Copy the contents of previous code block into a file called Project.toml and place it in an empty folder named newproject. It is important that the file is named Project.toml. You can create a new folder from the REPL with","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> mkdir(\"newproject\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"To install all the packages registered in this file you need to activate the folder containing your Project.toml file","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> activate newproject","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"and then instantiating it","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(newproject) pkg> instantiate","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The instantiate command will download and install all listed packages and their dependencies in just one click.","category":"page"},{"location":"getting_started_with_julia/#Getting-help-in-package-mode","page":"Getting started","title":"Getting help in package mode","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You can get help about a particular package operator by writing help in front of it","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> help activate","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You can get an overview of all package commands by typing help alone","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> help","category":"page"},{"location":"getting_started_with_julia/#Package-operations-in-Julia-code","page":"Getting started","title":"Package operations in Julia code","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"In some situations it is required to use package commands in Julia code, e.g., to automatize installation and deployment of Julia applications. This can be done using the Pkg package. For instance","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> using Pkg\njulia> Pkg.status()","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"is equivalent to calling status in package mode.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> status","category":"page"},{"location":"getting_started_with_julia/#Conclusion","page":"Getting started","title":"Conclusion","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"We have learned the basics of how to work with Julia. If you want to further dig into the topics we have covered here, you can take a look at the following links:","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Julia Manual\nPackage manager","category":"page"},{"location":"tsp/","page":"-","title":"-","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/tsp.ipynb\"","category":"page"},{"location":"tsp/","page":"-","title":"-","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"jacobi_2D/","page":"-","title":"-","text":"\n","category":"page"},{"location":"julia_async/","page":"Asynchronous programming in Julia","title":"Asynchronous programming in Julia","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/julia_async.ipynb\"","category":"page"},{"location":"julia_async/","page":"Asynchronous programming in Julia","title":"Asynchronous programming in Julia","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"julia_async/","page":"Asynchronous programming in Julia","title":"Asynchronous programming in Julia","text":"\n","category":"page"},{"location":"solutions/","page":"-","title":"-","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/solutions.ipynb\"","category":"page"},{"location":"solutions/","page":"-","title":"-","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"LEQ/","page":"-","title":"-","text":"\n","category":"page"},{"location":"julia_distributed/","page":"Distributed computing in Julia","title":"Distributed computing in Julia","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/julia_distributed.ipynb\"","category":"page"},{"location":"julia_distributed/","page":"Distributed computing in Julia","title":"Distributed computing in Julia","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"julia_distributed/","page":"Distributed computing in Julia","title":"Distributed computing in Julia","text":"\n","category":"page"},{"location":"julia_jacobi/","page":"-","title":"-","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/julia_jacobi.ipynb\"","category":"page"},{"location":"julia_jacobi/","page":"-","title":"-","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"julia_tutorial/","page":"-","title":"-","text":"\n","category":"page"},{"location":"mpi_tutorial/","page":"Distributed computing with MPI","title":"Distributed computing with MPI","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/mpi_tutorial.ipynb\"","category":"page"},{"location":"mpi_tutorial/","page":"Distributed computing with MPI","title":"Distributed computing with MPI","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"mpi_tutorial/","page":"Distributed computing with MPI","title":"Distributed computing with MPI","text":"\n","category":"page"},{"location":"notebook-hello/","page":"-","title":"-","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/notebook-hello.ipynb\"","category":"page"},{"location":"notebook-hello/","page":"-","title":"-","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"asp/","page":"-","title":"-","text":"\n","category":"page"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = XM_40017","category":"page"},{"location":"#Programming-Large-Scale-Parallel-Systems-(XM_40017)","page":"Home","title":"Programming Large-Scale Parallel Systems (XM_40017)","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Welcome to the interactive lecture notes of the Programming Large-Scale Parallel Systems course at VU Amsterdam!","category":"page"},{"location":"#What","page":"Home","title":"What","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"This page contains part of the course material of the Programming Large-Scale Parallel Systems course at VU Amsterdam. We provide several lecture notes in jupyter notebook format, which will help you to learn how to design, analyze, and program parallel algorithms on multi-node computing systems. Further information about the course is found in the study guide (click here) and our Canvas page (for registered students).","category":"page"},{"location":"","page":"Home","title":"Home","text":"note: Note\nMaterial will be added incrementally to the website as the course advances.","category":"page"},{"location":"","page":"Home","title":"Home","text":"warning: Warning\nThis page will eventually contain only a part of the course material. The rest will be available on Canvas. In particular, the material in this public webpage does not fully cover all topics in the final exam.","category":"page"},{"location":"#How-to-use-this-page","page":"Home","title":"How to use this page","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"You have two main ways of studying the notebooks:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Download the notebooks and run them locally on your computer (recommended). At each notebook page you will find a green box with links to download the notebook.\nYou also have the static version of the notebooks displayed in this webpage for quick reference.","category":"page"},{"location":"#How-to-run-the-notebooks-locally","page":"Home","title":"How to run the notebooks locally","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"To run a notebook locally follow these steps:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Install Julia (if not done already). More information in Getting started.\nDownload the notebook.\nLaunch Julia. More information in Getting started.\nExecute these commands in the Julia command line:","category":"page"},{"location":"","page":"Home","title":"Home","text":"julia> using Pkg\njulia> Pkg.add(\"IJulia\")\njulia> using IJulia\njulia> notebook()","category":"page"},{"location":"","page":"Home","title":"Home","text":"These commands will open a jupyter in your web browser. Navigate in jupyter to the notebook file you have downloaded and open it.","category":"page"},{"location":"#Authors","page":"Home","title":"Authors","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"This material is created by Francesc Verdugo with the help of Gelieza Kötterheinrich. Part of the notebooks are based on the course slides by Henri Bal.","category":"page"},{"location":"#License","page":"Home","title":"License","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"All material on this page that is original to this course may be used under a CC BY 4.0 license.","category":"page"},{"location":"#Acknowledgment","page":"Home","title":"Acknowledgment","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"This page was created with the support of the Faculty of Science of Vrije Universiteit Amsterdam in the framework of the project \"Interactive lecture notes and exercises for the Programming Large-Scale Parallel Systems course\" funded by the \"Innovation budget BETA 2023 Studievoorschotmiddelen (SVM) towards Activated Blended Learning\".","category":"page"}]
+[{"location":"getting_started_with_julia/#Getting-started","page":"Getting started","title":"Getting started","text":"","category":"section"},{"location":"getting_started_with_julia/#Introduction","page":"Getting started","title":"Introduction","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The programming of this course will be done using the Julia programming language. Thus, we start by explaining how to get up and running with Julia. After studying this page, you will be able to:","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Use the Julia REPL,\nRun serial and parallel code,\nInstall and manage Julia packages.","category":"page"},{"location":"getting_started_with_julia/#Why-Julia?","page":"Getting started","title":"Why Julia?","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Courses related with high-performance computing (HPC) often use languages such as C, C++, or Fortran. We use Julia instead to make the course accessible to a wider set of students, including the ones that have no experience with C/C++ or Fortran, but are willing to learn parallel programming. Julia is a relatively new programming language specifically designed for scientific computing. It combines a high-level syntax close to interpreted languages like Python with the performance of compiled languages like C, C++, or Fortran. Thus, Julia will allow us to write efficient parallel algorithms with a syntax that is convenient in a teaching setting. In addition, Julia provides easy access to different programming models to write distributed algorithms, which will be useful to learn and experiment with them.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"tip: Tip\nYou can run the code in this link to learn how Julia compares to other languages (C and Python) in terms of performance.","category":"page"},{"location":"getting_started_with_julia/#Installing-Julia","page":"Getting started","title":"Installing Julia","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"This is a tutorial-like page. Follow these steps before you continue reading the document.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Download and install Julia from julialang.org;\nFollow the specific instructions for your operating system: Windows, MacOS, or Linux\nDownload and install VSCode and its Julia extension;","category":"page"},{"location":"getting_started_with_julia/#The-Julia-REPL","page":"Getting started","title":"The Julia REPL","text":"","category":"section"},{"location":"getting_started_with_julia/#Starting-Julia","page":"Getting started","title":"Starting Julia","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"There are several ways of opening Julia depending on your operating system and your IDE, but it is usually as simple as launching the Julia app. With VSCode, open a folder (File > Open Folder). Then, press Ctrl+Shift+P to open the command bar, and execute Julia: Start REPL. If this does not work, make sure you have the Julia extension for VSCode installed. Independently of the method you use, opening Julia results in a window with some text ending with:","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia>","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You have just opened the Julia read-evaluate-print loop, or simply the Julia REPL. Congrats! You will spend most of time using the REPL, when working in Julia. The REPL is a console waiting for user input. Just as in other consoles, the string of text right before the input area (julia> in the case) is called the command prompt or simply the prompt.","category":"page"},{"location":"getting_started_with_julia/#Basic-usage","page":"Getting started","title":"Basic usage","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The usage of the REPL is as follows:","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You write some input\npress enter\nyou get the output","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"For instance, try this","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> 1 + 1","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"A \"Hello world\" example looks like this in Julia","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> println(\"Hello, world!\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Try to run it in the REPL.","category":"page"},{"location":"getting_started_with_julia/#Help-mode","page":"Getting started","title":"Help mode","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Curious about what the function println does? Enter into help mode to look into the documentation. This is done by typing a question mark (?) into the input field:","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> ?","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"After typing ?, the command prompt changes to help?>. It means we are in help mode. Now, we can type a function name to see its documentation.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"help?> println","category":"page"},{"location":"getting_started_with_julia/#Package-and-shell-modes","page":"Getting started","title":"Package and shell modes","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The REPL comes with two more modes, namely package and shell modes. To enter package mode type","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> ]","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Package mode is used to install and manage packages. We are going to discuss the package mode in greater detail later. To return back to normal mode press the backspace key several times.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"To enter shell mode type semicolon (;)","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> ;","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The prompt should have changed to shell> indicating that we are in shell mode. Now you can type commands that you would normally do on your system command line. For instance,","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"shell> ls","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"will display the contents of the current folder in Mac or Linux. Using shell mode in Windows is not straightforward, and thus not recommended for beginners.","category":"page"},{"location":"getting_started_with_julia/#Running-Julia-code","page":"Getting started","title":"Running Julia code","text":"","category":"section"},{"location":"getting_started_with_julia/#Running-more-complex-code","page":"Getting started","title":"Running more complex code","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Real-world Julia programs are not typed in the REPL in practice. They are written in one or more files and included in the REPL. To try this, create a new file called hello.jl, write the code of the \"Hello world\" example above, and save it. If you are using VSCode, you can create the file using File > New File > Julia File. Once the file is saved with the name hello.jl, execute it as follows","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> include(\"hello.jl\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"warning: Warning\nMake sure that the file \"hello.jl\" is located in the current working directory of your Julia session. You can query the current directory with function pwd(). You can change to another directory with function cd() if needed. Also, make sure that the file extension is .jl.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The recommended way of running Julia code is using the REPL as we did. But it is also possible to run code directly from the system command line. To this end, open a terminal and call Julia followed by the path to the file containing the code you want to execute.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"$ julia hello.jl","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The previous line assumes that you have Julia properly installed in the system and that it's usable from the terminal. In UNIX systems (Linux and Mac), the Julia binary needs to be in one of the directories listed in the PATH environment variable. To check that Julia is properly installed, you can use","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"$ julia --version","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"If this runs without error and you see a version number, you are good to go!","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"note: Note\nIn this tutorial, when a code snipped starts with $, it should be run in the terminal. Otherwise, the code is to be run in the Julia REPL.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"tip: Tip\nAvoid calling Julia code from the terminal, use the Julia REPL instead! Each time you call Julia from the terminal, you start a fresh Julia session and Julia will need to compile your code from scratch. This can be time consuming for large projects. In contrast, if you execute code in the REPL, Julia will compile code incrementally, which is much faster. Running code in a cluster (like in DAS-5 for the Julia assignment) is among the few situations you need to run Julia code from the terminal.","category":"page"},{"location":"getting_started_with_julia/#Running-parallel-code","page":"Getting started","title":"Running parallel code","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Since we are in a parallel computing course, let's run a parallel \"Hello world\" example in Julia. Open a Julia REPL and write","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> using Distributed\njulia> @everywhere println(\"Hello, world! I am proc $(myid()) from $(nprocs())\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Here, we are using the Distributed package, which is part of the Julia standard library that provides distributed memory parallel support. The code prints the process id and the number of processes in the current Julia session.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You will probably only see output from 1 process. We need to add more processes to run the example in parallel. This is done with the addprocs function.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> addprocs(3)","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"We have added 3 new processes. Plus the old one, we have 4 processes. Run the code again.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> @everywhere println(\"Hello, world! I am proc $(myid()) from $(nprocs())\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Now, you should see output from 4 processes.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"It is possible to specify the number of processes when starting Julia from the terminal with the -p argument (useful, e.g., when running in a cluster). If you launch Julia from the terminal as","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"$ julia -p 3","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"and then run","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> @everywhere println(\"Hello, world! I am proc $(myid()) from $(nprocs())\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You should get output from 4 processes as before.","category":"page"},{"location":"getting_started_with_julia/#Installing-packages","page":"Getting started","title":"Installing packages","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"One of the most useful features of Julia is its package manager. It allows one to install Julia packages in a straightforward and platform independent way. To illustrate this, let us consider the following parallel \"Hello world\" example. This example uses the Message Passing Interface (MPI). We will learn more about MPI later in the course.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Copy the following block of code into a new file named \"hello_mpi.jl\"","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"# file hello_mpi.jl\nusing MPI\nMPI.Init()\ncomm = MPI.COMM_WORLD\nrank = MPI.Comm_rank(comm)\nnranks = MPI.Comm_size(comm)\nprintln(\"Hello world, I am rank $rank of $nranks\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"As you can see from this example, one can access MPI from Julia in a clean way, without type annotations and other complexities of C/C++ code.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Now, run the file from the REPL","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> incude(\"hello_mpi.jl\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"It probably didn't work, right? Read the error message and note that the MPI package needs to be installed to run this code.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"To install a package, we need to enter package mode. Remember that we entered into help mode by typing ?. Package mode is activated by typing ] : ","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> ]","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"At this point, the prompt should have changed to (@v1.8) pkg> indicating that we are in package mode. The text between the parentheses indicates which is the active project, i.e., where packages are going to be installed. In this case, we are working with the global project associated with our Julia installation (which is Julia 1.8 in this example, but it can be another version in your case).","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"To install the MPI package, type","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> add MPI","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Congrats, you have installed MPI!","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"note: Note\nMany Julia package names end with .jl. This is just a way of signaling that a package is written in Julia. When using such packages, the .jl needs to be omitted. In this case, we have isntalled the MPI.jl package even though we have only typed MPI in the REPL.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"note: Note\nThe package you have installed it is the Julia interface to MPI, called MPI.jl. Note that it is not a MPI library by itself. It is just a thin wrapper between MPI and Julia. To use this interface, you need an actual MPI library installed in your system such as OpenMPI or MPICH. Julia downloads and installs a MPI library for you, but it is also possible to use a MPI library already available in your system. This is useful, e.g., when running on HPC clusters. See the documentation of MPI.jl for further details.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"To check that the package was installed properly, exit package mode by pressing the backspace key several times, and run it again","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> incude(\"hello_mpi.jl\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Now, it should work, but you probably get output from a single MPI rank only.","category":"page"},{"location":"getting_started_with_julia/#Running-MPI-code","page":"Getting started","title":"Running MPI code","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"To run MPI applications in parallel, you need a launcher like mpiexec. MPI codes written in Julia are not an exception to this rule. From the system terminal, you can run","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"$ mpiexec -np 4 julia hello_mpi.jl","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"But it will probably not work since the version of mpiexec needs to match with the MPI version we are using from Julia. You can find the path to the mpiexec binary you need to use with these commands","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> using MPI\njulia> MPI.mpiexec_path","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"and then try again","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"$ /path/to/my/mpiexec -np 4 julia hello_mpi.jl","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"with your particular path.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"However, this is not very convenient. Don't worry if you could not make it work! A more elegant way to run MPI code is from the Julia REPL directly, by using these commands:","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> using MPI\njulia> mpiexec(cmd->run(`$cmd -np 4 julia hello_mpi.jl`))","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Now, you should see output from 4 ranks.","category":"page"},{"location":"getting_started_with_julia/#Package-manager","page":"Getting started","title":"Package manager","text":"","category":"section"},{"location":"getting_started_with_julia/#Installing-packages-locally","page":"Getting started","title":"Installing packages locally","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"We have installed the MPI package globally and it will be available in all Julia sessions. However, in some situations, we want to work with different versions of the same package or to install packages in an isolated way to avoid potential conflicts with other packages. This can be done by using local projects.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"A project is simply a folder in the hard disk. To use a particular folder as your project, you need to activate it. This is done by entering package mode and using the activate command followed by the path to the folder you want to activate.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> activate .","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The previous command will activate the current working directory. Note that the dot . is indeed the path to the current folder.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The prompt has changed to (lessons) pkg> indicating that we are in the project within the lessons folder. The particular folder name can be different in your case.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"tip: Tip\nYou can activate a project directly when opening Julia from the terminal using the --project flag. The command $ julia --project=. will open Julia and activate a project in the current directory. You can also achieve the same effect by setting the environment variable JULIA_PROJECT with the path of the folder you want to activate.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"note: Note\nThe active project folder and the current working directory are two independent concepts! For instance, (@v1.8) pkg> activate folderB and then julia> cd(\"folderA\"), will activate the project in folderB and change the current working directory to folderA.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"At this point all package-related operations will be local to the new project. For instance, install the DataFrames package.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(lessons) pkg> add DataFrames","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Use the package to check that it is installed","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> using DataFrames\njulia> DataFrame(a=[1,2],b=[3,4])","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Now, we can return to the global project to check that DataFrames has not been installed there. To return to the global environment, use activate without a folder name.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(lessons) pkg> activate","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The prompt is again (@v1.8) pkg>","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Now, try to use DataFrames.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> using DataFrames\njulia> DataFrame(a=[1,2],b=[3,4])","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You should get an error or a warning unless you already had DataFrames installed globally.","category":"page"},{"location":"getting_started_with_julia/#Project-and-Manifest-files","page":"Getting started","title":"Project and Manifest files","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The information about a project is stored in two files Project.toml and Manifest.toml.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Project.toml contains the packages explicitly installed (the direct dependencies)\nManifest.toml contains direct and indirect dependencies along with the concrete version of each package.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"In other words, Project.toml contains the packages relevant for the user, whereas Manifest.toml is the detailed snapshot of all dependencies. The Manifest.toml can be used to reproduce the same envinonment in another machine.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You can see the path to the current Project.toml file by using the status operator (or st in its short form) while in package mode","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> status","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The information about the Manifest.toml can be inspected by passing the -m flag.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> status -m","category":"page"},{"location":"getting_started_with_julia/#Installing-packages-from-a-project-file","page":"Getting started","title":"Installing packages from a project file","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Project files can be used to install lists of packages defined by others. E.g., to install all the dependencies of a Julia application.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Assume that a colleague has sent to you a Project.toml file with this content:","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"[deps]\nBenchmarkTools = \"6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf\"\nDataFrames = \"a93c6f00-e57d-5684-b7b6-d8193f3e46c0\"\nMPI = \"da04e1cc-30fd-572f-bb4f-1f8673147195\"","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Copy the contents of previous code block into a file called Project.toml and place it in an empty folder named newproject. It is important that the file is named Project.toml. You can create a new folder from the REPL with","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> mkdir(\"newproject\")","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"To install all the packages registered in this file you need to activate the folder containing your Project.toml file","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> activate newproject","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"and then instantiating it","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(newproject) pkg> instantiate","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"The instantiate command will download and install all listed packages and their dependencies in just one click.","category":"page"},{"location":"getting_started_with_julia/#Getting-help-in-package-mode","page":"Getting started","title":"Getting help in package mode","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You can get help about a particular package operator by writing help in front of it","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> help activate","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"You can get an overview of all package commands by typing help alone","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> help","category":"page"},{"location":"getting_started_with_julia/#Package-operations-in-Julia-code","page":"Getting started","title":"Package operations in Julia code","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"In some situations it is required to use package commands in Julia code, e.g., to automatize installation and deployment of Julia applications. This can be done using the Pkg package. For instance","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"julia> using Pkg\njulia> Pkg.status()","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"is equivalent to calling status in package mode.","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"(@v1.8) pkg> status","category":"page"},{"location":"getting_started_with_julia/#Conclusion","page":"Getting started","title":"Conclusion","text":"","category":"section"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"We have learned the basics of how to work with Julia. If you want to further dig into the topics we have covered here, you can take a look at the following links:","category":"page"},{"location":"getting_started_with_julia/","page":"Getting started","title":"Getting started","text":"Julia Manual\nPackage manager","category":"page"},{"location":"tsp/","page":"-","title":"-","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/tsp.ipynb\"","category":"page"},{"location":"tsp/","page":"-","title":"-","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"jacobi_2D/","page":"-","title":"-","text":"\n","category":"page"},{"location":"julia_async/","page":"Asynchronous programming in Julia","title":"Asynchronous programming in Julia","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/julia_async.ipynb\"","category":"page"},{"location":"julia_async/","page":"Asynchronous programming in Julia","title":"Asynchronous programming in Julia","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"julia_async/","page":"Asynchronous programming in Julia","title":"Asynchronous programming in Julia","text":"\n","category":"page"},{"location":"solutions/","page":"-","title":"-","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/solutions.ipynb\"","category":"page"},{"location":"solutions/","page":"-","title":"-","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"LEQ/","page":"-","title":"-","text":"\n","category":"page"},{"location":"julia_distributed/","page":"Distributed computing in Julia","title":"Distributed computing in Julia","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/julia_distributed.ipynb\"","category":"page"},{"location":"julia_distributed/","page":"Distributed computing in Julia","title":"Distributed computing in Julia","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"julia_distributed/","page":"Distributed computing in Julia","title":"Distributed computing in Julia","text":"\n","category":"page"},{"location":"julia_jacobi/","page":"-","title":"-","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/julia_jacobi.ipynb\"","category":"page"},{"location":"julia_jacobi/","page":"-","title":"-","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"julia_tutorial/","page":"-","title":"-","text":"\n","category":"page"},{"location":"mpi_tutorial/","page":"Distributed computing with MPI","title":"Distributed computing with MPI","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/mpi_tutorial.ipynb\"","category":"page"},{"location":"mpi_tutorial/","page":"Distributed computing with MPI","title":"Distributed computing with MPI","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"mpi_tutorial/","page":"Distributed computing with MPI","title":"Distributed computing with MPI","text":"\n","category":"page"},{"location":"notebook-hello/","page":"-","title":"-","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/notebook-hello.ipynb\"","category":"page"},{"location":"notebook-hello/","page":"-","title":"-","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"matrix_matrix/","page":"Matrix-matrix multiplication","title":"Matrix-matrix multiplication","text":"\n","category":"page"},{"location":"asp/","page":"All pairs of shortest paths","title":"All pairs of shortest paths","text":"EditURL = \"https://github.com/fverdugo/XM_40017/blob/main/notebooks/asp.ipynb\"","category":"page"},{"location":"asp/","page":"All pairs of shortest paths","title":"All pairs of shortest paths","text":"
\n Tip\n
\n
\n
\n Download this notebook and run it locally on your machine [recommended]. Click here.\n
\n
\n
\n
","category":"page"},{"location":"asp/","page":"All pairs of shortest paths","title":"All pairs of shortest paths","text":"\n","category":"page"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = XM_40017","category":"page"},{"location":"#Programming-Large-Scale-Parallel-Systems-(XM_40017)","page":"Home","title":"Programming Large-Scale Parallel Systems (XM_40017)","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Welcome to the interactive lecture notes of the Programming Large-Scale Parallel Systems course at VU Amsterdam!","category":"page"},{"location":"#What","page":"Home","title":"What","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"This page contains part of the course material of the Programming Large-Scale Parallel Systems course at VU Amsterdam. We provide several lecture notes in jupyter notebook format, which will help you to learn how to design, analyze, and program parallel algorithms on multi-node computing systems. Further information about the course is found in the study guide (click here) and our Canvas page (for registered students).","category":"page"},{"location":"","page":"Home","title":"Home","text":"note: Note\nMaterial will be added incrementally to the website as the course advances.","category":"page"},{"location":"","page":"Home","title":"Home","text":"warning: Warning\nThis page will eventually contain only a part of the course material. The rest will be available on Canvas. In particular, the material in this public webpage does not fully cover all topics in the final exam.","category":"page"},{"location":"#How-to-use-this-page","page":"Home","title":"How to use this page","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"You have two main ways of studying the notebooks:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Download the notebooks and run them locally on your computer (recommended). At each notebook page you will find a green box with links to download the notebook.\nYou also have the static version of the notebooks displayed in this webpage for quick reference.","category":"page"},{"location":"#How-to-run-the-notebooks-locally","page":"Home","title":"How to run the notebooks locally","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"To run a notebook locally follow these steps:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Install Julia (if not done already). More information in Getting started.\nDownload the notebook.\nLaunch Julia. More information in Getting started.\nExecute these commands in the Julia command line:","category":"page"},{"location":"","page":"Home","title":"Home","text":"julia> using Pkg\njulia> Pkg.add(\"IJulia\")\njulia> using IJulia\njulia> notebook()","category":"page"},{"location":"","page":"Home","title":"Home","text":"These commands will open a jupyter in your web browser. Navigate in jupyter to the notebook file you have downloaded and open it.","category":"page"},{"location":"#Authors","page":"Home","title":"Authors","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"This material is created by Francesc Verdugo with the help of Gelieza Kötterheinrich. Part of the notebooks are based on the course slides by Henri Bal.","category":"page"},{"location":"#License","page":"Home","title":"License","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"All material on this page that is original to this course may be used under a CC BY 4.0 license.","category":"page"},{"location":"#Acknowledgment","page":"Home","title":"Acknowledgment","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"This page was created with the support of the Faculty of Science of Vrije Universiteit Amsterdam in the framework of the project \"Interactive lecture notes and exercises for the Programming Large-Scale Parallel Systems course\" funded by the \"Innovation budget BETA 2023 Studievoorschotmiddelen (SVM) towards Activated Blended Learning\".","category":"page"}]
}
diff --git a/dev/solutions/index.html b/dev/solutions/index.html
index 7035cbe..2d9140a 100644
--- a/dev/solutions/index.html
+++ b/dev/solutions/index.html
@@ -1,5 +1,5 @@
-- · XM_40017