From c817ba59e74a151b9f462479d04b1e100764f8bb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?=
<5621605+RodrigoGiraoSerrao@users.noreply.github.com>
Date: Sun, 13 Nov 2022 12:23:31 +0000
Subject: [PATCH] Add chapter Mathematical Functions.
---
CHANGELOG.md | 21 +
book/_toc.yml | 1 +
docs/Mathematical-Functions.ipynb | 9757 +++++++++++++++++++++++++++++
res/Applied_Maths.png | Bin 0 -> 45216 bytes
res/Graph_Age_Salary.png | Bin 0 -> 43465 bytes
res/Graph_Cubic.png | Bin 0 -> 25678 bytes
res/Graph_P_Q.png | Bin 0 -> 24608 bytes
7 files changed, 9779 insertions(+)
create mode 100644 docs/Mathematical-Functions.ipynb
create mode 100644 res/Applied_Maths.png
create mode 100644 res/Graph_Age_Salary.png
create mode 100644 res/Graph_Cubic.png
create mode 100644 res/Graph_P_Q.png
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4052640..117d491 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,27 @@ However, because this book is a rework of the first edition by Bernard Legrand,
this changelog also marks with [n] content that is new in this rework.
+## 0.7.0
+
+ - Add chapter “Mathematical Functions”:
+ - refactor “Sorting and Searching Data”:
+ - [n] explain how “Grade Up/Down” sort numeric arrays of arbitrary rank;
+ - [n] add subsection on how “Grade Up” `⍋` inverts a permutation;
+ - remove subsection “Encode & Decode > Applications > Calculating Positions in a Matrix” (rendered obsolete by high-rank “Index Of”);
+ - refactor “Randomised Values”:
+ - [n] say “Deal” `?0` generates decimal numbers between 0 and 1;
+ - move Specialist's Subsection “Random Link” to the main section (the book uses `⎕RL` a lot to generate variables, so it makes sense to describe it in the main text).
+ - refactor subsection “Some More Maths”:
+ - [n] list the “Circle” functions for complex numbers;
+ - [n] add the identity `(×≡∨×∧)`;
+ - [n] mention “Unique” `∪` next to “Union” and “Intersection”.
+ - refactor “Exercises”:
+ - [n] add exercise on representing sparse matrices and using “Encode” and “Decode” (replaces old “Encode & Decode > Applications > Calculating Positions in a Matrix”).
+ - refactor “The Specialist's Section”:
+ - move “Random Link” out;
+ - [n] write about total array ordering.
+
+
## 0.6.1
- Add section “More about DISPLAY” to chapter “Nested Arrays (Continued)”.
diff --git a/book/_toc.yml b/book/_toc.yml
index ed41b7d..19393c6 100644
--- a/book/_toc.yml
+++ b/book/_toc.yml
@@ -15,4 +15,5 @@ sections:
- file: Nested-Arrays-Continued
- file: Operators
- file: Tacit-Programming
+- file: Mathematical-Functions
- file: Appendices
diff --git a/docs/Mathematical-Functions.ipynb b/docs/Mathematical-Functions.ipynb
new file mode 100644
index 0000000..fae4001
--- /dev/null
+++ b/docs/Mathematical-Functions.ipynb
@@ -0,0 +1,9757 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "brilliant-opera",
+ "metadata": {},
+ "source": [
+ "# Mathematical Functions\n",
+ "\n",
+ "## Sorting and Searching Data\n",
+ "\n",
+ "### Sorting Numeric Data\n",
+ "\n",
+ "#### Sorting Numeric Vectors\n",
+ "\n",
+ "Two primitive functions are provided to sort data:\n",
+ "\n",
+ " - _Grade up_ `⍋`, typed with APL+Shift+4, returns the set of indices required to sort the array in ascending order.\n",
+ " - _Grade down_ `⍒`, typed with APL+Shift+3, returns the set of indices required to sort the array in descending order.\n",
+ "\n",
+ "Here is an example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "ancient-halifax",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "3 1 7 2 6 9 5 10 8 4\n",
+ ""
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "vec ← 40 60 20 110 70 60 40 90 60 80\n",
+ "⍋vec"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "realistic-armenia",
+ "metadata": {},
+ "source": [
+ "Notice that the function _grade up_ `⍋` does not actually return a sorted (i.e., re-ordered) array.\n",
+ "Instead, we obtain a set of indices which tell us that:\n",
+ "\n",
+ " - the smallest item is at index `3`;\n",
+ " - then comes the item at index `1`;\n",
+ " - then the item at index `7`;\n",
+ " - then the item at index `2`;\n",
+ " - ...\n",
+ " - and the largest item is at index `4`.\n",
+ "\n",
+ "To obtain the vector `vec` sorted in ascending order, we must index it by these values, as shown below:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "blind-jerusalem",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "20 40 40 60 60 60 70 80 90 110\n",
+ ""
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "vec[⍋vec]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "norwegian-missouri",
+ "metadata": {},
+ "source": [
+ "As you might imagine, _grade down_ sorts the vector in descending order:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "severe-thickness",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "110 90 80 70 60 60 60 40 40 20\n",
+ ""
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "vec[⍒vec]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "analyzed-train",
+ "metadata": {},
+ "source": [
+ "When several items of the argument are equal, the relative positions of the items that are equal do not change.\n",
+ "For this reason, `⍒vec` is equal to `⌽⍋vec` only if the values in `vec` are all different.\n",
+ "This is not the case for our vector:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "demographic-housing",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "3 1 7 2 6 9 5 10 8 4\n",
+ ""
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⍋vec"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "color-wrapping",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "4 8 10 5 9 6 2 7 1 3\n",
+ ""
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⌽⍋vec"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "union-carolina",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "4 8 10 5 2 6 9 1 7 3\n",
+ ""
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⍒vec"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dental-audit",
+ "metadata": {},
+ "source": [
+ "Let us compare `⌽⍋vec` with `⍒vec`:\n",
+ "\n",
+ "```APL\n",
+ "4 8 10 5 9 6 2 7 1 3 ⍝ ⌽⍋vec\n",
+ "4 8 10 5 2 6 9 1 7 3 ⍝ ⍒vec\n",
+ " ↑ ↑ ↑\n",
+ "```\n",
+ "\n",
+ "Notice how the numbers `2 6 9` are in opposite directions in the two results.\n",
+ "That is because those indices refer to the three times the number 60 shows up in `vec`.\n",
+ "\n",
+ "Both when using _grade up_ and _grade down_, the indices show up in the order `2 6 9` because both grades preserve the relative positions of the items that are the same.\n",
+ "Thus, when we _reverse_ the result of _grade up_, those three numbers show up as `9 6 2`.\n",
+ "\n",
+ "You may rightfully wonder why the functions _grade up_ and _grade down_ return indices instead of sorted data.\n",
+ "There are, of course, good reasons for it, as we will show here.\n",
+ "\n",
+ "Firstly, the availability of the intermediate result of the sorting process opens up possibilities for interesting and useful manipulations.\n",
+ "Here is one example: given a vector `v` of unique values, the expression `⍋⍋v` (or `⍋⍒v`) indicates which position the items of `v` would occupy if they were sorted in increasing (or decreasing) order."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "referenced-batman",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "8 2 5 1 3 6 7 4\n",
+ ""
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "class ← 153 432 317 609 411 227 186 350\n",
+ "⍋⍒class"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "small-ethics",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "153 432 317 609 411 227 186 350\n",
+ " 8 2 5 1 3 6 7 4\n",
+ ""
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "class,[.5]⍋⍒class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "premium-cyprus",
+ "metadata": {},
+ "source": [
+ "The result above shows that `153` would be sent to position `8` if `class` were ordered in decreasing order, the `432` would be in position `2`, and so on.\n",
+ "Here is the verification:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "prostate-infection",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "609 432 411 350 317 227 186 153\n",
+ ""
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← classSorted ← class[⍒class]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "difficult-korea",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "classSorted[8]≡153"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "communist-causing",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "classSorted[2]≡432"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "entire-sellers",
+ "metadata": {},
+ "source": [
+ "Secondly, it makes it much easier to sort complementary arrays (e.g., arrays that represent columns in a database table) in the same order.\n",
+ "Suppose, for example, that we would like to sort our familiar lists of prices and quantities in ascending order of quantity:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "decreased-wedding",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "price ← 5.2 11.5 3.6 4 8.45\n",
+ "qty ← 2 1 3 6 2\n",
+ "idx ← ⍋qty"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "major-bradford",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "11.5 5.2 8.45 3.6 4\n",
+ ""
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← price ← price[idx]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "acoustic-mountain",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 2 2 3 6\n",
+ ""
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← qty ← qty[idx]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "noted-danish",
+ "metadata": {},
+ "source": [
+ "#### Sorting Numeric Matrices\n",
+ "\n",
+ "The functions _grade up_ and _grade down_ can be used to sort the rows of a matrix.\n",
+ "\n",
+ "In this case, they sort the rows by comparing the first element of each row; then, for rows that have the same first element, they compare second elements; and so on and so forth.\n",
+ "Both functions return a vector of row indices, which can be used to sort the matrix (do not forget the semicolon when indexing)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "regulation-fluid",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2 40 8\n",
+ "5 55 2\n",
+ "2 33 9\n",
+ "7 20 2\n",
+ "5 55 1\n",
+ "5 52 9\n",
+ "2 40 9\n",
+ ""
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← matrix ← 7 3⍴2 40 8 5 55 2 2 33 9 7 20 2 5 55 1 5 52 9 2 40 9"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "judicial-focus",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2 33 9\n",
+ "2 40 8\n",
+ "2 40 9\n",
+ "5 52 9\n",
+ "5 55 1\n",
+ "5 55 2\n",
+ "7 20 2\n",
+ ""
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "matrix[⍋matrix;]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "smart-differential",
+ "metadata": {},
+ "source": [
+ "In the result above, you can see that the first column is sorted in ascending order.\n",
+ "Then, when the first column has the same element, the second column is sorted in ascending order.\n",
+ "For example, there are three rows in `matrix` that start with a `2`.\n",
+ "Those three rows are the first three rows of the sorted matrix and, among those three rows, the second column is sorted:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "random-undergraduate",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "33 9\n",
+ "40 8\n",
+ "40 9\n",
+ ""
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "3 ¯2↑matrix[⍋matrix;]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "analyzed-cleanup",
+ "metadata": {},
+ "source": [
+ "Finally, when the first and second columns are the same, we order by the third column.\n",
+ "In the example above, we can see that two rows started with `2 40`, and those two rows were then sorted according to their third column."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "tamil-rugby",
+ "metadata": {},
+ "source": [
+ "#### Sorting Numeric High-rank Arrays\n",
+ "\n",
+ "The functions _grade up_ and _grade down_ can be used to sort the major cells of numeric arrays of arbitrary rank, not just vectors and matrices.\n",
+ "\n",
+ "To sort the rows of a matrix, we compared the rows item by item.\n",
+ "To sort the planes of a cuboid, we will compare the planes row by row.\n",
+ "To sort the cuboids of a 4D array, we will compare the cuboids plane by plane.\n",
+ "And this is the reasoning that you should follow to sort the major cells of any arbitrary array.\n",
+ "\n",
+ "Consider the following cuboid:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "third-antarctica",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2 1\n",
+ "2 2\n",
+ "\n",
+ "1 2\n",
+ "2 2\n",
+ "\n",
+ "1 2\n",
+ "1 1\n",
+ "\n",
+ "2 1\n",
+ "2 1\n",
+ "\n",
+ "2 2\n",
+ "2 2\n",
+ ""
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← cuboid ← 5 2 2⍴2 1 2 2 1 2 2 2 1 2 1 1 2 1 2 1 2 2 2 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "coupled-alberta",
+ "metadata": {},
+ "source": [
+ "Looking at all of its planes, we see two matrices whose first row is `1 2`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "comprehensive-foundation",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 2\n",
+ "2 2\n",
+ "\n",
+ "1 2\n",
+ "1 1\n",
+ ""
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cuboid[2 3;;]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "competent-testimony",
+ "metadata": {},
+ "source": [
+ "Those two matrices have the smallest first row, so those two matrices will be the first two matrices in the sorted cuboid:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "id": "offshore-heath",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 2\n",
+ "1 1\n",
+ "\n",
+ "1 2\n",
+ "2 2\n",
+ ""
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "2↑cuboid[⍋cuboid;;]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "olive-vienna",
+ "metadata": {},
+ "source": [
+ "However, they show up in the reverse order in the sorted cuboid because the two matrices have the same first row, thus we need to compare the second row of each matrix to determine which matrix comes first.\n",
+ "\n",
+ "This is the type of reasoning that must be followed to sort the whole cuboid:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "id": "necessary-morrison",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 2\n",
+ "1 1\n",
+ "\n",
+ "1 2\n",
+ "2 2\n",
+ "\n",
+ "2 1\n",
+ "2 1\n",
+ "\n",
+ "2 1\n",
+ "2 2\n",
+ "\n",
+ "2 2\n",
+ "2 2\n",
+ ""
+ ]
+ },
+ "execution_count": 21,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cuboid[⍋cuboid;;]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "surface-evidence",
+ "metadata": {},
+ "source": [
+ "### Sorting Characters\n",
+ "\n",
+ "#### Using the Default Alphabet\n",
+ "\n",
+ "When applied to characters, the monadic forms of _grade up_ and _grade down_ refer to an implicit alphabetic order.\n",
+ "In all modern versions of Dyalog (the _Unicode Editions_), it is the numerical order of the corresponding Unicode code points.\n",
+ "However, in the _Classic Editions_ of Dyalog, it is given by a specific _system variable_ known as the _atomic vector_ `⎕AV`, described in [the chapter about system interfaces](./System-Interfaces.ipynb).\n",
+ "\n",
+ "For example, all modern versions of Dyalog give this result:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "id": "regional-montana",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " CGUaaaacdeehklnoooprrrrssstw\n",
+ ""
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "text ← 'Grade Up also works on Characters'\n",
+ "text[⍋text]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "relevant-cologne",
+ "metadata": {},
+ "source": [
+ "In _Classic Editions_ of Dyalog, the result would have been `' aaaacdeehjlnoooprrrrssstwCGU'`.\n",
+ "That is because the vector `⎕AV` has the lower case characters before the upper case ones, which means all the lower case letters will be sorted before all the upper case letters.\n",
+ "However, in the _Unicode Editions_, text is sorted according to Unicode code points, and in the Unicode standard, the upper case letters come before the lower case letters.\n",
+ "This is why we obtained two different results.\n",
+ "\n",
+ "For this reason, sorting characters using the default alphabet should be reserved for text that contains only lower case or only upper case letters, or matrices where upper and lower case letters appear in the same columns, as in the following example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "id": "challenging-mount",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Canberra \n",
+ "Paris \n",
+ "Washington\n",
+ "Moscow \n",
+ "Martigues \n",
+ "Mexico \n",
+ ""
+ ]
+ },
+ "execution_count": 23,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← towns ← ↑'Canberra' 'Paris' 'Washington' 'Moscow' 'Martigues' 'Mexico'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "id": "absolute-motion",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Canberra \n",
+ "Martigues \n",
+ "Mexico \n",
+ "Moscow \n",
+ "Paris \n",
+ "Washington\n",
+ ""
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "towns[⍋towns;]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "domestic-rebecca",
+ "metadata": {},
+ "source": [
+ "The names are sorted correctly because all letters in each column are of the same case (all are upper case, or all are lower case).\n",
+ "However, if we mix upper and lower case letters, we may not get the result we expected.\n",
+ "\n",
+ "For example, here we redefine the matrix `towns` but we are not careful, and thus do not capitalise properly all of the names of the cities:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "id": "electoral-reverse",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "canberra \n",
+ "paris \n",
+ "Washington\n",
+ "Moscow \n",
+ "Martigues \n",
+ "Mexico \n",
+ ""
+ ]
+ },
+ "execution_count": 25,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← towns ← ↑'canberra' 'paris' 'Washington' 'Moscow' 'Martigues' 'Mexico'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "egyptian-protest",
+ "metadata": {},
+ "source": [
+ "Because capitalisation was not uniform, sorting will differ wildly from what we obtained before:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "id": "surface-stanley",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Martigues \n",
+ "Mexico \n",
+ "Moscow \n",
+ "Washington\n",
+ "canberra \n",
+ "paris \n",
+ ""
+ ]
+ },
+ "execution_count": 26,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "towns[⍋towns;]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "thick-offset",
+ "metadata": {},
+ "source": [
+ "In the result above, `'Washington'` (which was supposed to be the last row) came before `'canberra'` (which was supposed to be the first row) because all the upper case letters come before the lower case letters in the implicit alphabet that defines the order of characters."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "gothic-monroe",
+ "metadata": {},
+ "source": [
+ "#### Using an Explicit Alphabet\n",
+ "\n",
+ "To avoid this kind of problem, it is advisable to use the dyadic versions of the sorting primitives, which take an explicit alphabet as their left argument (notice it contains a blank in the end).\n",
+ "Let us try this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "id": "increased-stocks",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "canberra \n",
+ "Martigues \n",
+ "Mexico \n",
+ "Moscow \n",
+ "paris \n",
+ "Washington\n",
+ ""
+ ]
+ },
+ "execution_count": 27,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "alph ← 'aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ '\n",
+ "towns[alph⍋towns;]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "outside-discussion",
+ "metadata": {},
+ "source": [
+ "Alas, our satisfaction will be short lived!\n",
+ "Imagine that `'Mexico'` now becomes `'mexico'`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "id": "textile-gauge",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "canberra \n",
+ "paris \n",
+ "Washington\n",
+ "Moscow \n",
+ "Martigues \n",
+ "mexico \n",
+ ""
+ ]
+ },
+ "execution_count": 28,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "towns[6;1] ← 'm'\n",
+ "⎕← towns"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "likely-jackson",
+ "metadata": {},
+ "source": [
+ "In our alphabet, a lower case \"m\" comes before the upper case \"M\", so we would obtain:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "id": "about-forward",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "canberra \n",
+ "mexico \n",
+ "Martigues \n",
+ "Moscow \n",
+ "paris \n",
+ "Washington\n",
+ ""
+ ]
+ },
+ "execution_count": 29,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "towns[alph⍋towns;]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "horizontal-exhibit",
+ "metadata": {},
+ "source": [
+ "There is fortunately a solution to this annoying problem.\n",
+ "Instead of a vector, let us organise our alphabet into a matrix, as shown here:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "id": "attached-mambo",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "abcdefghijklmnopqrstuvwxyz \n",
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ \n",
+ ""
+ ]
+ },
+ "execution_count": 30,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← alph ← ⍉27 2⍴alph,' '"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "nonprofit-adobe",
+ "metadata": {},
+ "source": [
+ "Although you cannot see it, note that the last column contains a blank in both rows:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "id": "adjusted-pharmacy",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "abcdefghijklmnopqrstuvwxyz #\n",
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ #\n",
+ ""
+ ]
+ },
+ "execution_count": 31,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "alph,'#'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "duplicate-order",
+ "metadata": {},
+ "source": [
+ "Now, \"m\" and \"M\" have the same priority because they are placed in the same columns of the alphabet.\n",
+ "When sorting a matrix, the lower case letters and the upper case letters will now be sorted identically:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "id": "present-damage",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "canberra \n",
+ "Martigues \n",
+ "mexico \n",
+ "Moscow \n",
+ "paris \n",
+ "Washington\n",
+ ""
+ ]
+ },
+ "execution_count": 32,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "towns[alph⍋towns;]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "textile-cotton",
+ "metadata": {},
+ "source": [
+ "Note that if we include the same town twice, but with different capitalisation, the lower case version will come first:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "id": "japanese-priest",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "canberra \n",
+ "paris \n",
+ "Washington\n",
+ "Moscow \n",
+ "Martigues \n",
+ "mexico \n",
+ "washington\n",
+ ""
+ ]
+ },
+ "execution_count": 33,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "towns ⍪← 'washington'\n",
+ "⎕← towns"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "id": "unknown-coordinator",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "canberra \n",
+ "Martigues \n",
+ "mexico \n",
+ "Moscow \n",
+ "paris \n",
+ "washington\n",
+ "Washington\n",
+ ""
+ ]
+ },
+ "execution_count": 34,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "towns[alph⍋towns;]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "derived-refund",
+ "metadata": {},
+ "source": [
+ "### Grading and Permutations\n",
+ "\n",
+ "Suppose the function _grade up_ or _grade down_ is applied to a vector of length `n`.\n",
+ "The result of the function is going to be what mathematicians call a _permutation_.\n",
+ "\n",
+ "A permutation (of size `n`) is a vector of length `n` that contains all integers from `1` to `n` in _any_ order.\n",
+ "Intuitively speaking, a permutation is the vector `⍳n` shuffled.\n",
+ "\n",
+ "As we have seen above, permutation vectors can be used to index into other vectors (or arbitrary arrays) and change the orders of their contents.\n",
+ "That is one of the primary use cases of permutation vectors and that is, essentially, the intended use case of the permutation vectors returned by the functions _grade up_ and _grade down_:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "id": "valuable-anxiety",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "word ← 'MISSISSIPPI'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "id": "atlantic-governor",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2 5 8 11 1 9 10 3 4 6 7\n",
+ ""
+ ]
+ },
+ "execution_count": 36,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← permutationVector ← ⍋word"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "august-vertical",
+ "metadata": {},
+ "source": [
+ "As we can see, the result is a permutation vector of size 11 because it contains all the integers from 1 to 11.\n",
+ "Then, we can use that permutation vector to reorder other vectors of length 11.\n",
+ "For example, we can use the permutation vector to sort the original character vector:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "id": "precious-prescription",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "IIIIMPPSSSS\n",
+ ""
+ ]
+ },
+ "execution_count": 37,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "word[permutationVector]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "collaborative-scottish",
+ "metadata": {},
+ "source": [
+ "When a vector (or another array) is indexed by a permutation vector, we know that:\n",
+ "\n",
+ " - the result of the indexing will have as many elements as the array being indexed;\n",
+ " - no element of the array being indexed will be repeated; and\n",
+ " - all elements of the array being indexed will be in the final result.\n",
+ "\n",
+ "These characteristics of the result of the indexing operation all follow from the fact that the indexing vector is a permutation vector.\n",
+ "\n",
+ "Finally, if you look at permutation vectors as vectors that allow you to change the order of things, sometimes you might want to change the order **back**.\n",
+ "If `p` is a permutation vector, then `⍋p` is the inverse permutation vector.\n",
+ "So, if you use the permutation vector `p` to reorder something, you can use the permutation vector `⍋p` to undo that reordering.\n",
+ "\n",
+ "In other words, if `vp ← v[p]`, then `v ≡ vp[⍋p]`.\n",
+ "\n",
+ "Here is an example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "id": "configured-equipment",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 38,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "wordP ← word[permutationVector] ⍝ reorder the word\n",
+ "word ≡ wordP[⍋permutationVector] ⍝ go back"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "sapphire-morocco",
+ "metadata": {},
+ "source": [
+ "### Finding Values\n",
+ "\n",
+ "The function _find_ `⍷`, which you can type with APL+Shift+E, is a primitive function that allows you to search for an array `needle` in an array `haystack`.\n",
+ "The result is a Boolean array with the same shape as `haystack`, with a 1 at the starting point of each occurrence of `needle` in `haystack`.\n",
+ "Here is an example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "id": "indirect-removal",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0 0 0 0 0 1 0 0 0 1 0 0 0 0 0\n",
+ ""
+ ]
+ },
+ "execution_count": 39,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← where ← 'at' ⍷ 'Congratulations'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 40,
+ "id": "governing-amount",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "C o n g r a t u l a t i o n s\n",
+ "0 0 0 0 0 1 0 0 0 1 0 0 0 0 0\n",
+ ""
+ ]
+ },
+ "execution_count": 40,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "'Congratulations',[.5]where"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "corrected-badge",
+ "metadata": {},
+ "source": [
+ "The function _find_ can also be applied to numeric arrays.\n",
+ "Here, we search for a vector of 3 numbers in a longer vector:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 41,
+ "id": "flying-shirt",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0\n",
+ ""
+ ]
+ },
+ "execution_count": 41,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "2 5 1 ⍷ 4 8 2 5 1 6 4 2 5 3 5 1 2 2 5 1 7"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "false-safety",
+ "metadata": {},
+ "source": [
+ "The rank of `haystack` can be higher than the rank of `needle`.\n",
+ "For example, we can search for a vector in a matrix:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "id": "failing-smith",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0 0 0 0 0 0 0 0 0 0\n",
+ "0 0 0 0 0 0 0 0 0 0\n",
+ "0 1 0 0 0 0 0 0 0 0\n",
+ "0 0 0 0 0 0 0 0 0 0\n",
+ "0 0 0 0 0 0 0 0 0 0\n",
+ "0 0 0 0 0 0 0 0 0 0\n",
+ "0 1 0 0 0 0 0 0 0 0\n",
+ ""
+ ]
+ },
+ "execution_count": 42,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "'as' ⍷ towns"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "gorgeous-humidity",
+ "metadata": {},
+ "source": [
+ "The opposite is also permitted (i.e., the left argument having a higher rank than the right argument), but would not be very useful.\n",
+ "The result would be only zeroes."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "rental-alias",
+ "metadata": {},
+ "source": [
+ "## Encode and Decode\n",
+ "\n",
+ "APL offers two primitives, _encode_ `⊤` and _decode_ `⊥` – typed with APL+n and APL+b, respectively –, to convert numeric values from their decimal form to a representation in any other number system, and back again.\n",
+ "\n",
+ "Because we may not be very familiar with this kind of calculation, it may seem that only mad mathematicians should invest their time studying such conversions.\n",
+ "In fact, these functions are used rather frequently to solve common problems.\n",
+ "But before studying them, we need to present some basic notions."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "reasonable-framing",
+ "metadata": {},
+ "source": [
+ "### Some Words of Theory\n",
+ "\n",
+ "#### Familiar, But Not Decimal\n",
+ "\n",
+ "8839 is a simple number, represented in our good old decimal system.\n",
+ "But if 8839 represents a number of seconds, we could just express it as 2 hours, 27 minutes, and 19 seconds.\n",
+ "`2 27 19` is the representation of `8839` in a non-uniform number system based on 24 hour days, each divided into 60 minutes, each divided into 60 seconds.\n",
+ "\n",
+ "The second representation is more familiar to us, but is **not** a decimal representation: the value has been expressed in a complex _base_ (or _radix_); we shall say that it is **coded**, even though it is familiar.\n",
+ "\n",
+ "Converting `8839` into `2 27 19` is called _encoding_ because the result is not decimal.\n",
+ "Converting `2 27 19` into `8839` is called _decoding_ because the result is decimal.\n",
+ "We shall say that `24 60 60` is the _base_ of the number system of `2 27 19`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "paperback-pierre",
+ "metadata": {},
+ "source": [
+ "#### Three Important Remarks\n",
+ "\n",
+ " - In this case, encoding a scalar (`8839`) produces a vector (`2 27 19`).\n",
+ " In general, the representation of a decimal scalar in a non-decimal base cannot be expected to be a single number.\n",
+ " It will always be an array of the same shape as the left argument to the function _encode_, and in all but very special cases this left argument will be a vector or a higher-rank array.\n",
+ "\n",
+ "For example, a binary value cannot be written as `101011` (this is a decimal number); it must be written as a vector of binary digits: `1 0 1 0 1 1`.\n",
+ "\n",
+ " - The items of an encoded value can be greater than 9.\n",
+ " In our examples, we had items equal to 27 and 19.\n",
+ " But they are always smaller than the corresponding item of the _base_.\n",
+ " Would you say that you spent 2 hours and 87 minutes to do something?\n",
+ " Probably not, because 87 is greater than 60.\n",
+ " You would say 3 hours and 27 minutes.\n",
+ "\n",
+ " - No mater whether days were made of only 18 hours, 2 hours, 27 minutes, and 19 seconds would always represent 8839 seconds.\n",
+ "\n",
+ "This leads to the following rule: the first item of the base vector is never taken into account when _decoding_, but it is always used for _encoding_."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "duplicate-cassette",
+ "metadata": {},
+ "source": [
+ "#### Base and Weights\n",
+ "\n",
+ "Given the _base_ vector `24 60 60` and a value `2 27 19`, one can _decode_ it (obtain its decimal representation) by any of the three formulas that follow.\n",
+ "(Note that `3600=60×60`.)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "id": "sound-kuwait",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "8839\n",
+ ""
+ ]
+ },
+ "execution_count": 43,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(2×3600) + (27×60) + 19"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "id": "fitting-kazakhstan",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "8839\n",
+ ""
+ ]
+ },
+ "execution_count": 44,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "+/ 3600 60 1 × 2 27 19"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "id": "classified-grounds",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "8839\n",
+ ""
+ ]
+ },
+ "execution_count": 45,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "3600 60 1 +.× 2 27 19"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fourth-primary",
+ "metadata": {},
+ "source": [
+ "The last formula clearly shows that decoding a set of values is nothing else than an _inner product_.\n",
+ "This is important because it means that the same shape compatibility rules will apply.\n",
+ "\n",
+ "The values `3600 60 1` are the _weights_ representing how many seconds are in an hour, in a minute, and in a second.\n",
+ "They can be obtained from the _base_ vector as follows:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "id": "marine-jersey",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "3600 60 1\n",
+ ""
+ ]
+ },
+ "execution_count": 46,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "base ← 24 60 60\n",
+ "⎕← weights ← ⌽ 1,×\\ ⌽ 1↓base"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "infectious-ensemble",
+ "metadata": {},
+ "source": [
+ "This formula confirms the remark we made earlier: the first item of the _base_ vector is not used when _decoding_ a value.\n",
+ "However, it is needed in order to do the reverse operation (_encoding_) using the same _base_ vector.\n",
+ "\n",
+ "Once the weights are calculated, we can define the relationship between _decode_ and _inner product_:\n",
+ "\n",
+ "\n",
+ "***Rule***:\n",
+ "\n",
+ " > `base ⊥ values` is equivalent to `weights +.× values`.\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "changed-quantity",
+ "metadata": {},
+ "source": [
+ "### Using Decode & Encode\n",
+ "\n",
+ "#### Decode\n",
+ "\n",
+ "_Decode_ is represented by `⊥`.\n",
+ "It accepts the _base_ vector directly as its left argument, so that you do not have to calculate the weights:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "id": "greenhouse-telling",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "8839\n",
+ ""
+ ]
+ },
+ "execution_count": 47,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "base ⊥ 2 27 19"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "modified-insulation",
+ "metadata": {},
+ "source": [
+ "Because the first item of the base vector is not used when decoding, we could have obtained the same result in this way:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 48,
+ "id": "female-distinction",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "8839\n",
+ ""
+ ]
+ },
+ "execution_count": 48,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "base ← 0 60 60\n",
+ "base ⊥ 2 27 19"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "perceived-european",
+ "metadata": {},
+ "source": [
+ "As another example application of the function _decode_, suppose eggs are packaged in boxes, each containing 6 packs of 6 eggs.\n",
+ "\n",
+ "If we have 2 full boxes, plus 5 packs, plus 3 eggs, we can calculate the total number of eggs using any of the following expressions, each giving the same result:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "id": "wireless-coalition",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "105\n",
+ ""
+ ]
+ },
+ "execution_count": 49,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(2×36) + (5×6) + 3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 50,
+ "id": "documentary-original",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "105\n",
+ ""
+ ]
+ },
+ "execution_count": 50,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "+/ 36 6 1 × 2 5 3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "id": "continuing-wesley",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "105\n",
+ ""
+ ]
+ },
+ "execution_count": 51,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "36 6 1 +.× 2 5 3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 52,
+ "id": "starting-vertical",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "105\n",
+ ""
+ ]
+ },
+ "execution_count": 52,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "6 6 6 ⊥ 2 5 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "advisory-schedule",
+ "metadata": {},
+ "source": [
+ "The first item of the base is not relevant when decoding, so we could have used any other element:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "id": "searching-neighborhood",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "105\n",
+ ""
+ ]
+ },
+ "execution_count": 53,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "999 6 6 ⊥ 2 5 3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 54,
+ "id": "bibliographic-demonstration",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "105\n",
+ ""
+ ]
+ },
+ "execution_count": 54,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "0 6 6 ⊥ 2 5 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "young-burning",
+ "metadata": {},
+ "source": [
+ "However, in this very special case, our base is uniform (`6 6 6`), which lets us write the last expression in a simpler way.\n",
+ "As usual, the scalar value is reused as appropriate:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 55,
+ "id": "empty-november",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "105\n",
+ ""
+ ]
+ },
+ "execution_count": 55,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "6 ⊥ 2 5 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fuzzy-liberty",
+ "metadata": {},
+ "source": [
+ "This says that `2 5 3` is the base 6 representation of 105."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "republican-working",
+ "metadata": {},
+ "source": [
+ "#### Shape Compatibility\n",
+ "\n",
+ "We said that _decode_ is nothing but a plain _inner product_, so the same shape compatibility rules must be satisfied.\n",
+ "\n",
+ "Imagine that we have to convert two durations given in hours, minutes, and seconds, into just seconds.\n",
+ "The first duration is 2 hours, 27 minutes, and 19 seconds, and the second one is 5 hours, 3 minutes, and 48 seconds.\n",
+ "When we put those durations into a single variable, it is natural to express the data as a matrix, as shown here:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 56,
+ "id": "bridal-devon",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2 27 19\n",
+ "5 3 48\n",
+ ""
+ ]
+ },
+ "execution_count": 56,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← hms ← 2 3⍴2 27 19 5 3 48"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "shaped-crime",
+ "metadata": {},
+ "source": [
+ "But we cannot combine a 3-item vector (`base ← 24 60 60`) with a 2-row matrix (`hms`).\n",
+ "`base ⊥ hms` would cause a `LENGTH ERROR`.\n",
+ "We must transpose `hms` in order to make the lengths of the arguments compatible:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 57,
+ "id": "laughing-substance",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "8839 18228\n",
+ ""
+ ]
+ },
+ "execution_count": 57,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "base ⊥ ⍉hms"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "higher-indie",
+ "metadata": {},
+ "source": [
+ "The length of `base` is equal to the length of the first dimension of `⍉hms`.\n",
+ "The same rule applies to _inner product_."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "several-trust",
+ "metadata": {},
+ "source": [
+ "#### Encode\n",
+ "\n",
+ "As an example of encoding a decimal number, we can encode 105 into base 6:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 58,
+ "id": "modular-yeast",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2 5 3\n",
+ ""
+ ]
+ },
+ "execution_count": 58,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "6 6 6 ⊤ 105"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "incredible-trading",
+ "metadata": {},
+ "source": [
+ "Please note that specifying a scalar as the left argument in the expression above does not give the same result:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 59,
+ "id": "distributed-hayes",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "3\n",
+ ""
+ ]
+ },
+ "execution_count": 59,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "6 ⊤ 105"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "prostate-audio",
+ "metadata": {},
+ "source": [
+ "The reason is that it is not really possible for APL to \"reuse the scalar as appropriate\" here, because what does \"appropriate\" mean in this case?\n",
+ "The left argument to _encode_ defines the number of digits in the new number system, so if we want or need three digits, we must specify three `6`s.\n",
+ "(In [a previous Specialist's Section](./Operators.ipynb#The-Result-of-an-Inverse-Function) you find an example showing a clever way to have APL itself figure out the number of digits needed to properly encode a number.)\n",
+ "\n",
+ "We can convert a number of seconds into hours, minutes, and seconds, like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 60,
+ "id": "hungarian-seminar",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "6 30 56\n",
+ ""
+ ]
+ },
+ "execution_count": 60,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "24 60 60 ⊤ 23456"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "crucial-halloween",
+ "metadata": {},
+ "source": [
+ "However, when converting 3 values, the results must be read carefully:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "id": "stone-dealer",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " 2 6 5\n",
+ "27 30 3\n",
+ "19 56 48\n",
+ ""
+ ]
+ },
+ "execution_count": 61,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "24 60 60 ⊤ 8839 23456 18228"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "optimum-small",
+ "metadata": {},
+ "source": [
+ "Do not read these results horizontally: 8839 seconds are **not** equal to 2 hours, 5 minutes, and 2 seconds!\n",
+ "You must read the result vertically, and you will recognise the results we got earlier: `2 27 19`, `6 30 56`, and `5 3 48`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "floppy-niger",
+ "metadata": {},
+ "source": [
+ "#### Limited Encoding\n",
+ "\n",
+ "The shape of the result of `bases ⊤ values` is equal to `bases ,⍥⍴ values`.\n",
+ "\n",
+ "No specific rule is imposed on the arguments' shapes, but if the last dimension of the base is too small, APL proceeds to a limited encoding, as shown below:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 62,
+ "id": "caring-disco",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2 27 19\n",
+ ""
+ ]
+ },
+ "execution_count": 62,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "24 60 60 ⊤ 8839 ⍝ full conversion"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 63,
+ "id": "liked-usage",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "27 19\n",
+ ""
+ ]
+ },
+ "execution_count": 63,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "60 60 ⊤ 8839 ⍝ truncated result"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 64,
+ "id": "armed-seventh",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "19\n",
+ ""
+ ]
+ },
+ "execution_count": 64,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "60 ⊤ 8839 ⍝ ditto"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "plastic-sacramento",
+ "metadata": {},
+ "source": [
+ "The last two results are truncated to the length of the specified base vector, but nothing indicates that they have been truncated.\n",
+ "To avoid potential misinterpretation, it is common to use a leading zero as the first item of the base:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 65,
+ "id": "vocal-underwear",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 10 17 36\n",
+ ""
+ ]
+ },
+ "execution_count": 65,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "0 24 60 60 ⊤ 123456"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 66,
+ "id": "quarterly-bishop",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "34 17 36\n",
+ ""
+ ]
+ },
+ "execution_count": 66,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "0 60 60 ⊤ 123456"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 67,
+ "id": "confidential-irish",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2057 36\n",
+ ""
+ ]
+ },
+ "execution_count": 67,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "0 60 ⊤ 123456"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 68,
+ "id": "advised-playback",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "123456\n",
+ ""
+ ]
+ },
+ "execution_count": 68,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "0 ⊤ 123456"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "aquatic-hughes",
+ "metadata": {},
+ "source": [
+ "The first conversion states that 123456 seconds represent 1 day, 10 hours, 17 minutes, and 36 seconds.\n",
+ "In the second conversion, limited to hours, 1 day plus 10 hours gave 34 hours.\n",
+ "In the third conversion, limited to minutes, shows that 123456 seconds is 2057 minutes and 36 seconds."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "radical-vegetable",
+ "metadata": {},
+ "source": [
+ "#### Using Several Simultaneous Bases\n",
+ "\n",
+ "If one needs to encode or decode several values in several different bases, _base_ will no longer be a vector, but a matrix.\n",
+ "However, this is a bit more complex and will be studied in [the Specialist's Section](#the-Specialist's-Section)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "universal-brooklyn",
+ "metadata": {},
+ "source": [
+ "### Applications of Encoding and Decoding\n",
+ "\n",
+ "#### Condense or Expand Values\n",
+ "\n",
+ "It is sometimes convenient to condense several values into a single one.\n",
+ "In general, this does not save much memory space, but it may be more convenient to manipulate a single value rather than several.\n",
+ "This can be achieved by decoding the values into a decimal number.\n",
+ "\n",
+ "Say, for example, that you have a list of 5 rarely used settings that you need to save in a relational database.\n",
+ "Instead of creating 5 columns in the database table to hold the settings, you could _decode_ the 5 values into a decimal integer and save it in a single database column.\n",
+ "\n",
+ "Often, it is convenient to select a base made of powers of 10, corresponding to the maximum number of digits of the given values, for example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 69,
+ "id": "theoretical-pontiac",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "35681724\n",
+ ""
+ ]
+ },
+ "execution_count": 69,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "100 1000 10 100 ⊥ 35 681 7 24"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "composed-federal",
+ "metadata": {},
+ "source": [
+ "The single value `35681724` contains the same information as the original vector, and because all base values used are powers of 10, it is fairly easy to recognise the original numbers.\n",
+ "\n",
+ "The conversion base was built like this:\n",
+ "\n",
+ " - 100 for 35 which has 2 digits;\n",
+ " - 1000 for 681 which has 3 digits;\n",
+ " - 10 for 7 which has only 1 digit; and\n",
+ " - 100 for 24 which has 2 digits.\n",
+ "\n",
+ "Of course, the base vector must be built according to the largest values that can appear in each of the items, not just from an arbitrary number as in our small example.\n",
+ "\n",
+ "The reverse transformation may be done by encoding the value using the same base:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 70,
+ "id": "geographic-replication",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "35 681 7 24\n",
+ ""
+ ]
+ },
+ "execution_count": 70,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "100 1000 10 100 ⊤ 35681724"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "comprehensive-guidance",
+ "metadata": {},
+ "source": [
+ "A similar technique may be used to separate the integer and decimal parts of positive numbers:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 71,
+ "id": "sapphire-trader",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "127 619 423 19\n",
+ " 0.83 0.26 0.44 0.962\n",
+ ""
+ ]
+ },
+ "execution_count": 71,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "0 1⊤127.83 619.26 423.44 19.962"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "refined-chance",
+ "metadata": {},
+ "source": [
+ "#### Calculating Polynomials\n",
+ "\n",
+ "Let us recall the example about packing eggs:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 72,
+ "id": "incorporate-rider",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "105\n",
+ ""
+ ]
+ },
+ "execution_count": 72,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "6⊥2 5 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "suitable-polls",
+ "metadata": {},
+ "source": [
+ "We can say that we used _decode_ to calculate `(2×6*2) + (5×6) + 3`.\n",
+ "In other words, using traditional math notation, we calculated $2x^2 + 5x + 3$ for $x = 6$.\n",
+ "This example shows that _decode_ can be used to calculate a polynomial represented by the coefficients of the unknown variable, sorted according to the decreasing powers of the variable.\n",
+ "\n",
+ "For example, to calculate $3x^4 + 2x^2 - 7x + 2$ for $x = 1.2$, we can write"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 73,
+ "id": "distributed-bullet",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2.7008\n",
+ ""
+ ]
+ },
+ "execution_count": 73,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "1.2 ⊥ 3 0 2 ¯7 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "electrical-stationery",
+ "metadata": {},
+ "source": [
+ "Do not forget zero coefficients for the missing powers of $x$ (here, we have no term in $x^3$).\n",
+ "\n",
+ "This is equivalent to"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 74,
+ "id": "competent-heater",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2.7008\n",
+ ""
+ ]
+ },
+ "execution_count": 74,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(1.2 * 4 3 2 1 0) +.× 3 0 2 ¯7 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "basic-motorcycle",
+ "metadata": {},
+ "source": [
+ "To calculate the value of a polynomial for several values of $x$, the values must be placed in a vertical 1-column matrix, to be compliant with the shape compatibility rules.\n",
+ "For example, to calculate the same polynomial for $x$ varying from 0 to 2 by steps of 0.2, we could write:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 75,
+ "id": "secret-quarter",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0 0.2 0.4 0.6 0.8 1 1.2 1.4 1.6 1.8 2\n",
+ ""
+ ]
+ },
+ "execution_count": 75,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← xs ← 0.2 × ¯1+⍳11"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 76,
+ "id": "characteristic-anderson",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " 2.00 0.68 ¯0.40 ¯1.09 ¯1.09 0.00 2.70 7.64 15.58 27.37 44.00\n",
+ ""
+ ]
+ },
+ "execution_count": 76,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "2⍕ (⍪xs) ⊥ 3 0 2 ¯7 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "applied-mission",
+ "metadata": {},
+ "source": [
+ "The results have been displayed with only 2 decimal digits using an appropriate _format_.\n",
+ "\n",
+ "Finally, let us calculate $4x^4 + 2x^3 + 3x^2 - x - 6$ for $x = -1.5$:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 77,
+ "id": "located-optics",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "15.75\n",
+ ""
+ ]
+ },
+ "execution_count": 77,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "¯1.5 ⊥ 4 2 3 ¯1 ¯6"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "serious-hudson",
+ "metadata": {},
+ "source": [
+ "Notice how we used a negative base.\n",
+ "That may seem strange, but there is nothing mathematically wrong with it."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "possible-jefferson",
+ "metadata": {},
+ "source": [
+ "#### Right-aligning Text\n",
+ "\n",
+ "We mentioned that _decode_ could be replaced by an _inner product_ using the following equivalence: `base ⊥ values ←→ weights +.× values`, if `weights ← ⌽1,×\\⌽1↓base`.\n",
+ "\n",
+ "What happens if the vector `base` contains one or more zeroes?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 78,
+ "id": "decimal-arbitration",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2400 300 60 20 2 1\n",
+ ""
+ ]
+ },
+ "execution_count": 78,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⌽1,×\\⌽1↓10 8 5 3 10 2 ⍝ base has no zeroes"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 79,
+ "id": "pointed-nancy",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0 0 60 20 2 1\n",
+ ""
+ ]
+ },
+ "execution_count": 79,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⌽1,×\\⌽1↓10 8 0 3 10 2 ⍝ base has a zero"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "developmental-complex",
+ "metadata": {},
+ "source": [
+ "We can see that the weights to the left of a zero are all forced to become zero.\n",
+ "In other words, `10 8 0 3 10 2 ⊥ values` is strictly identical to `0 0 0 3 10 2 ⊥ values`.\n",
+ "\n",
+ "Let us use this discovery to right-align some text containing blanks:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 80,
+ "id": "strange-skiing",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "This little \n",
+ "text contains both\n",
+ "embedded and \n",
+ "trailing blanks \n",
+ ""
+ ]
+ },
+ "execution_count": 80,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← text ← ↑'This little' 'text contains both' 'embedded and' 'trailing blanks'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 81,
+ "id": "durable-compensation",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0 0 0 0 1 0 0 0 0 0 0 1 1 1 1 1 1 1\n",
+ "0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0\n",
+ "0 0 0 0 0 0 0 0 1 0 0 0 1 1 1 1 1 1\n",
+ "0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 1 1\n",
+ ""
+ ]
+ },
+ "execution_count": 81,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "text=' '"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "current-arlington",
+ "metadata": {},
+ "source": [
+ "If we use the rows of this Boolean matrix as decoding bases, the result above states that the ones placed to the left of a zero will not have any effect.\n",
+ "This means that the expression"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 82,
+ "id": "prostate-point",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "7 0 6 3\n",
+ ""
+ ]
+ },
+ "execution_count": 82,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "¯1 + 1⊥⍨text=' '"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "satisfied-shopping",
+ "metadata": {},
+ "source": [
+ "counts the number of trailing `1`s in each row.\n",
+ "Thus, we can use those numbers to rotate right each row:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 83,
+ "id": "embedded-literature",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " This little\n",
+ "text contains both\n",
+ " embedded and\n",
+ " trailing blanks\n",
+ ""
+ ]
+ },
+ "execution_count": 83,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "text⌽⍨1-1⊥⍨text=' '"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "extensive-bruce",
+ "metadata": {},
+ "source": [
+ "Another example uses the same property of _decode_: given a Boolean vector `bin`, one can find how many `1`s there are to the right of the last zero using the expression `bin⊥bin`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 84,
+ "id": "crude-nashville",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "3\n",
+ ""
+ ]
+ },
+ "execution_count": 84,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "bin ← 0 1 1 0 1 1 1 ⍝ 3 trailing ones\n",
+ "bin⊥bin"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "russian-dispatch",
+ "metadata": {},
+ "source": [
+ "## Randomised Values\n",
+ "\n",
+ "**Random numbers** are often used for demonstration purposes or to test an algorithm.\n",
+ "Strictly speaking, \"random\" would mean that a set of values is completely unpredictable.\n",
+ "This is not the case for numbers generated by a computer: they are, by definition, perfectly deterministic.\n",
+ "\n",
+ "However, the values produced by an algorithm may appear to a human being as if they were random values when, given a subset of those numbers, the human is unable to predict the next values in the sequence.\n",
+ "If this first condition is satisfied, and if all of the unique values in a long series appear approximately the same number of times, these values can be qualified as _pseudo-random_ numbers.\n",
+ "\n",
+ "In APL, the question mark `?` is used to produce pseudo-random numbers."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "enclosed-slovak",
+ "metadata": {},
+ "source": [
+ "### Deal: Dyadic Usage\n",
+ "\n",
+ "The dyadic usage of the question mark is called _deal_.\n",
+ "The expression `number ? limit` produces as many unique pseudo-random integers as specified by `number`, all among `⍳limit`.\n",
+ "For this reason, we must have `number≤limit`.\n",
+ "\n",
+ "The name of the function _deal_ relates to dealing cards.\n",
+ "When you are dealt a hand of cards from a deck, the cards that you get are arbitrary and you cannot be given the same card twice.\n",
+ "You may get cards with the same value or with the same suit, but you **cannot** get the exact same card twice.\n",
+ "\n",
+ "Some examples follow.\n",
+ "First, we deal a hand of 7 numbers out of 52:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 85,
+ "id": "historical-people",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "36 35 20 49 44 5 51\n",
+ ""
+ ]
+ },
+ "execution_count": 85,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "7?52"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "communist-extra",
+ "metadata": {},
+ "source": [
+ "If we execute the same expression again, we get a different set of values:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 86,
+ "id": "iraqi-exhaust",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "50 26 10 40 4 34 2\n",
+ ""
+ ]
+ },
+ "execution_count": 86,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "7?52"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "diverse-estate",
+ "metadata": {},
+ "source": [
+ "If `number=limit`, we get a permutation of `⍳limit`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 87,
+ "id": "sustained-religion",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "6 9 12 4 10 7 1 2 5 8 11 3\n",
+ ""
+ ]
+ },
+ "execution_count": 87,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "12?12"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "unable-medicine",
+ "metadata": {},
+ "source": [
+ "And as we mentioned before, we cannot have `number>limit`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 88,
+ "id": "proof-still",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "DOMAIN ERROR: Deal right argument must be greater than or equal to the left argument\n",
+ " 13?12\n",
+ " ∧\n"
+ ]
+ }
+ ],
+ "source": [
+ "13?12"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ignored-vacation",
+ "metadata": {},
+ "source": [
+ "### Roll: Monadic Use\n",
+ "\n",
+ "The monadic use of the question mark is called _roll_.\n",
+ "The name relates to rolling a die."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "rising-maine",
+ "metadata": {},
+ "source": [
+ "#### Positive Integer Arrays as Argument\n",
+ "\n",
+ "In the expression `?array`, where `array` is any array of positive integer values, each item `a` of `array` produces a pseudo-random value from `⍳a`, so that the result is an array of the same shape as `array`.\n",
+ "Each item of the result is calculated independently of the other items.\n",
+ "\n",
+ "Here are some examples:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 89,
+ "id": "accessible-experience",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 2 3 4\n",
+ ""
+ ]
+ },
+ "execution_count": 89,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "?1 2 3 4"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 90,
+ "id": "oriental-quarterly",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "6 2 6 2\n",
+ ""
+ ]
+ },
+ "execution_count": 90,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "?10 20 30 40"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 91,
+ "id": "independent-airplane",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "VALUE ERROR: Undefined name: mat\n",
+ " mat\n",
+ " ∧\n"
+ ]
+ }
+ ],
+ "source": [
+ "mat"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 92,
+ "id": "joined-marina",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "VALUE ERROR: Undefined name: mat\n",
+ " ?mat\n",
+ " ∧\n"
+ ]
+ }
+ ],
+ "source": [
+ "?mat"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "parliamentary-words",
+ "metadata": {},
+ "source": [
+ "If the argument of _roll_ is made of a repeated single value `v`, the result is an array made of values all taken from `⍳v`.\n",
+ "This makes it possible to produce any number of values within the same limits.\n",
+ "\n",
+ "For example, we can simulate rolling a die 10 times:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 93,
+ "id": "typical-capital",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "6 6 6 3 4 6 2 3 5 6\n",
+ ""
+ ]
+ },
+ "execution_count": 93,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "?10⍴6"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "specific-preference",
+ "metadata": {},
+ "source": [
+ "As another example, we can fill a matrix with the number `20`.\n",
+ "Then, for each of those numbers, we generate a pseudo-random number in `⍳20`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 94,
+ "id": "indie-bishop",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "15 10 1 14 16 6 11 4\n",
+ " 1 19 5 8 12 19 2 17\n",
+ " 1 15 8 3 5 7 8 11\n",
+ "12 19 2 12 17 19 14 20\n",
+ ""
+ ]
+ },
+ "execution_count": 94,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "?4 8⍴20"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "accepting-providence",
+ "metadata": {},
+ "source": [
+ "#### 0 as Argument\n",
+ "\n",
+ "The function _deal_ can also handle `0` as an argument (either as a scalar, or as element(s) of the argument array).\n",
+ "When presented with a zero, the function _deal_ will generate a pseudo-random decimal number strictly between 0 and 1:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 95,
+ "id": "faced-document",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0.355175\n",
+ ""
+ ]
+ },
+ "execution_count": 95,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "?0"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 96,
+ "id": "liberal-seating",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0.978111\n",
+ ""
+ ]
+ },
+ "execution_count": 96,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "?0"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 97,
+ "id": "outer-southwest",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0.749925\n",
+ ""
+ ]
+ },
+ "execution_count": 97,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "?0"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 98,
+ "id": "smooth-perfume",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "5 5 0.562462 0.548926 3 3\n",
+ ""
+ ]
+ },
+ "execution_count": 98,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "?6 6 0 0 3 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "medical-slope",
+ "metadata": {},
+ "source": [
+ "### Derived Usages\n",
+ "\n",
+ "#### Random Number Within an Arbitrary Range\n",
+ "\n",
+ "We have seen that the values produced by _deal_ and _roll_ are always integer values extracted from `⍳v`, where `v` is a given limit.\n",
+ "However, it is possible to obtain a set of integers or decimal values between any limits, just by adding a constant and dividing by some appropriate value.\n",
+ "\n",
+ "Imagine that we would like to obtain 10 decimal values, with 2 decimal digits, all between 743 and 761, inclusive.\n",
+ "We could follow these steps:\n",
+ "\n",
+ " - Let us first calculate integer values starting from 1:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 99,
+ "id": "stylish-latitude",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1494 514 1104 336 1586 444 1088 775 1459 1458\n",
+ ""
+ ]
+ },
+ "execution_count": 99,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "lims ← 743 761\n",
+ "⎕← set ← ? 10⍴1 + 100×-⍨/lims"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "vocal-directory",
+ "metadata": {},
+ "source": [
+ " - The values generated above are between 1 and 1801.\n",
+ " - If we add `¯1+100×743` we obtain values between 74300 and 76100:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 100,
+ "id": "nuclear-crisis",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "75793 74813 75403 74635 75885 74743 75387 75074 75758 75757\n",
+ ""
+ ]
+ },
+ "execution_count": 100,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "set +← ¯1+100×⌊/lims\n",
+ "⎕← set"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "corresponding-invitation",
+ "metadata": {},
+ "source": [
+ " - Once divided by 100, they give decimal values with two decimal places between 743 and 761, inclusive:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 101,
+ "id": "hindu-firmware",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "757.93 748.13 754.03 746.35 758.85 747.43 753.87 750.74 757.58 757.57\n",
+ ""
+ ]
+ },
+ "execution_count": 101,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "set ÷← 100\n",
+ "set"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "pacific-birthday",
+ "metadata": {},
+ "source": [
+ "We can check if the limits are well respected:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 102,
+ "id": "aggressive-charger",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "746.35 758.85\n",
+ ""
+ ]
+ },
+ "execution_count": 102,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(⌊/,⌈/) set"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "nutritional-hepatitis",
+ "metadata": {},
+ "source": [
+ "#### Sets of Random Characters\n",
+ "\n",
+ "Random characters can be obtained by indexing a set of characters by a random set of integer values smaller than or equal to the size of the character vector, as shown here:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 103,
+ "id": "organic-snake",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "CLOKIJUGZXBQPRW\n",
+ "BINSCHZCQSEYYHF\n",
+ "MMQLWFUMOBTKPJD\n",
+ ""
+ ]
+ },
+ "execution_count": 103,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕A[?3 15⍴≢⎕A]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hired-workshop",
+ "metadata": {},
+ "source": [
+ "### Random Link\n",
+ "\n",
+ "The system variable `⎕RL` has been used throughout this book when creating large(r) arrays with random values and this section will explain what `⎕RL`, called _random link_, actually does.\n",
+ "\n",
+ "#### Reproducibility When Generating Random Numbers\n",
+ "\n",
+ "The primitive functions _deal_ and _roll_ do not produce really random numbers.\n",
+ "They produce pseudo-random numbers because there is actually an algorithm that runs to generate specific numbers, although the numbers generated by this algorithm \"look random\".\n",
+ "\n",
+ "The algorithm makes use of something called a _seed value_, that you set within _random link_, and each time a new (pseudo-random) value is generated, this seed value is changed so that the next value will be different.\n",
+ "\n",
+ "Sometimes, one might want to be able to recreate a random event later on.\n",
+ "For example, when running an experiment or a simulation, you may want to generate some numbers that you did not specify explicitly, but you may need to generate the exact same numbers later.\n",
+ "This is possible thanks to _random link_.\n",
+ "\n",
+ "To reproduce a given set of random values, one can dynamically set `⎕RL` to any desired value, an integer in the range from 1 to 2147483646.\n",
+ "\n",
+ "For example, throughout the book we have used the function _roll_ to generate a 4 by 6 matrix we have called `forecast`.\n",
+ "If we did not use _random link_, each time you opened the book, you would get a different matrix:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 104,
+ "id": "broad-provincial",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "350 40 80 190 460 190\n",
+ "250 380 50 410 180 150\n",
+ "550 70 170 310 380 370\n",
+ "520 390 470 300 540 250\n",
+ ""
+ ]
+ },
+ "execution_count": 104,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "10×?4 6⍴55"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 105,
+ "id": "perceived-forward",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "360 120 50 370 510 380\n",
+ "460 140 270 230 370 380\n",
+ "550 550 450 180 160 520\n",
+ "420 470 30 330 500 500\n",
+ ""
+ ]
+ },
+ "execution_count": 105,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "10×?4 6⍴55"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 106,
+ "id": "correct-double",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "320 410 220 20 270 550\n",
+ "270 220 350 500 360 310\n",
+ "130 70 260 180 430 100\n",
+ "450 250 40 540 350 310\n",
+ ""
+ ]
+ },
+ "execution_count": 106,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "10×?4 6⍴55"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "plain-hampton",
+ "metadata": {},
+ "source": [
+ "To prevent this, before generating the matrix `forecast`, we set the _random link_ `⎕RL` to a specific value, which has been `⎕RL ← 73`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 107,
+ "id": "random-reform",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " 90 160 420 500 20 30\n",
+ "110 450 170 370 290 360\n",
+ "340 190 320 120 510 370\n",
+ "150 460 240 520 490 280\n",
+ ""
+ ]
+ },
+ "execution_count": 107,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕RL ← 73\n",
+ "⎕← forecast ← 10×?4 6⍴55"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "solar-remains",
+ "metadata": {},
+ "source": [
+ "It is not relevant that the seed was set to `73`.\n",
+ "What is relevant is that the seed **was set to a fixed value**.\n",
+ "\n",
+ "Each time you start the APL interpreter or clear the session with `)clear`, APL will reset the _random link_ so you can get pseudo-random values out of the box.\n",
+ "So, you only need to change the _random link_ when you know you will need reproducibility.\n",
+ "\n",
+ "If you have set the seed for some computations but need to go back to generating pseudo-random numbers over which you have no control, you can set the _random link_ to _zilde_:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 108,
+ "id": "every-window",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "⎕RL ← ⍬"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "northern-middle",
+ "metadata": {},
+ "source": [
+ "#### The Algorithm\n",
+ "\n",
+ "APL uses one of three algorithm to generate pseudo-random numbers and you can check which one is being used by looking at the value of `⎕RL`.\n",
+ "`⎕RL` is a 2-item vector, where the first item is the current seed and the second item is an integer between 0 and 2:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 109,
+ "id": "wrong-overview",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "┌┬─┐\n",
+ "││1│\n",
+ "└┴─┘\n",
+ ""
+ ]
+ },
+ "execution_count": 109,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕RL"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "whole-brick",
+ "metadata": {},
+ "source": [
+ "In this case, the seed is `⍬` and the algorithm chosen is the algorithm 1.\n",
+ "If you read the Dyalog documentation, you will find some information on the three possible algorithms:\n",
+ "\n",
+ "| Id | Name | Algorithm | Valid seed values |\n",
+ "| :- | :- | :- | :- |\n",
+ "| 0 | RNG0 | Lehmer linear congruential generator | `0`, `⍬`, or an integer in the range 1 to `¯2+2*31` |\n",
+ "| 1 | RNG1 | Mersenne Twister | `0`, `⍬`, an integer in the range 1 to `¯1+2*63`, or a 625-item integer vector |\n",
+ "| 2 | RNG2 | Operating System random number generator | `⍬` |\n",
+ "\n",
+ "By default, Dyalog uses the Mersenne Twister (algorithm 1).\n",
+ "\n",
+ "For the sake of curiosity, let us see how one would implement _roll_ using the algorithm 0:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 110,
+ "id": "particular-evaluation",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "∇ z ← Roll n\n",
+ " seed ← (¯1+2*31)|seed×7*5\n",
+ " z ← ⎕IO+⌊n×seed÷2*31\n",
+ "∇"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dried-portrait",
+ "metadata": {},
+ "source": [
+ "The first instruction prepares the seed used for the next number.\n",
+ "_Residue_ is used to ensure that the result will always be smaller than `¯1+2*31`.\n",
+ "Consequently, `⎕RL÷2*31` always returns a value in the range 0-1.\n",
+ "Multiplied by the argument, it produces a result strictly smaller than `n`, and by adding `⎕IO` we get the desired result.\n",
+ "\n",
+ "You can compare this implementation of `Roll` to the primitive function _roll_ if we set the seed and the algorithm to the correct values:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 111,
+ "id": "sized-offense",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 7 2\n",
+ ""
+ ]
+ },
+ "execution_count": 111,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "seed ← 73\n",
+ "Roll¨ 3⍴10"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 112,
+ "id": "funky-introduction",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 7 2\n",
+ ""
+ ]
+ },
+ "execution_count": 112,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕RL ← 73 0\n",
+ "?3⍴10"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 113,
+ "id": "outdoor-market",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "341854744\n",
+ ""
+ ]
+ },
+ "execution_count": 113,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "seed"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 114,
+ "id": "geographic-title",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "341854744\n",
+ ""
+ ]
+ },
+ "execution_count": 114,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⊃⎕RL"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "interstate-fellow",
+ "metadata": {},
+ "source": [
+ "## Some More Maths\n",
+ "\n",
+ "### Logarithms\n",
+ "\n",
+ "The base `b` logarithm of a number `n` is calculated with the function _logarithm_ `⍟` that is typed with APL+Shift+8.\n",
+ "\n",
+ "The value `l ← b⍟n` is such that `b*l` gives back the original number `n`.\n",
+ "Here are some examples:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 115,
+ "id": "polar-antarctica",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "3\n",
+ ""
+ ]
+ },
+ "execution_count": 115,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "10⍟1000"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "unexpected-antibody",
+ "metadata": {},
+ "source": [
+ "The logarithm of 1000 in base 10 is 3 because 10 to the 3rd power gives 1000:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 116,
+ "id": "weighted-projector",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1000\n",
+ ""
+ ]
+ },
+ "execution_count": 116,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "10*3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 117,
+ "id": "careful-visibility",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "4\n",
+ ""
+ ]
+ },
+ "execution_count": 117,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "3⍟81"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "offshore-campus",
+ "metadata": {},
+ "source": [
+ "The base-3 logarithm of 81 is 4 because 3 to the power of 4 gives 81:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 118,
+ "id": "looking-encoding",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "81\n",
+ ""
+ ]
+ },
+ "execution_count": 118,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "3*4"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "charged-stupid",
+ "metadata": {},
+ "source": [
+ "The monadic form of the function _logarithm_ gives the _natural logarithm_ (or _napierian logarithm_) of a number:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 119,
+ "id": "beginning-tyler",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2.30259\n",
+ ""
+ ]
+ },
+ "execution_count": 119,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⍟10"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "unique-auckland",
+ "metadata": {},
+ "source": [
+ "The base of the natural logarithm is also the base of the monadic function _power_, so we can easily compute it:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 120,
+ "id": "adult-approach",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2.71828\n",
+ ""
+ ]
+ },
+ "execution_count": 120,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "*1"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 121,
+ "id": "heated-effect",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 121,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⍟*1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "clear-brisbane",
+ "metadata": {},
+ "source": [
+ "The monadic usage of the function _logarithm_ is the same as using `*1` as the left argument:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 122,
+ "id": "suited-burner",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2.30259\n",
+ ""
+ ]
+ },
+ "execution_count": 122,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⍟10"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 123,
+ "id": "desperate-server",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2.30259\n",
+ ""
+ ]
+ },
+ "execution_count": 123,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(*1)⍟10"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "indoor-mumbai",
+ "metadata": {},
+ "source": [
+ "This is similar to how the monadic function _reciprocal_ `÷n` is equivalent to the dyadic usage `1÷n` or the monadic function _negate_ `-n` is equivalent to the dyadic usage `0-n`.\n",
+ "\n",
+ "The relationship between the natural and the base `b` logarithms of a number is described the formulas below.\n",
+ "Given:\n",
+ "\n",
+ " - `n ← ⍟a`\n",
+ " - `l ← b⍟a`\n",
+ " - `e ← *1`, the base of the natural logarithm\n",
+ "\n",
+ "Then, we have:\n",
+ "\n",
+ " - `n ≡ l×⍟b`\n",
+ " - `l ≡ n×b⍟e`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "legal-publisher",
+ "metadata": {},
+ "source": [
+ "### Factorial & Binomial\n",
+ "\n",
+ "The product of the first `n` positive integers, or the _factorial_ of `n`, is written as $n!$ in traditional mathematics.\n",
+ "APL uses the same symbol for the function, but in APL a monadic function is always placed to the left of its argument.\n",
+ "So, factorial looks like `!n` in APL.\n",
+ "\n",
+ "As in mathematics, `!0` is equal to 1:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 124,
+ "id": "romance-richards",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 1 2 6 24 120 720 5040\n",
+ ""
+ ]
+ },
+ "execution_count": 124,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "!0 1 2 3 4 5 6 7"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "julian-fitting",
+ "metadata": {},
+ "source": [
+ "If `n` is a decimal number, `!n` gives the _gamma_ function of `n + 1`.\n",
+ "This is explained in [the Specialist's Section](#Gamma-and-Beta-Functions).\n",
+ "\n",
+ "The monadic function _factorial_ `!n` represents the number of possibilities when sorting `n` objects.\n",
+ "But if one picks only `p` objects among `n` objects, the number of possible _combinations_ is given by `(!n)÷(!p)×!n-p`.\n",
+ "This can be obtained directly using the dyadic function _binomial_ `p!n`.\n",
+ "\n",
+ "For example, how many different hands can you be dealt if you are dealt 7 cards out of a 52 card deck?\n",
+ "That's"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 125,
+ "id": "coordinate-prayer",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "133784560\n",
+ ""
+ ]
+ },
+ "execution_count": 125,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "7!52"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "vocational-characterization",
+ "metadata": {},
+ "source": [
+ "That is a surprisingly big number!\n",
+ "Over 130 million different hands.\n",
+ "\n",
+ "If `p` is greater than `n`, then `p!n` gives the result 0.\n",
+ "In hindsight, this makes a lot of sense: if you have 10 items, in how many combinations can you make with 15 of those items?\n",
+ "Obviously, zero!\n",
+ "You do not even have enough items to consider doing those combinations.\n",
+ "\n",
+ "The formula `(0,⍳n)!n` gives the coefficients of $(x + 1)^n$, which is the reason why the dyadic usage of `!` is called _binomial_.\n",
+ "One can obtain a set of coefficients with the following expression:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 126,
+ "id": "major-fields",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 1 0 0 0 0\n",
+ "1 2 1 0 0 0\n",
+ "1 3 3 1 0 0\n",
+ "1 4 6 4 1 0\n",
+ "1 5 10 10 5 1\n",
+ ""
+ ]
+ },
+ "execution_count": 126,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "x ← ⍳5\n",
+ "⍉(0,x)∘.!x"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "further-facial",
+ "metadata": {},
+ "source": [
+ "The matrix above shows, for example, that $(x + 1)^3 = 1x^3 + 3x^2 + 3x + 1$.\n",
+ "The coefficients `1 3 3 1` were taken from the third row of the matrix."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "careful-establishment",
+ "metadata": {},
+ "source": [
+ "### Trigonometry\n",
+ "\n",
+ "#### Multiples of π\n",
+ "\n",
+ "The mathematical constant pi (or π) is very useful in many mathematical and technical calculations.\n",
+ "It can be obtained via the primitive function _circle_ (the symbol `○`, typed with APL+o), which gives multiples of pi.\n",
+ "Do not confuse the function circle `o` with the jot `∘` used for the _outer product_ or the operators _beside_ and _bind_.\n",
+ "\n",
+ "$\\pi \\times n$ can be obtained with `○n`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 127,
+ "id": "neutral-layer",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "3.14159 6.28319 1.5708\n",
+ ""
+ ]
+ },
+ "execution_count": 127,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "○1 2 0.5"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 128,
+ "id": "wicked-parking",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1.0472\n",
+ ""
+ ]
+ },
+ "execution_count": 128,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "○ ÷3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "nasty-approach",
+ "metadata": {},
+ "source": [
+ "This last expression gives $\\frac\\pi3$.\n",
+ "However, be careful!\n",
+ "The symbol `○`, by itself, does not represent the value π that we divided by 3 with `○÷3`.\n",
+ "`÷3` computed the reciprocal of 3 and then the monadic function _pi times_ got applied to that."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "burning-credit",
+ "metadata": {},
+ "source": [
+ "#### Circular and Hyperbolic Trigonometry\n",
+ "\n",
+ "Using the dyadic form of the glyph _circle_, one can obtain all the possible direct and inverse functions of circular and hyperbolic trigonometry.\n",
+ "\n",
+ "the trigonometric function is designated by the left argument to `○` according to the table below.\n",
+ "You can see that the positive left arguments refer to direct trigonometric functions, while negative arguments refer to their inverse functions.\n",
+ "Values from 1 to 3 calculate circular functions and values from 5 to 7 calculate hyperbolic functions.\n",
+ "\n",
+ "##### Direct Trigonometric Functions\n",
+ "\n",
+ "| `f` | `f ○ x` |\n",
+ "| :- | :- |\n",
+ "| `0` | $\\sqrt{1 - x^2}$ |\n",
+ "| `1` | $\\sin{x}$ |\n",
+ "| `2` | $\\cos{x}$ |\n",
+ "| `3` | $\\tan{x}$ |\n",
+ "| `4` | $\\sqrt{1 + x^2}$ |\n",
+ "| `5` | $\\text{sinh }x$ |\n",
+ "| `6` | $\\text{cosh }x$ |\n",
+ "| `7` | $\\text{tanh }x$ |\n",
+ "\n",
+ "\n",
+ "#### Inverse Trigonometric Functions\n",
+ "\n",
+ "| `f` | `f ○ x` |\n",
+ "| :- | :- |\n",
+ "| `¯1` | $\\arcsin{x}$ |\n",
+ "| `¯2` | $\\arccos{x}$ |\n",
+ "| `¯3` | $\\arctan{x}$ |\n",
+ "| `¯4` | $\\sqrt{-1 + x^2}$ |\n",
+ "| `¯5` | $\\text{argsinh }x$ |\n",
+ "| `¯6` | $\\text{argcosh }x$ |\n",
+ "| `¯7` | $\\text{argtanh }x$ |\n",
+ "\n",
+ "For example:\n",
+ "\n",
+ " - `2○x` means $\\cos x$;\n",
+ " - `5○x` means $\\text{sinh }x$;\n",
+ " - `¯2○x` means $\\arccos x$;\n",
+ " - `0○val` calculates $|\\cos x|$ if $val = \\sin x$, or $|\\sin x|$ if $val = \\cos x$;\n",
+ " - `4○val` calculates $|\\text{cosh }x|$ if $val = \\text{sinh }x$; and\n",
+ " - `¯4○val` calculates $|\\text{sinh }x|$ if $val = \\text{cosh }x$.\n",
+ "\n",
+ "For direct circular trigonometry (`f∊1 2 3`) the value of `x` must be in radians.\n",
+ "For inverse circular trigonometry (`f∊-1 2 3`) the returned result is in radians."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "uniform-ontario",
+ "metadata": {},
+ "source": [
+ "#### Concrete Trigonometry Examples\n",
+ "\n",
+ "Let us compute the cosine of $0$, $\\pi/6$, and $\\pi/3$, respectively:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 129,
+ "id": "conceptual-florist",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 0.866025 0.5\n",
+ ""
+ ]
+ },
+ "execution_count": 129,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "2○ 0,○÷6 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dense-covering",
+ "metadata": {},
+ "source": [
+ "Next, we calculate three different functions for $\\pi/3$:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 130,
+ "id": "emotional-optimization",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0.866025 0.5 1.73205\n",
+ ""
+ ]
+ },
+ "execution_count": 130,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "1 2 3 ○ ○÷3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "christian-falls",
+ "metadata": {},
+ "source": [
+ "These three functions were, respectively, the sine, the cosine, and the tangent.\n",
+ "\n",
+ "Now, we confirm that $\\arctan 1 = \\pi/4$:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 131,
+ "id": "funky-emergency",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 131,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(¯3○1) = ○÷4"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "tamil-tuning",
+ "metadata": {},
+ "source": [
+ "We can also calculate the sine and cosine of $\\pi/2$:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 132,
+ "id": "pleased-secret",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 6.12323E¯17\n",
+ ""
+ ]
+ },
+ "execution_count": 132,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "1 2 ○ ○÷2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "hairy-wellington",
+ "metadata": {},
+ "source": [
+ "The very last result shows that the algorithms used to calculate the circular or hyperbolic values sometimes lead to very minor rounding approximation errors.\n",
+ "The second value is very close to zero, but it should be zero."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "alien-confidence",
+ "metadata": {},
+ "source": [
+ "#### Manipulating Complex Numbers\n",
+ "\n",
+ "Since the support for complex numbers was added to Dyalog APL, the function _circle_ was extended to provide some useful functions to manipulate complex numbers.\n",
+ "We list them in the table below, where `x` represents an arbitrary number.\n",
+ "\n",
+ "| `f` | `f ○ x` |\n",
+ "| :- | :- |\n",
+ "| `8`| `(-1+x*2)*0.5` |\n",
+ "| `9`| real part of `x` |\n",
+ "| `10` | `\\|x`, the magnitude of `x` |\n",
+ "| `11` | imaginary part of `x` |\n",
+ "| `12` | phase of `x` |\n",
+ "| `¯8` | `-8○x` |\n",
+ "| `¯9` | `x` |\n",
+ "| `¯10` | `+x`, the complex conjugate of `x` |\n",
+ "| `¯11` | `x×0J1` |\n",
+ "| `¯12` | `*x×0J1` |\n",
+ "\n",
+ "To exemplify the usage of some of these left arguments, we will verify some simple identities.\n",
+ "\n",
+ "For example, we can split a complex number up and put it back together:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 133,
+ "id": "twelve-commission",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "c ← 3J4"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 134,
+ "id": "greater-advisory",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 134,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "re ← 9○c\n",
+ "im ← 11○c\n",
+ "c ≡ re+¯11○im"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "latest-hello",
+ "metadata": {},
+ "source": [
+ "Using the function _magnitude_ directly should be the same as using `10○`, which should be the same as computing the magnitude of a complex number by hand:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 135,
+ "id": "touched-thomas",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "5\n",
+ ""
+ ]
+ },
+ "execution_count": 135,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← mag ← |c"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 136,
+ "id": "unauthorized-removal",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 136,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "mag≡10○c"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 137,
+ "id": "sublime-blond",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 137,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "mag≡0.5*⍨+/re im*2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "friendly-toolbox",
+ "metadata": {},
+ "source": [
+ "The phase of a complex number is given by the arctangent of the ratio of its imaginary and real parts:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 138,
+ "id": "grave-evidence",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0.927295\n",
+ ""
+ ]
+ },
+ "execution_count": 138,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← phase ← 12○c"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 139,
+ "id": "imposed-beatles",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 139,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "phase≡¯3○im÷re"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "mighty-butler",
+ "metadata": {},
+ "source": [
+ "Finally, we can reconstruct a complex number from its phase and magnitude (which are used when representing complex numbers in polar form):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 140,
+ "id": "spread-ending",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 140,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "c ≡ magׯ12○phase"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "smaller-ultimate",
+ "metadata": {},
+ "source": [
+ "### GCD and LCM\n",
+ "\n",
+ "#### Greatest Common Divisor (GCD)\n",
+ "\n",
+ "When applied to binary values, the symbol `∨` represents the Boolean function _or_.\n",
+ "The same symbol can be applied to numbers other than 0 and 1.\n",
+ "Then, it calculates their _greatest common divisor_, or GCD.\n",
+ "`∨` is always a dyadic scalar function: applied to arrays of the same shape, it gives a result of the same shape; applied between a scalar and any array, it gives a result of the same shape as the array, and the scalar value is reused as needed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 141,
+ "id": "ancient-testing",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "165 165 1\n",
+ ""
+ ]
+ },
+ "execution_count": 141,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "15180 ∨ 285285 ¯285285 47"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "popular-naples",
+ "metadata": {},
+ "source": [
+ "The result of GCD is always positive.\n",
+ "Two integers always have, at least, the number 1 as a common divisor.\n",
+ "When the result of GCD between two integers is 1, that means there was no other divisor in common between the two integers and we say that those two integers are _coprime_.\n",
+ "For example, 15180 and 47 are coprime.\n",
+ "\n",
+ "As always, if one of the items of the argument to `∨` is 0, the corresponding item from the other argument is returned (41, in the case below):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 142,
+ "id": "hawaiian-quebec",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "74 41 1\n",
+ ""
+ ]
+ },
+ "execution_count": 142,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "5180 0 28 ∨ 6142 41 19"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "secret-commerce",
+ "metadata": {},
+ "source": [
+ "The function _gcd_ also works with non-integer arguments:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 143,
+ "id": "applicable-texas",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0.2 0.3 1.4\n",
+ ""
+ ]
+ },
+ "execution_count": 143,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "5178 417 28 ∨ 7.4 0.9 1.4"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "worldwide-lawsuit",
+ "metadata": {},
+ "source": [
+ "#### Lowest Common Multiple (LCM)\n",
+ "\n",
+ "When applied to binary values, the symbol `∧` represents the Boolean function _and_.\n",
+ "The same symbol can be applied to numbers other than 0 and 1.\n",
+ "Then, it calculates their lowest common multiple, or LCM.\n",
+ "`∧` is also a dyadic scalar function."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 144,
+ "id": "smoking-leader",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "12008 8 847071 ¯4152 0\n",
+ ""
+ ]
+ },
+ "execution_count": 144,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "152 1 6183 ¯519 0 ∧ 316 8 411 24 16"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "serious-miracle",
+ "metadata": {},
+ "source": [
+ "The GCD and the LCM satisfy a relationship: if `a` and `b` are two numbers, then we have that `(a×b) ≡ (a∨b)×(a∧b)`.\n",
+ "This relationship can be rewritten as the elegant train `(×≡∨×∧)`, which always returns 1:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 145,
+ "id": "cultural-vacuum",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 145,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "428 (×≡∨×∧) 561"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 146,
+ "id": "governing-trout",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 146,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "¯28 (×≡∨×∧) 561"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 147,
+ "id": "stone-camel",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 147,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "428 (×≡∨×∧) ¯51"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 148,
+ "id": "sustained-encyclopedia",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 148,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "¯42 (×≡∨×∧) ¯61"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "imperial-exhibition",
+ "metadata": {},
+ "source": [
+ "### Set Union and Intersection\n",
+ "\n",
+ "Mathematical set theory describes the following two functions between two sets `a` and `b`:\n",
+ "\n",
+ " - _intersection_ `a ∩ b`, gives the items in both sets; and\n",
+ " - _union_ `a ∪ b`, gives the items that are in either set.\n",
+ "\n",
+ "The same functions are found in Dyalog, using the same symbols.\n",
+ "To type the symbol for set intersection `∩`, use APL+c.\n",
+ "To type the symbol for set union, use APL+v.\n",
+ "They work on scalar and vector arguments:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 149,
+ "id": "capital-welcome",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "┌──┬───────┐\n",
+ "│53│dollars│\n",
+ "└──┴───────┘\n",
+ ""
+ ]
+ },
+ "execution_count": 149,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "'Hey' 'give' 'me' 53 'dollars' ∩ 53 'euros' 'not' 'dollars'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 150,
+ "id": "absolute-madonna",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "┌───┬────┬──┬──┬───────┬─────┬───┐\n",
+ "│Hey│give│me│53│dollars│euros│not│\n",
+ "└───┴────┴──┴──┴───────┴─────┴───┘\n",
+ ""
+ ]
+ },
+ "execution_count": 150,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "'Hey' 'give' 'me' 53 'dollars' ∪ 53 'euros' 'not' 'dollars'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "swedish-restriction",
+ "metadata": {},
+ "source": [
+ "In mathematics, we use these two functions with _sets_, which are not the same thing as APL vectors.\n",
+ "APL vectors are ordered and may contain duplicates, which makes a couple of conventions necessary."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "interpreted-start",
+ "metadata": {},
+ "source": [
+ "#### Intersection\n",
+ "\n",
+ "The result of the function _intersection_ is in the order that the items appear in the left argument, including duplicates."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 151,
+ "id": "unlimited-commons",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2 3 2 3\n",
+ ""
+ ]
+ },
+ "execution_count": 151,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "1 1 2 3 2 3 ∩ 2 3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 152,
+ "id": "modified-combination",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "euioouoeeayo\n",
+ ""
+ ]
+ },
+ "execution_count": 152,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "text ← 'The quick brown fox jumps over the lazy dog'\n",
+ "vowels ← 'aeiouy'\n",
+ "text ∩ vowels"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "other-interaction",
+ "metadata": {},
+ "source": [
+ "In fact, the result of the _intersection_ is equal to the left argument, but with all items that are not found in the right argument removed.\n",
+ "aWe can check this by using the function _without_ twice:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 153,
+ "id": "solved-template",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 153,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(text∩vowels) ≡ text~text~vowels"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "final-browse",
+ "metadata": {},
+ "source": [
+ "#### Union\n",
+ "\n",
+ "The result of the function _union_ is always the left argument, followed by all items of the right argument that are not already found in the left argument – including duplicates:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 154,
+ "id": "statistical-delaware",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 1 2 3 2 3 4 4 4 6 6 6\n",
+ ""
+ ]
+ },
+ "execution_count": 154,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "1 1 2 3 2 3 ∪ 2 2 2 4 4 4 6 6 6"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "prescribed-maryland",
+ "metadata": {},
+ "source": [
+ "Again, we can verify this property by making use of the function _without_:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 155,
+ "id": "underlying-competition",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1\n",
+ ""
+ ]
+ },
+ "execution_count": 155,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "left ← 1 1 2 3 2 3\n",
+ "right ← 2 2 2 4 4 4 6 6 6\n",
+ "(left∪right) ≡ left,right~left"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "technical-class",
+ "metadata": {},
+ "source": [
+ "#### Unique\n",
+ "\n",
+ "The function _union_ can also be used monadically, in which case it is the function _unique_.\n",
+ "The function _unique_ works on arrays of arbitrary dimension and returns an array of the same rank, with all the unique major cells of the initial argument, in order of appearance:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 156,
+ "id": "sharp-russell",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 2 3\n",
+ ""
+ ]
+ },
+ "execution_count": 156,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "∪1 1 2 3 2 3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 157,
+ "id": "equipped-wells",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Jan\n",
+ "Feb\n",
+ "Jan\n",
+ "Apr\n",
+ "Dec\n",
+ "Apr\n",
+ "Mar\n",
+ ""
+ ]
+ },
+ "execution_count": 157,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← months ← ↑'Jan' 'Feb' 'Jan' 'Apr' 'Dec' 'Apr' 'Mar'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 158,
+ "id": "appointed-camping",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Jan\n",
+ "Feb\n",
+ "Apr\n",
+ "Dec\n",
+ "Mar\n",
+ ""
+ ]
+ },
+ "execution_count": 158,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "∪months"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "lucky-switch",
+ "metadata": {},
+ "source": [
+ "## Domino\n",
+ "\n",
+ "### Some Definitions\n",
+ "\n",
+ "#### Identity Matrix\n",
+ "\n",
+ "Any number multiplied by 1 stays unchanged.\n",
+ "Similarly, a matrix multiplied by a special square Boolean matrix remains unchanged.\n",
+ "We call _identity matrix_ to that special matrix.\n",
+ "\n",
+ "The _identity matrix_ is a square matrix that is all zeroes except in the main diagonal, where it has ones.\n",
+ "\n",
+ "For example, if we have a 3 by 3 matrix"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 159,
+ "id": "bearing-davis",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "12 50 7\n",
+ "44 3 25\n",
+ "30 71 80\n",
+ ""
+ ]
+ },
+ "execution_count": 159,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← m ← 3 3⍴12 50 7 44 3 25 30 71 80"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "productive-boston",
+ "metadata": {},
+ "source": [
+ "Then, the identity matrix for `m` must be a 3 by 3 matrix of zeroes with ones in the main diagonal.\n",
+ "A simple way of creating the identity matrix of size `n` is by using the expression `n n⍴n+1↑1`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 160,
+ "id": "comparative-booth",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 0 0\n",
+ "0 1 0\n",
+ "0 0 1\n",
+ ""
+ ]
+ },
+ "execution_count": 160,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← i ← 3 3⍴4↑1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "judicial-sally",
+ "metadata": {},
+ "source": [
+ "Now, if we multiply `m` and `i` together, we get `m` back:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 161,
+ "id": "monetary-principal",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "12 50 7\n",
+ "44 3 25\n",
+ "30 71 80\n",
+ ""
+ ]
+ },
+ "execution_count": 161,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m+.×i"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 162,
+ "id": "deluxe-terrain",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "12 50 7\n",
+ "44 3 25\n",
+ "30 71 80\n",
+ ""
+ ]
+ },
+ "execution_count": 162,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "i+.×m"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "after-portfolio",
+ "metadata": {},
+ "source": [
+ "#### Inverse Matrices\n",
+ "\n",
+ "If we multiply 4 by 0.25, or 0.25 by 4, we obtain 1, which is the identity item for multiplication.\n",
+ "Alternatively, we can say that 0.25 is the reciprocal, or inverse, of 4, and vice-versa.\n",
+ "\n",
+ "Given that `i` is the identity item for matrix multiplication, if we can find two matrices `m` and `m_` whose product is `i`, we can say that those two matrices are the inverse of each other.\n",
+ "\n",
+ "For example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 163,
+ "id": "handed-zambia",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 0 2\n",
+ "0 2 1\n",
+ "0.5 3 1.5\n",
+ ""
+ ]
+ },
+ "execution_count": 163,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← m ← 3 3⍴1 0 2 0 2 1 .5 3 1.5"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 164,
+ "id": "integral-prisoner",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " 0 ¯3 2\n",
+ "¯0.25 ¯0.25 0.5\n",
+ " 0.5 1.5 ¯1\n",
+ ""
+ ]
+ },
+ "execution_count": 164,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← m_ ← 3 3⍴0 ¯3 2 ¯.25 ¯.25 .5 .5 1.5 ¯1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dirty-going",
+ "metadata": {},
+ "source": [
+ "If we multiply these two matrices together, in either order, we get the identity matrix:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 165,
+ "id": "dressed-greenhouse",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 0 0\n",
+ "0 1 0\n",
+ "0 0 1\n",
+ ""
+ ]
+ },
+ "execution_count": 165,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m+.×m_"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 166,
+ "id": "nearby-tomorrow",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 0 0\n",
+ "0 1 0\n",
+ "0 0 1\n",
+ ""
+ ]
+ },
+ "execution_count": 166,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m_+.×m"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "threatened-talent",
+ "metadata": {},
+ "source": [
+ "Note that, in general, `a+.×b` and `b+.×a` give different results!\n",
+ "\n",
+ "Here is a second example with 2 by 2 matrices:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 167,
+ "id": "quick-clause",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2 1\n",
+ "4 1\n",
+ ""
+ ]
+ },
+ "execution_count": 167,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← m ← 2 2⍴2 1 4 1"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 168,
+ "id": "clinical-denver",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "¯0.5 0.5\n",
+ " 2 ¯1\n",
+ ""
+ ]
+ },
+ "execution_count": 168,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← m_ ← 2 2⍴¯.5 .5 2 ¯1"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 169,
+ "id": "judicial-promise",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 0\n",
+ "0 1\n",
+ ""
+ ]
+ },
+ "execution_count": 169,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m+.×m_"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "recent-disco",
+ "metadata": {},
+ "source": [
+ "Now, the result is the identity matrix of size 2, because the matrices `m` and `m_` are 2 by 2 (and not 3 by 3).\n",
+ "\n",
+ "For the moment, we will only concern ourselves with inverses for square matrices.\n",
+ "We shall in [the Specialist's Section](#Domino-and-Rectangular-Matrices) that it is also possible to define inverses for non-square matrices."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "developed-script",
+ "metadata": {},
+ "source": [
+ "### Matrix Inverse\n",
+ "\n",
+ "#### Monadic Domino\n",
+ "\n",
+ "APL provides a _matrix inverse_ primitive function, represented by the symbol `⌹` and typed with APL+Shift+=, which is the same key as `÷`.\n",
+ "Because of its appearance, this symbol is named _domino_.\n",
+ "\n",
+ "The monadic usage of _domino_ returns the inverse of a matrix:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 170,
+ "id": "norman-masters",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "¯0.5 0.5\n",
+ " 2 ¯1\n",
+ ""
+ ]
+ },
+ "execution_count": 170,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⌹m"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "intense-situation",
+ "metadata": {},
+ "source": [
+ "Calculating the inverse of a matrix is a complex operation, and the result might be only an approximation.\n",
+ "For example, the inverse of the matrix `m` has the value `¯1` in the bottom right corner.\n",
+ "However, it would not be surprising if the function _matrix inverse_ had returned this result, instead:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 171,
+ "id": "substantial-consumer",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "¯0.5 0.5\n",
+ " 2 ¯0.999999\n",
+ ""
+ ]
+ },
+ "execution_count": 171,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "2 2⍴¯.5 .5 2 ¯.999999"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "difficult-thomas",
+ "metadata": {},
+ "source": [
+ "#### Singular Matrices\n",
+ "\n",
+ "In normal arithmetic, zero has no inverse and `÷0` in APL causes a `DOMAIN ERROR`.\n",
+ "\n",
+ "In the same way, some matrices cannot be inverted.\n",
+ "Those matrices are said to be _singular_:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 172,
+ "id": "settled-postage",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "1 3 5\n",
+ "3 4 15\n",
+ "2 7 10\n",
+ "DOMAIN ERROR\n",
+ " ⌹⎕←3 3⍴1 3 5 3 4 15 2 7 10\n",
+ " ∧\n"
+ ]
+ }
+ ],
+ "source": [
+ "⌹ ⎕← 3 3⍴1 3 5 3 4 15 2 7 10"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ambient-binary",
+ "metadata": {},
+ "source": [
+ "#### Solving a Set of Linear Equations\n",
+ "\n",
+ "Here is a set of three linear equations with three unknowns, written in traditional mathematical notation:\n",
+ "\n",
+ "$$\n",
+ "\\begin{alignat}{4}\n",
+ "-8 &= 3&&x + 2&&y -\\ &&z \\\\\n",
+ "19 &= &&x -\\ &&y + 3&&z \\\\\n",
+ "0 &= 5&&x + 2&&y\n",
+ "\\end{alignat}\n",
+ "$$\n",
+ "\n",
+ "This set of equations can be represented using a vector for the constants and a matrix for the coefficients of the three unknowns, as shown below:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 173,
+ "id": "popular-wages",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "cons ← ¯8 19 0"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 174,
+ "id": "therapeutic-specific",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "3 2 ¯1\n",
+ "1 ¯1 3\n",
+ "5 2 0\n",
+ ""
+ ]
+ },
+ "execution_count": 174,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← coefs ← 3 3⍴3 2 ¯1 1 ¯1 3 5 2 0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "natural-december",
+ "metadata": {},
+ "source": [
+ "To solve the set of equations above, we must find a 3-element vector `xyz` such that `cons ≡ coefs +.× xyz`.\n",
+ "\n",
+ "We can find such a vector provided that the matrix `coefs` has an inverse, i.e., that it is not singular.\n",
+ "Let us multiply both sides of the equality `cons ≡ coefs +.× xyz` by the inverse of `coefs`:\n",
+ "\n",
+ "```APL\n",
+ " cons ≡ coefs +.× xyz ←→\n",
+ "←→ (⌹coefs) +.× cons ≡ (⌹coefs) +.× coefs +.× xyz\n",
+ "```\n",
+ "\n",
+ "Now, because we know that `id ← (⌹coefs) +.× coefs` is the identity matrix, we can simplify the expression:\n",
+ "\n",
+ "```APL\n",
+ " (⌹coefs) +.× cons ≡ (⌹coefs) +.× coefs +.× xyz ←→\n",
+ "←→ (⌹coefs) +.× cons ≡ id +.× xyz ←→\n",
+ "←→ (⌹coefs) +.× cons ≡ xyz\n",
+ "```\n",
+ "\n",
+ "Eureka!\n",
+ "We found a way of calculating the values that we had to find:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 175,
+ "id": "fifteen-african",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2 ¯5 4\n",
+ ""
+ ]
+ },
+ "execution_count": 175,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← xyz ← (⌹coefs) +.× cons"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "liked-magic",
+ "metadata": {},
+ "source": [
+ "In general, the solution to a set of linear equations is given by `solution ← (⌹coefficients) +.× constants`.\n",
+ "\n",
+ "Note that, in the formula above, we multiply `constants` by the inverse (or reciprocal) of a matrix.\n",
+ "When dealing with numbers, multiplying by the reciprocal of a number is usually known as division, which motivates the name of the dyadic usage of the _domino_ `⌹`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "intellectual-bulgaria",
+ "metadata": {},
+ "source": [
+ "### Matrix Division\n",
+ "\n",
+ "The dyadic form of _domino_ is called _matrix divide_, so it can do exactly what we have just done: it can easily sets of linear equations like the one shown above.\n",
+ "We solve that set of linear equations again, this time using _matrix divide_:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 176,
+ "id": "lesbian-homework",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2 ¯5 4\n",
+ ""
+ ]
+ },
+ "execution_count": 176,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cons⌹coefs"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "saved-smart",
+ "metadata": {},
+ "source": [
+ "Naturally, this method works only if the coefficient matrix has an inverse.\n",
+ "In other words, the set of equations must have a single solution.\n",
+ "If three is no solution, a `DOMAIN ERROR` will be reported.\n",
+ "\n",
+ "We can summarise this as follows: given a system of `n` linear equations with `n` unknowns, where the matrix of coefficients is called `coefs` and the vector of constants is called `cons`, the solution `sol` of the system is given by `sol ← cons⌹coefs`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "determined-weapon",
+ "metadata": {},
+ "source": [
+ "### Two or Three Steps in Geometry\n",
+ "\n",
+ "#### A Complex Solution to a Simple Problem\n",
+ "\n",
+ "To begin with, we invite you to study a complicated method to solve a simple problem.\n",
+ "Our intention is then to generalise this method to develop a solution for an everyday problem in statistical studies.\n",
+ "\n",
+ "The goal is to find the coefficients of a straight line passing through two points `P` and `Q`, of which the coordinates are given below:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 177,
+ "id": "fewer-editor",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "xs ← 2 4 ⍝ x coordinates of P and Q\n",
+ "ys ← 2 3 ⍝ y coordinates of P and Q"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "demonstrated-forum",
+ "metadata": {},
+ "source": [
+ "The points `P` and `Q` are shown in the figure below.\n",
+ "\n",
+ "\n",
+ "\n",
+ "The general equation describing a straight line is $y = mx + b$.\n",
+ "With our two points given, the following is obtained:\n",
+ "\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "2 &= 2m + b\n",
+ "3 &= 4m + b\n",
+ "\\end{align}\n",
+ "$$\n",
+ "\n",
+ "This is a set of two linear equations in which the unknowns are $m$ and $b$.\n",
+ "Let us solve this set of equations by the method demonstrated in the previous section:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 178,
+ "id": "sound-madness",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "cons ← ys"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 179,
+ "id": "intensive-rainbow",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2 1\n",
+ "4 1\n",
+ ""
+ ]
+ },
+ "execution_count": 179,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← coefs ← xs,[1.5]1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "assisted-wheat",
+ "metadata": {},
+ "source": [
+ "Now, $m$ and $b$ can be calculated using the method we saw above:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 180,
+ "id": "brave-contemporary",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0.5 1\n",
+ ""
+ ]
+ },
+ "execution_count": 180,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← c ← cons⌹coefs"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "empty-packet",
+ "metadata": {},
+ "source": [
+ "We can also compute the coefficients of the line directly from the vectors `xs` and `ys`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 181,
+ "id": "combined-netherlands",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0.5 1\n",
+ ""
+ ]
+ },
+ "execution_count": 181,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← c ← ys⌹xs,[1.5]1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dynamic-profile",
+ "metadata": {},
+ "source": [
+ "In traditional notation, this gives us the equation $y = 0.5x + 1$ for the line.\n",
+ "Did you find this tedious?\n",
+ "Maybe, but now let us discover its scope."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "alleged-niagara",
+ "metadata": {},
+ "source": [
+ "#### Calculating Additional Y Coordinates\n",
+ "\n",
+ "Having found the coefficients of the line shown in the previous section, let us try to calculate the y coordinates of several points for which the x coordinates are known.\n",
+ "\n",
+ "The coefficients of our line were obtained by this calculation:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 182,
+ "id": "executive-moderator",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "c ← ys⌹coefs"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "innocent-richards",
+ "metadata": {},
+ "source": [
+ "We saw earlier that this is strictly equivalent to `c ← (⌹coefs) +.× ys`.\n",
+ "Let us multiply both terms of this expression by `coefs`:\n",
+ "\n",
+ "```APL\n",
+ " c ≡ (⌹coefs) +.× ys ←→\n",
+ "←→ coefs +.× c ≡ coefs +.× (⌹coefs) +.× ys ←→\n",
+ "←→ coefs +.× c ≡ ys\n",
+ "```\n",
+ "\n",
+ "This shows that the y coordinates `ys` of some points placed on a line defined by the coefficients `c` can be calculated from their x coordinates `xs` by the formula `coefs +.× c` or, in a more explicit form: `ys ← c +.×⍨ xs,[1.5]1`.\n",
+ "\n",
+ "Let us apply this technique to a set of points:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 183,
+ "id": "objective-spoke",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "1 0 2.5 4\n",
+ ""
+ ]
+ },
+ "execution_count": 183,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "c +.×⍨ 0 ¯2 3 6,[1.5]1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "transparent-species",
+ "metadata": {},
+ "source": [
+ "You can check these values in the graph shown above."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "superior-waterproof",
+ "metadata": {},
+ "source": [
+ "### Least Squares Fitting\n",
+ "\n",
+ "#### Linear Regression\n",
+ "\n",
+ "Our line was defined by two points.\n",
+ "What happens if we no longer have 2 points, but many?\n",
+ "Of course, there is a high probability that these points are not aligned.\n",
+ "\n",
+ "As an example, suppose that we have twelve employees and we know their ages (in years) and salaries (in peanuts):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 184,
+ "id": "martial-biology",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "age ← 20 21 28 31 33 34 36 37 40 44 45 51\n",
+ "salaries ← 3071 2997 2442 3589 3774 3071 3108 5291 5180 7548 5772 5883"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 185,
+ "id": "communist-contribution",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " 20 21 28 31 33 34 36 37 40 44 45 51\n",
+ "3071 2997 2442 3589 3774 3071 3108 5291 5180 7548 5772 5883\n",
+ ""
+ ]
+ },
+ "execution_count": 185,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "age,[.5]salaries"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "acknowledged-letters",
+ "metadata": {},
+ "source": [
+ "We shall place the ages on the x axis and salaries on the y axis (see the figure below).\n",
+ "\n",
+ "This time, the matrix"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 186,
+ "id": "talented-interest",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "20 1\n",
+ "21 1\n",
+ "28 1\n",
+ "31 1\n",
+ "33 1\n",
+ "34 1\n",
+ "36 1\n",
+ "37 1\n",
+ "40 1\n",
+ "44 1\n",
+ "45 1\n",
+ "51 1\n",
+ ""
+ ]
+ },
+ "execution_count": 186,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "age,[1.5]1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "obvious-intro",
+ "metadata": {},
+ "source": [
+ "will have more rows (12) than columns (2), and the set of equations has no solution, which confirms that no single straight line can join all those points.\n",
+ "\n",
+ "In this type of situation, it may be desirable to define a straight line which best represents the spread of points.\n",
+ "Generally, a straight line is sought such that the sum of the squares of the deviations of the y coordinates between the given points and the line is minimised.\n",
+ "This particular line is called the _least squares line_ and the process of finding it is called _linear regression_.\n",
+ "\n",
+ "To find this line, we shall use `⌹` once more.\n",
+ "The expression used to calculate the coefficients of a line passing through two points can be applied to a rectangular matrix, and the function _matrix divide_ gives the coefficients of the _least squares line_ passing through the set of points.\n",
+ "Is it not magic?\n",
+ "\n",
+ "For the given points, here is the calculation:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 187,
+ "id": "blond-draft",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "134.946 ¯412.6\n",
+ ""
+ ]
+ },
+ "execution_count": 187,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← c ← salaries⌹age,[1.5]1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "thousand-bachelor",
+ "metadata": {},
+ "source": [
+ "This gives the line $y = 134.946x - 412.6$.\n",
+ "\n",
+ "We can calculate the approximate salaries located on the line, at the same x coordinates as the given points and compare with the actual salaries:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 188,
+ "id": "presidential-skiing",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " 3071 2997 2442 3589 3774 3071 3108 5291 5180 7548 5772 5883\n",
+ " 2286 2421 3366 3771 4041 4176 4445 4580 4985 5525 5660 6470\n",
+ ""
+ ]
+ },
+ "execution_count": 188,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "0⍕ salaries ,[.5] c+.×⍨age,[1.5]1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "about-illinois",
+ "metadata": {},
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "retained-surrey",
+ "metadata": {},
+ "source": [
+ "#### Extension\n",
+ "\n",
+ "In the previous example, we measured the effect of a single factor (age) on a single observation (salary) using a linear model.\n",
+ "\n",
+ "What if we want to use the same model to explore the relationship between several factors and a single observation?\n",
+ "The following example is inspired by a controller in IBM France who tried to see if the heads of his commercial agencies had \"reasonable\" expense claim forms.\n",
+ "\n",
+ "The amounts were stored in a vector `amounts`.\n",
+ "He tried to measure the effect of 4 factors on these expenses amounts:\n",
+ "\n",
+ " - the number of salesmen in each agency, `nbmen`;\n",
+ " - the size of the area covered by each agency, `radius`;\n",
+ " - the number of customers in each agency, `nbcus`; and\n",
+ " - the annual income produced by each agency, `income`.\n",
+ "\n",
+ "In other words, he tried to find the vector `tc` of 5 theoretical coefficients, $c_1, \\cdots, c_5$, which most closely satisfy this equation:\n",
+ "\n",
+ "$$\n",
+ "{\\rm amounts} = {\\rm nbmen}\\times c_1 + {\\rm radius}\\times c_2 + {\\rm nbcus}\\times c_3 + {\\rm income}\\times c_4 + c_5\n",
+ "$$\n",
+ "\n",
+ "Or, in APL, we want to find the 5-element vector `tc` that most closely satisfies this equality:\n",
+ "\n",
+ "```APL\n",
+ "amounts ≡ tc +.× nbmen radius nbcus income 1\n",
+ "```\n",
+ "\n",
+ "Here is the data:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 189,
+ "id": "resident-thomas",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "amounts ← 40420 23000 28110 32460 25800 33610 61520 44970\n",
+ "nbmen ← 25 20 24 28 14 8 31 17\n",
+ "radius ← 90 50 12 12 30 30 120 75\n",
+ "nbcus ← 430 87 72 210 144 91 207 161\n",
+ "income ← 2400 9000 9500 4100 6500 3300 9800 4900"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "heavy-tension",
+ "metadata": {},
+ "source": [
+ "Let us apply exactly what we did on ages and salaries, and calculate the following variables:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 190,
+ "id": "responsible-eleven",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "25 90 430 2400 1\n",
+ "20 50 87 9000 1\n",
+ "24 12 72 9500 1\n",
+ "28 12 210 4100 1\n",
+ "14 30 144 6500 1\n",
+ " 8 30 91 3300 1\n",
+ "31 120 207 9800 1\n",
+ "17 75 161 4900 1\n",
+ ""
+ ]
+ },
+ "execution_count": 190,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← coefs ← nbmen,radius,nbcus,income,[1.5]1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "accessible-zimbabwe",
+ "metadata": {},
+ "source": [
+ "Next, we compute the 5 coefficients of the least squares line:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 191,
+ "id": "cognitive-cabin",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " 1154.23 362.14 ¯99.39 ¯3.33 31193.65\n",
+ ""
+ ]
+ },
+ "execution_count": 191,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← 2⍕tc ← amounts⌹coefs"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dirty-dragon",
+ "metadata": {},
+ "source": [
+ "Now, we compute the y coordinates of the points on the least squares line:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 192,
+ "id": "opening-celtic",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ys ← coefs+.×tc"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "solved-norfolk",
+ "metadata": {},
+ "source": [
+ "Finally, we compute the differences between the expenses predicted by the expenses claims and the actual claims, and we compute the percentage deviation:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 193,
+ "id": "robust-memphis",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "diff ← amounts-ys\n",
+ "pcent ← 100×diff÷ys"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "spiritual-compound",
+ "metadata": {},
+ "source": [
+ "Finally, we display all the data, with a \"-\" next to the agencies for which the expenses are significantly above the predicted value (possibly because the agencies have bad managers) and a \"+\" sign next to the agencies for which the expenses are significantly below the predicted value (possibly because the agencies have good managers):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 194,
+ "id": "ruled-migration",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " +- - \n",
+ ""
+ ]
+ },
+ "execution_count": 194,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← flag ← '+ -'[1+¯10 10⍸pcent]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 195,
+ "id": "brutal-question",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " Real Normal Diff % \n",
+ ""
+ ]
+ },
+ "execution_count": 195,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "⎕← title ← 29↑' Real Normal Diff %'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 196,
+ "id": "collaborative-stocks",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " Real Normal Diff % \n",
+ " 40420 41914 ¯1494 ¯4 \n",
+ " 23000 33773 ¯10773 ¯32+\n",
+ " 28110 24455 3655 15-\n",
+ " 32460 33335 ¯875 ¯3 \n",
+ " 25800 22264 3536 16-\n",
+ " 33610 31260 2350 8 \n",
+ " 61520 57229 4291 7 \n",
+ " 44970 45660 ¯690 ¯2 \n",
+ ""
+ ]
+ },
+ "execution_count": 196,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "title⍪(7 0⍕amounts,ys,diff,[1.5]pcent),flag"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "raising-japan",
+ "metadata": {},
+ "source": [
+ "#### Non-linear Adjustment\n",
+ "\n",
+ "In this last example we used independent factors and tried to combine them with a linear expression.\n",
+ "We could as well have used vectors linked one to the other by any mathematical expression, like `results ← (c1×var)+(c2×var*2)+(c3×⍟var)+c4` (if this makes sense).\n",
+ "A typical case is trying to fit a set of points with a polynomial curve.\n",
+ "Here are 8 points:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 197,
+ "id": "tough-expression",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "xs ← ¯1 ¯1 0.5 1.5 2 2 3 4\n",
+ "ys ← ¯3 ¯1 0 ¯1 ¯1 1 3 5"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "historical-cover",
+ "metadata": {},
+ "source": [
+ "A linear regression would give a line with the following coefficients:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 198,
+ "id": "outdoor-filling",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " 1.22 ¯1.31\n",
+ ""
+ ]
+ },
+ "execution_count": 198,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "2⍕ ys⌹xs,[1.5]1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "entitled-chassis",
+ "metadata": {},
+ "source": [
+ "The right argument was obtained by laminating 1 to `xs`.\n",
+ "However, we could just as well have obtained it with the following _outer product_:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 199,
+ "id": "streaming-commander",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "¯1 1\n",
+ "¯1 1\n",
+ " 0.5 1\n",
+ " 1.5 1\n",
+ " 2 1\n",
+ " 2 1\n",
+ " 3 1\n",
+ " 4 1\n",
+ ""
+ ]
+ },
+ "execution_count": 199,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "xs∘.*1 0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "outstanding-bouquet",
+ "metadata": {},
+ "source": [
+ "Now, instead of taking only powers 1 and 0 of `xs`, we could extend the scope of the powers up to the third degree, for example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 200,
+ "id": "frequent-interim",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "¯1 1 ¯1 1\n",
+ "¯1 1 ¯1 1\n",
+ " 0.125 0.25 0.5 1\n",
+ " 3.375 2.25 1.5 1\n",
+ " 8 4 2 1\n",
+ " 8 4 2 1\n",
+ "27 9 3 1\n",
+ "64 16 4 1\n",
+ ""
+ ]
+ },
+ "execution_count": 200,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "xs∘.*3 2 1 0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "defined-painting",
+ "metadata": {},
+ "source": [
+ "We would then obtain not the coefficients of a straight line, but those of a third degree polynomial curve, shown in the figure below.\n",
+ "\n",
+ "\n",
+ "\n",
+ "To get the coefficients, we do the usual computation:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 201,
+ "id": "prepared-channels",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " 0.10 ¯0.16 0.58 ¯1.07\n",
+ ""
+ ]
+ },
+ "execution_count": 201,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "2⍕ c←ys⌹xs∘.*3 2 1 0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "severe-alert",
+ "metadata": {},
+ "source": [
+ "In other words, this set of points can be approximated by the line $0.1x^3 - 0.16x^2 + 0.58x - 1.07$."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ordered-anniversary",
+ "metadata": {},
+ "source": [
+ "## Exercises\n",
+ "\n",
+ "\n",
+ "***Exercise 1***:\n",
+ "\n",
+ "Can you predict (and explain) the results of the two expressions below?\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 202,
+ "id": "large-knock",
+ "metadata": {
+ "tags": [
+ "skip-execution"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "19\n",
+ ""
+ ]
+ },
+ "execution_count": 202,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "0 ⊥ 12 34 60 77 19"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 203,
+ "id": "pointed-stock",
+ "metadata": {
+ "tags": [
+ "skip-execution"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "202\n",
+ ""
+ ]
+ },
+ "execution_count": 203,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "1 ⊥ 12 34 60 77 19"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "binary-invitation",
+ "metadata": {},
+ "source": [
+ "\n",
+ "***Exercise 2***:\n",
+ "\n",
+ "In a binary number system, a group of 4 bits represents values from 0 to 15.\n",
+ "Those 4 bits represent the 16 states of a base 16 number system known as the hexadecimal number system.\n",
+ "This system is very often used in computer science, with the numbers 0-9 represented by the characters \"0\"-\"9\", and the numbers 10-15 represented by the characters \"A\" to \"F\".\n",
+ "Write a function to convert hexadecimal values to decimal, and the reverse.\n",
+ "Assume hexadecimal values are represented as 4 character vectors:\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 204,
+ "id": "internal-cruise",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "D2H ← {'0123456789ABCDEF'[1+16 16 16 16⊤⍵]}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 205,
+ "id": "prime-delicious",
+ "metadata": {
+ "tags": [
+ "skip-execution"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "┌──┬────┐\n",
+ "│0 │0000│\n",
+ "├──┼────┤\n",
+ "│1 │0001│\n",
+ "├──┼────┤\n",
+ "│2 │0002│\n",
+ "├──┼────┤\n",
+ "│3 │0003│\n",
+ "├──┼────┤\n",
+ "│4 │0004│\n",
+ "├──┼────┤\n",
+ "│5 │0005│\n",
+ "├──┼────┤\n",
+ "│6 │0006│\n",
+ "├──┼────┤\n",
+ "│7 │0007│\n",
+ "├──┼────┤\n",
+ "│8 │0008│\n",
+ "├──┼────┤\n",
+ "│9 │0009│\n",
+ "├──┼────┤\n",
+ "│10│000A│\n",
+ "├──┼────┤\n",
+ "│11│000B│\n",
+ "├──┼────┤\n",
+ "│12│000C│\n",
+ "├──┼────┤\n",
+ "│13│000D│\n",
+ "├──┼────┤\n",
+ "│14│000E│\n",
+ "├──┼────┤\n",
+ "│15│000F│\n",
+ "└──┴────┘\n",
+ ""
+ ]
+ },
+ "execution_count": 205,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(⍪,D2H¨) ¯1+⍳16"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 206,
+ "id": "aerial-wings",
+ "metadata": {
+ "tags": [
+ "skip-execution"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "┌────┬────┬────┐\n",
+ "│1A5C│C20F│EB79│\n",
+ "└────┴────┴────┘\n",
+ ""
+ ]
+ },
+ "execution_count": 206,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "D2H¨ 6748 49679 60281"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 207,
+ "id": "south-transmission",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "H2D ← {16⊥¯1+'0123456789ABCDEF'⍳⍵}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 208,
+ "id": "typical-secondary",
+ "metadata": {
+ "tags": [
+ "skip-execution"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "6748 49679 60281\n",
+ ""
+ ]
+ },
+ "execution_count": 208,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "H2D¨ '1A5C' 'C20F' 'EB79'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "banner-sword",
+ "metadata": {},
+ "source": [
+ "\n",
+ "***Exercise 3***:\n",
+ "\n",
+ "A sparse array is an array of zeroes with only a small number of non-zero elements.\n",
+ "To save memory, sparse arrays are often represented in condensed forms, where we only store the positions of the non-zero values.\n",
+ "In this exercise, you will write two functions, `Contract` and `Expand`, to convert a sparse array into its compressed form and vice-versa.\n",
+ "\n",
+ "`Contract` accepts an arbitrary sparse array `sa` as its only argument and returns a vector with the following characteristics:\n",
+ " - the first element is the rank `r` of `sa`;\n",
+ " - the next `r` elements are the shape of `sa`;\n",
+ " - the next `n` elements are the `n` non-zero values of `sa`; and\n",
+ " - the final `n` elements are the positions of the corresponding non-zero elements. However, the final `n` elements should be the indices of the non-zero values in the ravel of `sa`, and not the high-rank indices.\n",
+ "\n",
+ "The function `Expand` should do the reverse operation.\n",
+ "\n",
+ "Implement `Contract` and `Expand` without using the function _ravel_.\n",
+ "\n",
+ "\n",
+ "Here is a worked example with a 5 by 7 matrix:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 209,
+ "id": "careful-canvas",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "0 3 0 0 0 0 0\n",
+ "0 0 0 0 0 0 0\n",
+ "0 0 0 0 0 0 0\n",
+ "5.6 0 0 0 0 0 0\n",
+ "0 0 0 0 0 0 0\n",
+ ""
+ ]
+ },
+ "execution_count": 209,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "sa ← 5 7⍴0 ⋄ sa[1;2] ← 3 ⋄ sa[4;1] ← 5.6\n",
+ "⎕← sa"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 210,
+ "id": "warming-theater",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "]dinput\n",
+ "Contract ← {\n",
+ " flat ← 1+(⍴⍵)⊥¯1+⍉↑idx←⍸0≠sa\n",
+ " (≢⍴⍵),(⍴⍵),⍵[idx],flat\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 211,
+ "id": "colonial-hydrogen",
+ "metadata": {
+ "tags": [
+ "skip-execution"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "2 5 7 3 5.6 2 22\n",
+ ""
+ ]
+ },
+ "execution_count": 211,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Contract sa"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "waiting-jimmy",
+ "metadata": {},
+ "source": [
+ "The matrix `sa` has two non-zero values at indices `1 2` and `4 1`, respectively.\n",
+ "However, in the ravel of `sa`, those two non-zero values are at indices `2` and `22`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 212,
+ "id": "progressive-albany",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "3 5.6\n",
+ ""
+ ]
+ },
+ "execution_count": 212,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(,sa)[2 22]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "continuous-specification",
+ "metadata": {},
+ "source": [
+ "Thus, the final part of the result of `Contract sa` should be the 4-item vector `3 5.6 2 22`:\n",
+ "\n",
+ "```APL\n",
+ " Contract sa\n",
+ "2 5 7 3 5.6 2 22\n",
+ "↑ ~~~ ∧∧∧∧∧ ∧∧∧∧\n",
+ "| | | the corresponding indices are 2 and 22\n",
+ "| | the non-zero values are 3 and 5.6\n",
+ "| shape of sa\n",
+ "rank of sa\n",
+ "```\n",
+ "\n",
+ "The function `Expand` should do the reverse operation:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "personalized-carolina",
+ "metadata": {},
+ "source": [
+ "\n",
+ "***Exercise 4***:\n",
+ "\n",
+ "Create three variables, filled with random integers, according to the following specifications:\n",
+ "\n",
+ " - a vector of 12 values between 8 and 30, without duplicates;\n",
+ " - a 4 by 6 matrix filled with values between 37 and 47, with possible duplicates; and\n",
+ " - a 5 by 2 matrix filled with values between ¯5 and 5, without duplicates.\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "entitled-sponsorship",
+ "metadata": {},
+ "source": [
+ "\n",
+ "***Exercise 5***:\n",
+ "\n",
+ "Create a vector of 15 random numbers between 0.01 and 0.09 inclusive, with 3 significant digits each, with possible duplicates.\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "mysterious-print",
+ "metadata": {},
+ "source": [
+ "\n",
+ "***Exercise 6***:\n",
+ "\n",
+ "What will we obtain by executing the expression `10+?(10+?10)⍴10`?\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "painted-tract",
+ "metadata": {},
+ "source": [
+ "\n",
+ "***Exercise 7***:\n",
+ "\n",
+ "We would like to obtain a vector of 5 items, chosen randomly and without duplicates among the elements of `list ← 12 29 5 44 31 60 8 86`.\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "affected-multiple",
+ "metadata": {},
+ "source": [
+ "\n",
+ "***Exercise 8***:\n",
+ "\n",
+ "Create a vector with a length randomly chosen between 6 and 16, and filled with random integers between 3 and 40 inclusive, with possible duplicates.\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "raising-judges",
+ "metadata": {},
+ "source": [
+ "\n",
+ "***Exercise 9***:\n",
+ "\n",
+ "The value of $\\cos x$ can be calculated by the following formula, written with traditional mathematical notation:\n",
+ "\n",
+ "$$\n",
+ "\\cos x = x^0/0! - x^2/2! + x^4/4! - x^6/6! + x^8/8! - x^{10}/10! \\cdots\n",
+ "$$\n",
+ "\n",
+ "Write a function `n Cos x` that accepts an integer `n` as left argument and a value `x` as right argument and computes the value of the expression above up to the power `2×n`, for the value `x`.\n",
+ "\n",
+ "(To verify your answer, `n Cos x` should be close to `2○x`.)\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "pressing-animal",
+ "metadata": {},
+ "source": [
+ "\n",
+ "***Exercise 10***:\n",
+ "\n",
+ "Try to evaluate the following expressions, and then check your result on the computer:\n",
+ "\n",
+ " - `(1○○÷4)*2`; and\n",
+ " - `2×0.5+¯2○1○0.5`.\n",
+ "\n",
+ "(This exercise assumes you have some trigonometry knowledge.)\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "sticky-symphony",
+ "metadata": {},
+ "source": [
+ "\n",
+ "***Exercise 11***:\n",
+ "\n",
+ "Find the solution to this set of equations:\n",
+ "\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "x - y &= 5 \\\\\n",
+ "y - 2z &= -7 \\\\\n",
+ "z - x &= 2\n",
+ "\\end{align}\n",
+ "$$\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "middle-example",
+ "metadata": {},
+ "source": [
+ "\n",
+ "***Exercise 12***:\n",
+ "\n",
+ "Three variables $a$, $b$, and $c$, meet the following conditions:\n",
+ "\n",
+ "$$\n",
+ "\\begin{align}\n",
+ "a - b + 3c &= 13 \\\\\n",
+ "4b - 2a &= -6 \\\\\n",
+ "a - 2b + 2c &= 10\n",
+ "\\end{align}\n",
+ "$$\n",
+ "\n",
+ "Can you calculate the value of $3a + 5b - c$?\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fewer-combat",
+ "metadata": {},
+ "source": [
+ "## The Specialist's Section\n",
+ "\n",
+ "
\n",
+ "
l^aA(87tUy7%ptAg~N-xRYGOoDffZYgPW| zu^W4=vW8O01cF=~)@Nu)?cNgMaG8*$fu_Mvt(|pIP vVjVDH!I8CVKmw7+T685<-=&1N}qN6TgJ7iiDK+u^q zr|{j*u-QUN(D3WDdcrV5X5TmG>Aef~m@vj>BhlN=aH3M^H*k}OiOJiNF}P>;dCVLt z#ISbbPT8)Brd*dlHbZfj& zet&6D{N}eaII@m?5<;|IG=F2Ybb2Rk?!DtUF?^xl(e>*Ub0w*EwWv>n*Rb#Uo( a{Zhe+Rq9Vv;5bUTu7GY4gULYMw(?Np(-$wva(;+dt>pzbku)vtr1X;o5=^ z?37_TjOoQ0a%FSlD4yyn^4NhFEwz$0-*RWw&fi-WJHqMo4w${oT@QJ3=@;G6w75#V z5Ij;g*oT_=@N=lT{J|6{OxNy7smb`C26s(YdA!cfHSD*fJGQG@j`rL#9Py&ZV{Y}? zjW+%(#}%;Jvl2^Gw2#(q%6D1KX5}bx{cvkIw)j1Ck??hET?v#>f pcQxN zpft6zqfC$(p)b`js2?YOUcsPU{$Z-g6u(G`F=(~>pNG~0Uab+ooB!Qr_cMqaG~IH& z#F !M>3!HyEy!>CWJmK+wK zQ^(?*WFicz%;;@4x)e=HE#y}iN`1V+NbV)moC1H7o4~+bMNpA9-kBaWym~-a7%{#* zGvIf~diYwm{|Di+zqD0dFUqPC+$nD0!|SZso7_|O7o@~y+aw }3S4Eb6Mp=;wEmW0 zA$F8wk~u#Ui3Jw;A6&lV7J5F@eSXwnTIUxR6kAdA?4)7K{Xx!H?5Jx2FR%-|FY~|+ z-Bv-<%>uvYOEbtHhrVG Q;xs4aMumG+p7*IiY+(f@0}T~AkLtI?J|vhG{%#vs1?P|hFk;Q*SG5!f&=tr zRO@wz8}k+BxO5qx=(@N}@LIXkx?Gq*7)5z87&qO;Jnh(9YIrvrNV1X}Xh%wm;q4qV zHO#VjP8ZS|^AlPzSrYw7kWG?;;!}anPVkrXqu!seBn+kdV5;BQ2Hz|&ln*4;I%GZG z1v~%}2kNBF$dach0XEXwZUt+f=*i$y@q5XFs4u%|{cr&=6Hqi@fZs8(ZxR@veVX2S zaCg`}hQTY4w4s$*wo0WtEN}P}bz7eWui?rBoph3`skQ*OHJJP|Nb9n<14ta&Ow*}5 zs+=JpNC?v;X#sF85lu6F3vR3*x4U!SWc1o+e|~+Etwv_DAW Mt+S>EO*^9HMIMB|8Z-*gCD-{uqtS>qh @l+&|$fQ#Zm%7Fq{I=Z9~D+n{2W1eR|Ww}YfCJJgBFR4}7{i{a&Otw9! z;j9S{#O06?k(I>N6CF3K!hj15gObRgV0yO!al@tG(xIDawbDt>&!dXf`07B5AqSu4 zzr`DXIHa_#wsqGS-ywL(pT>(K2NGAeXp*)$qtAl&-e7QHSPT}1Y`B4SKa$Y?{^j-^ zgNQv-N3|c>3d|4xdkvojF1|syZn$Xo<=9pg=bKb_*Do}QRZ=?%?~h*d^nt4^q?!Ih zrS4f`QpgFPyq;n@`ySb0mN%YLmG35#TzCHlkIknRJ66kuT!xFgwuYr18QJcveS7`E z^MS0F>%$+JD92#26B=tRiv*UBvX;ES=OU2vAfWyRtM{Lk61v_MVBpyFsN)vS*5S;1 zYplP`P2}KFSk0c|}@vw%neerQA zFXx?PddvOynx`x^T(3=w3sitS927;6NXd-Gnd*kRK`y7uS$3lI88>f+S%=V9y)igS zFJNb*TziCVXd)BXZy{rDH&7bqVo2m%`w;16&byc1_RgB$>|R9pjRqZu!grld6Qckc z5e$9-;CIyuN@2BNSC;EfvZ`N~&4AU*$r}f)i&}b>XrwZMY*k2)kr$*Sh*tiQapkyR z&p8u&Z*6_5)9-Z{v*k89zjwAKI)Yv|3#&75(0z(S3sSVvoc77%2!C_=gjG!iCgDuQ z`644W&hP12vKzHPwSMa`1oW=RH}>gF*Th-h`ZzJVrHmYw-L8$Z^wQP(ZUL6Pt;eKJ zUiJmQ>8P?CUt(xSQyJaLZb?$jH0aH2#rVGdco+?4i>}Y32FsFbzC+d%)wo*j==oPi z0*qHS7DQDXgX=uKPa(vi49<4n%dp>dYY;+?$US z`9COoLs0g;Z8z2W?4GKjEV-){2b!->GmwTp1M9 eZHpmgGs4e* z-;B-&Y26@BhmBluMO33*nNGfQ9BodTiS>(fdV$ymFK=_mk( ^JSvU6N8JmCq zQW1U@wzKg1dGhKLL3vYoo>y@nrxtFfP6LE&CR?hU++Fc99la>QU+BK`mheLX!Zva! z=haJY>e>cBJOP$rfouI3lWO86uq3`dj>n#UHn3O&IAZDE45CpGp4^6Id>^}gKM8w% zR=9PQoVkGBACL3o)8WvZc;9Cs 3>!9T+z=i}jnk zmn4%mWK;zDm3>@f?wSBFZ>ST&LM9*#8Z= #h_c+o^2J8Ti$=}#0X(0l2dU51%I+U6rzbniaEME&!^SV0S0y~d(A!J^)1Ck)(l zTGD!gahv8gDJml)tKIuD(0?Q@_g1&MypVm|+_p|zmFXLng&KXc;IbobnWi;Z{|xVJ z;56%X&^J~*(plOut)^@|TFz_Av@ET^CSB@lnP0Hw-;`?qKRRQ35vHC>9Pl!N_j6i> z6naL##Z%!DM|xR5jM73UJANnet`Z;u0SOwpudsv(Kcg)tdee)tlb_3~&pW0^%gjkx ztR{nIipDp| RR@2he$=s5qI=eGFwY335&U)dYJ6+cDAQ1?!-Js;F%5 zT~T_LLc=li*U4a}A`h14nOI1N+wU?I8vK4s|D99E>m29HBn&+)*`wsCmK0kEH9z)x zjvH|`+kfB?0v%L&6ls!VW_$xNX^(be^|%%nEU1R5>wfi6a>N(?N9A#OmcS)hRmGM3 zW3|xvww8TH8Nal-9*Ftr+$L)9T|lU6ld9d_3-t4xT4jiB;N}RZsY uULkHKIH)ve+$B;H{j-#bq}J zB`%1!Wk`nb^o9Hgf0uqL{J#6Ud&|jrj6222azq(sVw=thI}lSf2vRtXub|#teL`jC zlz3gvr*40awuO1z3pJ93tu)$RsBY=jdHZiKlsQy$h*YVba(KJwzQFd1pMC4COB+gb zhraPsw%_XCh_@;|z4%S*mvxQew5;Y5VW1)-4w%R~@M8 9q`QXhlI}(j6oz3aNhyJ$Lq N$+MJ*M3Otc5e+E zVF_nbaFdWeD=Nd3OcvMQ%xYUWLR;hYwYUF2hdvQ1?K!wZ=rKyUP#*L+ODtFUv8MbI z@_kd?9avNKn{M*g=*Qnz95Q%RHzCS0Ljvqhpon{O)r^zg9?)oMA3{&dxf^s*2EgB| zmG2Cn!uup5OO<(UPbOgMeu`iC+si&OJVm?rd&CbJYgrqzx)0J`yqoDim6@7luN0-Q zo?19$c;)AGntbbJcar>QY8WuB|Ckm0L_0p@_@9Fm=J00D`9W7qW#R(5CPUq()+^-5 z ?pz{Y>F0cDvm%v!UXq(YtA4#i8MoQ)~UMztW%g z<>&cQmxfct ZG`>@6G8d0N bBoCg#^1nf(Af$hDI z1AKbO`x&1-apKXPA3kP26s1WH)@!3Necc9;{91Dy8al%O<_nX*^FArCl`lwg!5%zx zX}q8xnJ$ 0)#CBvT$@Bxv`A;%8OGw~2-^Vtp0JP-Tw+j0?m#v2c!`DGJXk ze;s1p``I+G&c}rRV?LJ8vkHZ$3Y?KX01hA}7ua-ZX~K|(?ZxpX8(Ym8rokuAtgqg& z3?!Qi^0eUgU4tXhHaS>RzDE6zL=`N}jF#)@tZSxIp>MO}!zPkmWwnn@Z@MBtGuLl^ zlk8Fqdj>=p)+xC}U}s4%8lQY7$(? &7{|$mpaD@E9NSleg%sD(~}@X A ZJ;v|b$_3$BG2 z-NVjR6{Iyp%A=iDq7ZcKO5~|CFXlWj8av`d!(0F|7zHc_d=DeIf1xRCTd}_M_Ty69 zmG!5P#QKFTj>UO%oVLf>y_U(g3 @x-odrOoZkdAfYFK_Ta^)&BYG;l1Qe1_6CHe>M#xEaiycwC^(=s-%u- z>ZW`GQU7vF&3tLy);$eYYAx#Q?yarO?T@zW!B|<-UTDJt=zcElbInDpk_rh=YD)GN zPTE0UY`gzhJLC_yY-U3rDX8&eLvx)#1T3GkN#ZT$C5n$SXBaD8*NyEx4Fct6j1gPH zK3ii*&6Z(S3y%Lo@}v??1c9FHgKE&MSifc8>4ar8pECrg+UGn|=o-FC_@n^lX0=(# z0vBs;4gP|->EF`;XeYvg-H6@5lB86yItE5K(+m;Tj{`0;u75xHa!wy6uwDHFvJ_jr z{$8|pfvGJP=iXFR-_}msuhlC5j>h7j!4nyLv}Lx*iDk7Fs0_OleG_ouB&VE+Kc{A@ zGont52Y}!#(A;zN;Vr5X3A7mJiJ WDp9;_YJ^RTcqA%pC zriVrP1=zJE!ptqSD-j-d`Px~tQ?wL{lJ3$xOAX|#^3 T&C)SFC3(d>^cRmD} z3(lL_;4$hUCQAb4ufCs<;QZY}tH!M`mu3k#PNfMBNza%PkrQ%-u|2j1h@1EQGA5#y zt}V4@jNC#{g`Pr2&hkPdJQv)Wqcb$G^~1gGgCLF&xpa(=D-5{uyRXQCKLx8_6NcZt zFOE`fvbQV0=V6qDo!j}lz$8=HvgF_UoqzOeF0C=`ys}=~>*zb*ssHhM{eOL@2M-BD zqBuCasax>9u}wHn7`#>OITk zo4I`!?h|dt8!|FeJ5DlahIdre`c|p?wR9({dhn6A&x0)>5BTo_3=kFUSKeMUnk^Dh zk8)C8+&mZ}3}A5_t+dz!tsTbUN3(V(=um6etC;eoy*RuN1iG~K^HKUugO>|alr}T; z#ZTA{puMuT>TfKFk{0kPWMhIQnX;_&V|D@B-=W+qz~^yckcdIY8!5M3F&*3_Cix>f zPdzGpc|?KpaU$xU2OwS9jsB(ev?2ZPZ5E8ui@S6DNmr7KcDq1gNy@@Z77lwWIugas zU0nD{B@RAL#2+jeOq a$$kmOiLf0+}-lDVEJgR@;$+t{hO zw Fsx<^Qkn8eJX360)e;Kq-Jjj!_rxI zr+nAjLCZ6IK!OR7wnB_Nn0tu=N-J&0vV~1kL-Q~P39yKR5zz> Fqt4 zV@&PsK~{@s7ugS=hG)8PyX}WqPIO|PIxzc($Ro3bVi1|_L@Iji!h4EW_Y%bbe 1;_i!V9&)e#bDmM>MFt)h69zY5s~O|jbX`KpEJUV z$jPtb7v DIWaB=M|CV&C_!>tp#9S9Q!?h{qDtx$WdEb&kGP~`nJl|B&$H3 z7C#FSz$nYsEy}BLo-F}xNeNzw0ogZU$Pm{eSAC9_ThnBjhS6Sy$LB*xGM)#ZN28OB zeNRLHT9dE#bdAX`)KHJon}WH7l#deMy(ujYwG-H4tu&25-vJ7Ghfb^)|I#uYG~Dl5 z&@C3uiry@JWo1v{TP(`7c>G2^D#Y>|EGi~8p-Hx+4G6%P|3~C|8e+XU#^shuth;Iz zvsEgZKaPIBVBkd(WXIQ8#vq_9hy2YR!C?CDJ%Xhlhe$F{CSz{}kRqGXwCOd9#?z_X zT!n_JIx86_+sl&Wo?-%@#2bRoJn8gT-x#Vau;@fluq;Q_I@?jP;3V=xrF>g-YfA%P zs{7h>R1&wXX$>M_$43f0ME?*c1k%)N=*3?gKRmlA&=pl?!FXuI^TdMRyYG;UsFrN` z{2s1Ns~IA_&u1I{bV!!N4#du=n4=W;#N@cvsn#dh 9r37_u`=Ss9nmQefJ09b8NxPYP?Fx%l zGIFeNqBi24w4VSdiP4WgZ}PC$Y$eWnMeB5jo|6}ty(I4d`M|V~GL*b+FfpFZ{oF6; zG;ypt^eihA?;hvR#7BFFb~v7UJ5tw7V%mhdOB$PZN_gH`wi`$mR0W2dSDKA=>r)a~ z2vT2Kdn~Pt2ewfv#q%x#cxrUx6fU9=Ft2ORYf?iQi5IaLA>V-78KV^$yuM`JpsKfS z#A4&a)4XKK2yF=1ivMuokm!%`2F|Uayu!_QLVbL0K*qCKI&~ZlPUjn=p`*3A3}xHz z7$i@lW2Te`8k@q`+-^P;Cagx`OO|hIm#hoR!PI}D;H>R7_UJP0zXu%@<)}x0z6E){ z$#@+ajWLkc7frlNX^-66SH;m1V4OJmy1;(`{bFDY&BMhz$m$~9PHQ!a0!y}*rgoOG z)68 w9K7@#MpY9p)>iH`_1SYZti0&=q>cW3j&S45rpY=v2OD0y e`4;CdbVOG8?&IWlV8X*)|lOx9U}WK6HE9QIMdITzdxS)@**4 z$yTnHsS8{24HI+YvW9ubl80_XMNwbW=t-?LDq^V!#1s-Jyvc@JCUZ^GfX_d@7({ znry_wTzew|y@R%we|Q1 Rs}Ao`pCLUOodk<{+*4U zBfVWEPK?q>ul)yk30yrU{rPda GIL(CrgAH5 MJ8^E*55+1%g{TY0NO!uj#Rb8dr+1rgctj5xP)%{SyVe_kET;AYSm zEvAdy%oFO1);K9pjSHTj@|*p*&5$l+0I6a-Uv5J+nyr|pHtRtR+=z{?;4nKmsj@Bj z#$*0{)}dfRYl=5r&-(V=z+aV4c)OR92%t5kV)$cEjL@B8Z$bF>mn|oUS5C0+6+s?A zjUfEh`Pw`_0vt`-CdL_6R<4!!U?IyR)Yheuq*rWmM7z tD-)qhZ>Du6!%qo8BcOE)HQ)3~=H!{&UHhDK2@dqyAd=UYOXdp4Yr5Ls z#U};@WS64m7G^>nKlo`2ia$zzP-KS_18VMKh=2-i4KHxD9>}M8=lJ1N&x@YErCw=LL?;5@Mw4A|xr@E0W#5`Q%L;0RCQ z!zf;+tp@ M^i~@C- zXiDMp+T(jso@0H(baig7fniG}A*KRmctHez*I4`wKXHxktg-HJlz&59+q_$&P=vob zNF!;I=bF0++QGkREQ?1LRq rSB(?I3|Jz8vyy{+mBBFCZE!k?P;?i|F)f*#3xhew!#Khfb-d7>MvHBaqY zXn3#gceC lmjm7lL^Tk5ural#01fUs;Ibp71t&d02CI|?&Du~ TQ!U-R$A0H6Re1l%Y^!Zfk!OcG(#-W<|lyRN}0 z*P7^(cEo9@_t@btE!Ehu_CpgIVN1T1>E%f0!ez960UDd`vxnVFwc1_gL^TWogT#}+ zaym;~-L42IdDU>2)Uf(YbYnJT?jVDplqN_mW9{WZNH1i>Z?M|p_fLS9;RvJiOK9mQ zQvd#ebH4|`gia@Z^0F5`4>J=QBInifmjCevOc%~MoXX*UFl*hUB8y=>Mri3t2< z2bZ{rHt2gd74UliH#*}a!k~|r#-TV?BYT@Cq {%WjIP$|c;cB|(+jhmW(I$iLLYrO#@L&-n8M5UF&F^^C) zE!?Yo-i!WXK{e+tMR--I+^Q|1Wo*4x *(Ehklb#{$>1mU?S z3?z1KB!4pUY{;1Z?X|7hN1e6z6|hDrT9Xv@O2J2m>TOsfnJ-=Kr%UqlCRpJxOs<+` zL;_cIE{Qz9XDF~LzNgf>7~83^bd`F`)yg8zpV`{=-2FqfN_I_G;~G?&$jZ{nXZYf| z<%sW8OiYN_UXR}om1o?A8$z;mkhlXU-5gK*d+TT&xz;;Wx_j%*My3lrY^nCY5I6_y z9enhzdvBynVm7dKqcQh)&6{)@i!H_JccIg_G?Jf16CiYs6UcJDVefr^Gu-S@*`&JwP9w3vQZy>g8if5%(&$ACQJw2Ms*Hd3b&t z=mIS<=+N3-IjrC7V~>D;0_!V(fzi)=+}|lWEvK=rz809yaA_S%^?Tp~Q}%N{eOD$1 zQzdm7!6aj(VdQaMnlmozyJ48*r+QQBS8ZtzzwIanmr)0P%ymWSOTH{5N|oxN1!S0K ziSXwp!{^{MKP^t84a6^nHNJGMlxq=L9q!^*moPd)h%$ex@>!0NDN!=gkQF+uY`x7Q z egFE|Hs!ElwdK10d7)Dq2D~#I>(6_kOzjT?CJ3kB1yj))>AVrNd zy;jrt>~|8f-Qn tF8h;*j0C{cHD|^KL(d%H| z^AGb}?lH|i7r`q@nJ+}i$3mV|n0?z?w^Aa%a_n4H`Y!~Lw{j)3tqm;( IIAnPoZ=R!GQKao_A*&W2 zK(Ru>BxS<-gw2C~dMYT at{B3^JX_xjFZ4-Bs-}0 zmlDtMyKjTvbR-Xrs^$q4=`EN)Q2IrkibbFy~nuPELs+}F4+ zyt5`r5rcK6Jcn#-IRoO9vThPq+E4u6w!LtHE%T21<^y-gY0lx5xH+Q>ql2=KHa{0} z=DweG`)a(={!3-sKt38WDbb{gZt9+;+nC6YmTRFm{jvBg#yXoU&b_n-Nakr`df)3e z4|aOO=%T23?R;d&=tBFCYK< a7}K$U-x$EO~^+%?BY zwjt4$=Nf;MBbCmX7RonzYj =l(A40e_fuZ%>W27n6QzbJjEDB|%^8i-`Fe z@wDd805H|RbGtglS~?da*<8<)>)mz*g3X1ieu2bLW`CpfidahNp@9#*_q+6C$m6c- z{n4G|u<+qU?1LjnZFAP|jI35uB8}7s+%8&jI{cXx{UX=t*%EhN*B#5mQk6Uz`yhU3 zGEPrPoEf=1Y~~sF1ofPM>lvvcvkEqGVjsDTwn#`6PR=B82WUnIYZdKao(-`aeb90} zbJjUGc!FDeW34kqG^wgKO@5f-y-g04tdns|{M>o2HY*=Jr}Bs6(YQ~X UlTD~m zoS5-2+Q;c}oH#vgovz{@NcLQZz*$@w6urhg92NIHSfSX4eXir}1k8GyvA6C!+WUMA zP !~ %X=|eX%pO>A zE(ub6v3kF>pU>}FNO~4E?=Wtq2UK$b3Pks?-j=l5C`Q~q5pAfa&$xi`9|j>8k*1js zpeXmZYyui)6;a{VT78{lA6SP41e^;y0sL*7xKCwG*N@IPg 62_FS-cWeS&~0r3oL}7@GVYT4OdDt zwyrIo?|!&)u}1&E&B9RNF>xK8?t|fs24CE%X5R?pT=}%H!14TS+FTykxhMT5pd$ca zwd8XKgQMMG79%fJL&=kJU#~G|>leD7>vH+D8q0ZN^$nS3u(uG8&`mV{CvCx~<8r{r zfL5*){G*hWZ1 ;you*TY=e9(7%h_K`p{dxW4M2vFlN)5pmz6!> zE_+IjIF=?sz}M@av+%1A${bm#effbyZ*pKvN^6p^$}bl4Bp%F_KGE;y0vj{k5Y1YP z?t0z(f(~7r$;B>bkh=-QZk$$<9s|0>_M%gZbK%%z%QCindOuZ0Sw95m)hxGmbUm)L z)fBZLd?0XLBpM>N?7$|PtQVVHnCzo+rJOnA2*?5tqVg(LPTly|$z5Ui9h$t4YK`a! zZ8aZ#x{$puBryqQgujt1+4!-uD&Z{4`~dC8&N=bvVJtqPH)1VXi0>On@?N+Kn6Q+8 zE`r_!@GppnuysYl>1Q1Ee+4NeS*=2NHNT~hd@u-1G1OX@qbmEPup=2Zjbd74?+URp z0NC;$Yz{twXW@%myH$7m-0?4Z@iPN(>_}ZcY>xj?;7sumAfBHPhHJA=nnr2csyDOS zu@h)h=TV2?Qncm?Z#mc*0VsKnLekwO 4!Lr20Z?LQ@EbmswGD(Ys6+-7aKP?dd7T;?A|W_Je#>C-o6Gz{KJ zsqj2H$W}@fdEsc<(ahXtC&obSg2nMs;(0;|myH4x09=5ip~pm$c3TPAQ|@m=bOuj# zG$%>W2(T4P+!eEv#jS51cdF7&Z0qkp2D5%X{<#Ieo?l^{lGMqdB>dy}kEo(23hH8$ z=l|#w6r|G^i};A?PWa+|3g?|V^scE%6FO5S=4Uo2KTRtkmUBBI-8Lxj!|CiUWF2yw zWdYB;?ac29-s@x)!LoUy@&? D8# )VfE z$aGTxaMC01(A0$h1gTNY573?V#H{xiJ+~N49H@u8fp1l>7YZv+Nlx>~EMgG9k^vZ( zT5l4qR?=#-x6QECm&GBBO3ALPw0tu<+D+ok9S22ZmV4<10snB1ZxfDANiz;?MOBli zD5&TP^7||3SwxSOi*}vS{YVQ?M_bR2ac4M`PQ$2I7H?1BU6#r(Ap;v)d {f%WMdc}{9b&3l)C}~}(wxB0YkI8MqPHj(hvYiXKHJmvu-Z!YsLY;hBgRP$D-H;F zpB!CgSC{k7gJ7cIA5{s?RO~5i0(`(@a?EgGy^G@u`nyljGV#NHvlxp%_7JHK)DUI9 zIEb-9;x|8@FS-7) obOB4expS_Z4Ay_Sv8dS# g+@NZqG>Ah>A*QW(8oNaW#lCE^?Cv! zxm>v2e`G>Ba=EqlM$OLcue*|P(Tl|}Kn$;bA|tRSIKkeFsZ~1Fy_nR(3=$>WJW$yp zHB#BeUnWeRONy#9Apnf*p<6DiaW`%2i>7gVvPWpsL@#Xp(95awYQ {R!YJ1M=Uq=~BO;&J!L^z8|{OE)@(<>weGJ zgyy*B9S@iWHjT95a019}wa1K}jyLX^HwszZ=6fU>(oZuZrQ1TC*4_K+GL!drBbVv8 z5tmelDO~$nYQ~e7w@Xwcc@H7m@`~~$?PbyYn~z+D<3> tWF2+`CY**(ynjq~&r((Jd z0Yz}%-;-h4+o>Bq(^8YMhUC`w`F*Kg^O4zgg#TtI`2Qn&DP+u$rWD#rj+c&>C~t55 zHQ6nrlTLcs3A6qG4JZSk_qK)ECeH=nZO)*dUwv2{shw22Z*H>n!h44zHuWB$VF92> zlY5mzX=gM~jg#wkP@(=xsmKk9)KOdY9_V-(?tcvcwJx9{>_A7-<7tH_-%rU}pYJAr z-gk<_w{2<(S~O6dP5y@eDA@^AEcd;X;C>dgtw-w7jtd>``BzjVYJ7Kv%^fvAZHmh_ z5WsnX2|a)UaL-|477iDuTFFUo0k_BF9}K=XMZCP&Ab9aI6Hwq0F&X|uisncB{cqyF zQScVoHloN!(J4YLukFnm(#^rr!#&+HS~v|r{iZQ|d<85!F4{N;ss7ktqfQ*amlkpJ zuZcBR@+Mw~FplkZ0Y1xpu%yWfn}xBy&}hZF=tfe#ROIOSY)8M(2j*fZPDM9IwA1-0 z1M)M_=P{*=`TP)#kEoxc*eIrV-OD2^fLxLP_rx%_)fJQP!hvdX^r>W5Od1JLZ~)|I zs(XrquvqEs<243A>5i4`-KC%wG*(cUzSCdqLY7ZC8|utSV)*~qJ3zZz*m|GJ<$QdW zkL%T4hazi-FV~EYayIV4)AWgd2aFd`6QM#wY)0$YD>A2V*w9^V{>>G`$vx%TuzEe% zAdPng0DG_8<-+cDcW6^hua9!^5bX4twkhi)uCT_<2|rT6y>MlLa-Y^?-0atTKUWS* zUT$P~*x`+HJOm(Nell}Zi30d_QEkjHK&<4U*w|maL1{8$;t(VwsKe9F_?st3$o2;Y zjnvgbm!PyqfTO%>!y>c?V!Qo!nLxY{8(kz;sf`L6nZk~@9T~YLx8mBtyI$iOs~n|~ zn3YwZc5D|RVL+uRcle&2!mKWF+#}?AicN*qgWV3K5ZwtHbp=;2;uE_l`^f6-c2J)j z@AMd88b(c|0gs-oIP>pcMFB#iPl?-`RHGcCn6(yPUE#mc!tPFIQ{#LG17Ptv%m(Hq z0~ApoqH=h $qv+l3$I7f@2Rn #KhuqTVDtvcV+baECs z=f`WrMFJ&JFF vrSb;5gPKLybO=0bZjR+(acnoMqknYzTO z$HjQ)Zdpi!A^5U>Z{zhkTbW!hdiL8|sb!%>7>6e&u-te?zAr43C7A@AMrGbd$1U=k zk%r1j^MwpBTk> `jy8^8^Cn)sjYk zEmr=D^BeOF;R)b|s8lYlXZ;a-_{0PxZStlkXS{J5c+MMGr^|DNseLo;4L6cX92S0+ zcTFV=P*N!N(+Le}w+``QmJ0XvfpCW9G9^15o7yOQF5vx4FMYLR0#6xZ#VQ+I#@zV| zHy@`>3Z&psQ0ee0o(}+U7Z}Eo+%$ V76ex~fEs&Yf^cw>43CeS>^BoL@NhX`Tezr`j%4iDJu_xk@G >&mq(LS27Al3Yoc z)>aEY+y$ uC>ka|`YPHs;eZ$6*P@g977$pS0_%V{YxLa(vbc4Z+`ZkHiRZdq$y znn nnlTXo_h=k|p(G3Q}fjjAh2npal zJoleo2m|jSd@caO$2M9&Sx6`2v?? *2QoNWg1acfjdxf7jV(i8LwuYAeuO zX|kbOWt*3u>)gXmLa} V6U;2~L7xz^rSB4WsiIwu@F5R6cjZhOO-OcbK31}vU+;{Q-=oo-$(I#W59d-( z00~J-z>xM~E?#977h%tBe4u!JJT_AxHV@d&lSZX4pXXi-nR1tVm$6iOL_9~1vH34h z&6#?7H`FQsr$*r|^NaJfa4jfa(27Q~bp%a&x?E8??A`jn*T}lOYfFWERnqZxS-!z1 zL#7;)>o(pD@jDh81V16WkPbm_Ypp#<%pp(u^gkcn*vZN}nw2q}vX`MWw{tdc#h;9~ zj8~k9B9jgnx=?ZYs(WEPEm;69LM-cSKNOP4wk~KMah;QF7^{B 53-Hy<{U$+%?mMo*eSI zXP_{XSK$S%_Dnu1#i4Rn{ztX-G)D(`7P6~2!ZG2}|7wAP;iKdwmhhJ?!Pz7rMeMx^ zRX2%Sb)ibI8!l+QjPs!O#O&y}ncE@m9exJLClFqYoH3Q={`uBp74XX)##6al7%REV zdm36Maq$I(uZLzp9L&aRz$BdTHV5?pEqHB`%I0%#hZRj6&S}m~|I6z}UArKbj-hEm zF=(mO#+I`ytb19$E*FT2D%Pt#*qGE53HCaMPM}H;eK DDn7x34%VZC>5REbR4wsoAsNPhoK{Ma#ml-w==Rsa@n bE=Ud5bENtsW)pdbT@j8CfR=iZU z+d{5vGs*P8O#HdeEf+6rXn>PH+M(_VtGF^ syka*Egq&W+K&zBMqb@&R^pv~Ky&TkbJ0gb*SY?Lt(UzLu)p5;O zYn%Hw{9TSH!Oz)P8>tNAtqlZ3W-GiQ=brOKSLH9>XFYGg2-5?qOp`L2rPs3P1m*7h zdSk4zjsS1jlzI6`p$H1DoWwDTlG)u|qRS1B>6uaUb3CHWm`n`^r1V&80lj}2=7-)= zvTY~!dOTZh{+Yjll!N+(=+1`q<@A&+f7jtlCSyP$ze7 DbmOoRsKx_Me8K`G#=Gj&Lkj>VN`aIX^>e2WKN1zsvi7F!O&9vl}{)~KF$ zm{Z4RBCo$Iu$^ZExH~yaU5C-@#7~-4+r0MuZ61mDccgp|HofW-O$ai)wvIz;R(Tww z5vu>ZOrAWEBL-MZ_ho(tuU5a7&Tk4iPUO$O+J-4ij+2IAWeUZ{UuZmZ@;=FGAx{YR zj ya9Gq8@r360=4$oR@si<(Ro)5Ll%13SZe|zFS6AVh zIhTy?6+N(g!=L5f*V@XwTXvAp+WWigmPMX +*EO|m$wskUC91h(_4$dss< zeu)h~I}c-_XY8+&=;pkL6uTbriSbdYYVM_k!5rV_y6J^IQY^#uuyL7YbpwEfUc zv;7#-?+XMMR(J9i)wzNeC;hhCfrnxxO|J&Jj@BvO-DU&2Lik3rg7RQb72{*N%oC}@ z?_|F%BtKBcA&+451XAS{WQ ;HtVm? ~H%Xz`$z zq7ByEpSqHK`IO`_>vYcaiZWAb04%09{ETTvR|T3`+8x7ipIIsTr=@lmmQm#oGN yoyv4(wJIXfGR;s>wgypl2LOWLq5D6Lob3JSOmVbUaz{Ba`{hbG|n!c0dfSt zkgaR_tr!8Tj>QM0h{hC@lG!c2RHW!5BSQd%24j_HNO4H$sx1mTGIxOYwq=twFg3!@ zV$4=$dW|v^)e7;#$dkxOEO`P9Fal;5Xx2!9ba8+Q+WU}u(5#^?$ZO2jY&u?F0&9R4 znhltcGG&G6)WO4jHbkxmDAHi6A*pRd*8YMy&dM&3Y^qtRPXMtcGk)CSbK%RfEv*6+ z+%JJ*>E)Qdth_w(2*0F1&0{1p!;yMqrMsyUd{x9c3ELZC$xvL!&X_NDCNm7^5yd(% zLUxILCQsV!XznNCy{mUkige`NgKR68L`|P{a=2evV#ZE$MAKJH_F5ZaD+11(NSS^K zyHpbxwJS@uOo;!&iK*in;1UdgDr=3}npyE0^_$RdlCCuterj1%QaIBr^EY{z8)RyC zO_jY88^ g2F{0U^}` zW8utwxS&30_4{)e$Fw*ShV%d4YaOB$#a|r<+{UkX?EKw948uB$c31Xx{GUivc4qzQ z*TnT!rD`f^E%DZ{k)ORJY8sHSBx#*eUl7xDD>ZrX^O73Y1VFT${AN4LS+R8B?bEjTK_m&@A2Z=r7tDWK-x$Xfhndd_AR zcrJD`dmig_n3)T;fyKoWodVyJm&dPSkv0Lh85zL+P$@-m5?EPI$8e4xHok%wb}sk0 zBjQ!gK18dQbn|+rw3|2nq`F1#@3%$k*jcgT_?Vy8YSVQ?lHH-ZK#FjoB|RNXulb)` ze_2tF%hT7SS(;A=VF$LSh4`jiVvy >_BT6Q5^P}L?9Ux#FM2JdGn%zosee&i+HVZ6*3&2+fKW-D;KbxCU zioK0V*7m2L`V|rU1Lk;EY9)ko9kH4waVEiQpf-Eq55LHyvcIGG`-?W>-(;3(k9N}c z7k3%SjYU6S7tgZL>`hD=G&sq_kR_2C1T^fQJZ $NqW>&9a>!MdGn z#Ol}={wa`~TW7pH6ZDH?=Ig)_XZ&i{#VU`xqxoX&-4DoiG1latd}8HJ4@oEuh%9^7 zx8#<0m7?b+DBu55s!i%ozzoW9xWpqw`ae3T$Oa_`vbMr5a(woyLam*wTdhb*g^xF* z#BPnyemb2=Y#9=7FK=di;Yvd=feqTMPVYnW#zW`yo6Ab=_oGteixKnj!j1K!OVp(l z>YKnTx65In-rpv@TPD3{CVfBdCgxzq4_^T@w}3+s{(!IR`qu}RCzuLj6R<&BGWwsM zlN*qXQdpXYj)@&mk+m2hE BP&W8} zbBwy}>q7|CT~;JqRb=!WSUcY#oSh!@ZZdNF0JGWe+U{mod1Eg7P#37HHn%-9>Lf?t z=7TbE&s}7CKG~IieSv+1^tv6 YJP4BO5UW!g5WZLhc27GSGb9uz+R_)M1)v&nut))Ej z{!OgLre67=E`m_pe?M~njd1=OaeP&K-1rWa9fWiaT;RU8Mi@wKM&2X_p^i~KTG|H7 zx-DEE7l1io3*5&xlj5JWlEIk2@Du2DM_l-}yL?UE57~ERO`DYIrX6)*#~MEEI@80H zfha0qG^xt=C1b@|tnaXt6v*lQbvOK4d~ox2<@mPu9Nae>zDF%1B*i0X9|&IbpJEP3 zzAg7tSA -nIR(9muvP<8%6bLG$+Jg#`idbKcuxl_s!glejKF zxAV`OfjqIES{^lY!0;CrOR_dCB@vcmG5t0Hvo;HlVenh#Fjb0cc;DR|s?+W~G;o68 z3J%!b5=cCHg;pLf3m878s*pbUnhSFNQES~`#t2s>o2%Qsw6#_Y6zf%)O}1gXKlM#u zpM;m7$f&)V!t=tB_e6YjtB%D{P ;yt#G2O{OT>AcDl2ZqeA;Z zrg}WI=VMHkvo)QHa(rhX*egJVv{I9wv^Vw$<)e9f!V}uYVs#aiveT`@r+n(afhdeQ zeedWI!mla6ra8$Ev32^!uaS{$@(Q)pcax#dUz3>ivGnqg-TZg7kWY#b+z26iemp z;r%kKQcec_B|yFF>p>Y_t^290-3$(<9Bc>azur!FFk4X2wzk650;&Ut%y< W; zb_hs9YtF-w7bmAzn(_!oQyW)2Lof|P|Aj)#6sSw4EXb6Vv~jTMwNf4SXhwQ72|1Yd zZQ6J*dl%to{%uoGE1{ZB$Dg4|;ZnO3XVhVzK;7fMHPV}pvhxbkFu2r(yVRVyVVkF# zSlQ|n_e<(t?*VV8js3MkwyeUaJ8X5I;$Qopg)9}Gu&(f7nl`;#;Xbx@IE1*-8)@@< zvpDK6G^*id5d$(ddLMRgPSG7)U~FAUukk-x=Zm7$`gVn~40fGwhMoCWyM)Bu+U)Lj zrEbe%&af`3fXz37n{~G>edh$Xg;G{|+NgXKGU%?J=6rArj#`(xi7d r*!Vc->4ZNKTy5+tJ@0;jOL#+R}_6w*xKAR3))ZmliFIAg{R%KU%@AiXk z>vBgr(nL{Bn3pK=%f{rr#rgd+Ny=4nf{49+#Cl_U00q-hKmN{2=7{)|)koBf#;xx5 zx4^Kw%nT`-JYlp?Kyh09>68Ndc;>U;6EnAmxAi3zPUDP_>OdLaLa-ORm3qzl?F+XA zYE;TfG`Sh+6JMf}T= =4cvg^ #`LvO0#-jIw@HUpn1~^uCK{ogi;$mEu7K4N|O}7@+nn z&LUf+gw7TdP=6Cp_kUlWF7xS(r2&$sV;f9+0x{rd5@bozT{L2uxn!;;8=s7GNg&d& zY=Kk9!UDgq1M#JN>&;V6I+r*7du25@3*N%K2Jk}-hg z>u6ug+>Sda7o(KbnPpcO%96Qta%;=AVFL zx)dO4c&!THk@nvU#t kg76c$s1iPeyC zml(7 |*uqaMdZ$NiuiJ-x((h;kkB!x?6Q7e^JdhdInp)QFFeY%2-Sp z7wef8%fgzkX(cD+m*NSRMfTh#(jh-}UXI+mhvloC`!!hl7IpVq>h2pti5^^id}e-Z zjgvL3=~(l}Y-P)=cPFNI2iki89h16`;CJU{>8ckRm#G>&eFBdZkv-3iyrs74u08w4 zGpi@xBiSvB5OF&&skrgGWQ$Xkm2=0{^TU;+L)F7s{P%l}h`kOSd*q<=RhcvI;9Y9| zsgA!-OWGvC-5P;lD4-thAC+ciK(OMmI@WX`qHc^l@qb$RQ-IxEp)IAA-phQ3r4Vln z9Quc5Q2T|If?{p+1XthlH^5>&!RT-jl=#V37Fx(pTt9B*xp#4sw`wVv))D3y^s83D zWSX~+H{l@}DgUT7PNY7ITIEz`)}-7<5<*fP37eDf5!~s!Z9{>+9+{b+b9qtveYwhA zz06(Rt6AB*PoErEC%a|K$&k>#8z}6e49&h*=*JuAC`(z3_R>@{59;Pq!e8ub6CDx( zRC5O6R%LTGVRbuT<=f3U)?Gi1t&G5PWn@nWhBHo4S;OO{8)U*wlGOWN)Lz}4J@Fyb z{G8WhpgH3hh6X##28960!kwB}yIEPsj&oH N;^Qn%$Ok8AR~hcqV-)sx~P?!M;#C62qh8@0G(ht=H; zSp3Cpjd=pvJ2-wSUc2xLdTvX&2jYD5SwLOaCVTY#`dT|^(2 $q_g6SoZ8#xC{jzZ2YRNAD6q}RvEkKpj z(hR|wIELMan7biI!r`bJxX^00XsIjjhCO0mVihR?(@^D^ieC2X{^r*Wq}!!wD(>$V zZ$`xltYT98t_)E2NDX9vVj4iGox6ivmUp;LV}e#pR&J&jKZJ)qbCWdl6vC!)ZyFZ9{;`gcQ=|E_U~nFK`%MZ*5>Qa|8Hs zID`gbqP2Qev%2`{_ x_^&uzueQ>WQv^R?yCiz7S$Naw!9D&Cch1gbHvxUe7gR@Y zng!I6)?6`tu%+%eJIh+$Y!vD*;Qj8?3pl2Yaza^F50jHq4F4*sLF)Wl?Dp;O>R$>+ zba#T%%e2d=N>WJB|JU7@KSKR||G&@+MHC?_Dxt*|gUVQ1C|h h;?nw}rTFp? z%XaXbHuVaHGPWfjoU^qjtvD$?KcCI!xaR=v4{%i|bM`xpNv=ODQZ!-9b_Td*pUeo? z#k|PGv*n9vn$XF+(qx4=dY(=K?#qtj&w&(pP|y9q!~sk40MGuclc@{D^|=k-=|ITh zF&nV!LNvXteb)cCA`;jof?(_u#-RjEFgqjI9dFdJ84ZhV*=8$XGrFBg7h)O*$fTbx z6m9)UNL&Fb7- Owsmt&tGoS-u _Olbs} zkuLEu$`9)2wF| `>HjG3#c|a xA=S(Kw45{chktB0cINsVI*t~lG(w6&5T zZuHHz3!O&luuq+kj*=u9O!R1r%)$#whJn;A2{3XE7A859EorSTMSa+g Ah!BIa5>)YGLn71i>hoq _y9?0AW?bz`$V_@Z3U*R!HW>LB1~!`~(+$EDm$E9cp? ziVD|fQwFn=mhxEK^*M##*3VRe7K6!KXn_+l0iFw(^`u^kh>X>UdEB$65KSZ=Y)x|b&9F6Ez^Dj2n8 ze^a1>ScAjk!h-a}WL)K4ow|m{9Bg`e&TFl$0{=DGimOw$Brv#3ds}Ti7X+|21a5uj zwVp%`T+O#&l3h^t8%xSv=|aSh@`^=go4Wa(uAyFp_CqP9{r))WBT92RdP3g3*_-7w zasJIS|7e7Si0OhlstEr-0QhOGsQp`3FV5W5I@ZLaJ9ugM*whx^E%u|m{U@WJY1Nyv z$fi-P+1%azv$#b6==JgJ(-tn0F?L*Tml6Ja-Pq;ESoJ(E5p0 0C##_Qr;I%1hS;B3q!FRz0}g?o%g=?2#>{xN}J) z0NEt0pJdQLtQ0lMEknB#6FAA7w1U+YG)Cqyk3Wcz7J6dMR?;N_bc+|5II<9 z3rc3WFI#i%x0)(Hmp$LQuE0*M$)}6^dyB4&TX{-rIY?76>W*TOu=<^qmemCc`jp@0 z_tg;}Jhpk3N7w@$yo3zp%iylq>q=W7-NNW@mxECa&h2S6zSl;>fgSA^;^}5rf5T^` z&+^5a-d@}}^$+q^({;i85_r =a84`w;yOp>zqFzeVc0cJ!W6!G zvbT c~b&CFesL#TSTCQMR&4EoYM0XdMKO{ylbso)xmcw_|! zGbz8`yU&uQ&QztP9apEyOU^#ZST;JctC!ZRMX^n7lkJ+l!CcHY6#0>>OletU? +e_A-yt%N|$IQ?{yHddu!M&xqVXH=Ib~~Is3t7YK_5394jfK z`WK-3z@F&T%JEQR8+U44%de4DvYhh7X+ kJJvt`*IA=4Ni66)H3!y zJ&dI%AFJMzw?*1zw%Z9J!pS&!xq&*zsILlDIC&oQO;K+qA{1_0e}R5uq3Vfe@v=E` z+4m^##`(U5=eh8{a{=@A?<<&w6|9tuZb=j*mw`j(G=APqgP%1oppUVvGot95^I7gA z5$tyyB=Q7~#RGQHajR{RW+0-%U$(;=P<68?WZSX}_s)|$;eUvIE~b)-;1pEU1{s-M zCDC0QlUs#bEx>Mj%CffHzAIM#FXCMDcbWUJtzV8zO3byGi7fbNnS{moz!lFv8Slyv zmp&ig P?uE@ufOFvMZ~Mw)P^GE`&kl#O+pD~V&m`cKQJ3iVnmn)%d{30s zC)dRz7M(r2YellQK0mK 0!w5q cpi!` z=Q@< 0hJCBON=m@XTT}%wm%k7r5@PySk3f|E#-^Th`tv z-@Ma2z`<`#D-)9acJ-{mqty12w~u+cB_tBB6~e{MdZ5ry@_ShZ|H)lD)pttJ2LB<= zliaNh+8odId}gB>COaVV*caJKmZegW@K)XK=cnkfA7`+>oyI{^# w9p< z`8H79fd@TGOGVU))`E~?Ej-{yc=B{#3 lXYLE(EJk|Ilicl`hutm;TO8cpmJn4@}n5{;NuJyLTzzp z!IQPC?wmP!&jxvt 0f_>*R`%WQ#TU37qlGbgl2 d1xc(W=_{@2<4mpV@F4#!UpFZql(t9-9GwswoiHC;Ggl&kY&9)n$4<)E zHO9Da8BuzqxSTU4blh-OFKrn%g|2;);+Cb@L0iNNOD2$ECJ*Mw2@^`5WOU~AENq}0 zLw24NEjAf#;`Yf{%bYqZ{#|5ECT2)XQifyjuJVIlyFM`(b@FIiceM4(m4dpt0?jX! zNk#JH=pW4JA6nL9_*$HA$VkAQ>@8(4%CgGhy2~O=v!^m|OA6P%4}!+A?to50+#W$` z(Bt}Iu}xic5we&TgOeDBwPt(FF7CJEcQq+j7D$3`c0;H`a(TAL^lv&yo4MOeY)57P z4otknm)Rl{$nMULdypili;^cs&ymI@m?+vcBMW#_Cn_77*9V_56PPrmp$ci^m1?+5 zKivXv*DhSDnhrN}QoV~aTqI)1o)%00S}gIVE%T;rMx7%(y7@MH3TVeJ |+ed#*aj#x-)hfWuJeeK+bLmzD(tzt#pNmR8?%b?83# zMJSGGE1Wvz{4rdhJeSDkKl{{*d6@GCw3 Z3B}_s?MKjdYx?k%{`}N|uc7UH& zNTU#aiw{jVYAW~uH{um@h!3RI!a5(_;?V8Cx0rhTdE@|Ds#+m8;hxxF<;4B?nyTIV zQEU=N2tLiVZj#q%cuYru1gsOh+;Ga&pL DTI}rJ*eN^3n}dQt$wQT-VxScZ4 v&f>BBVCgclu+-d`rjBK1?gEu>p?~ z=;9N1d?j#ZMdIuI{RFi^OhYsIjkCPJN`Z#dL%Cu}C*)s7*}vq!%XCv{UoZ8sGpfAP zmw_ZdT&d;JFM_`^|Eyxz1qe2eFJWYZAYHW5e?T)0LYgxSK{s|kH6)5f36&!iX5-UI zE<~KUS2{pig@gOIy%HHBhG(zw`-G=EG`-;kOq}4ozZC(Kf@Y~}EGhUr)vX(9f*$_# zf%re9JqCqP_9F>Bit;ttHTL{U{PuqVpyh#@&cJGnxTJsm&nxnhiKx@?c-}{6F^Xk; z1A7{)QkFQRBcV}w&rSQMC=E^@h*b@URf~i8O^O;-q~Hmty*_<@;-#cKG|l^wH96dr z =^irF!&ksag#J(OZMg#fXg$^KhG;0T zer`X+o&GEq`rEy|JTsl;{UcHe c)!jwD&3 z@X#{Nd-@HLvNXA!Un+b+7xkPzj4X6T>k33<9PCjUZVn~kr)+~ z>V@d$kXdK$=9`>lIib&nd57*)%9|IC<21`-q-22gc& kL;O!@ET{xIIp1gk_3Lx(QC2%0VGBVD|88)#`VhPI#1G+kub|dcSXb;e zV6@5 zA9JdOEizzD9tE$fNyfFSTJ6CE*mF@e zF&3`3EHjik8_t`w;miWRm3ZatdseNs`x1M^vEAR#g!43`n)NTk^5JX*9K+AHbGtuw zsyVx7pA-QeNm?s3YED%%X+N(1$n0}Mjk~FNb32NEQZoIh|H@sdRg>5-%~us-h$_p% zrB=_ne&)4sc}>Ik`{#%(c=5?4^W5kl70Nu4RG};4E@)GbzgHv6u2OcHQ|P!sRd!~4 z)p!&Sji?v&?h5GL1)}CvNB%h^2hoFPjAF3n_RUzz+U{-Cs8Rn-{&5d+r>Ev6o(|nj z(oOJV2MhwBp#U(qWr&&>fn}YZb{#_;niq3l=Mj{9@aYH)oy+T{ Q75uj-xSdl`isXZdpu zynoUQCr?=Ql-{*_CHc=)WbLhkg} QJug$)e;WC;4O$7Cd)zV1 z?GyO)=--~wnQ;;Wc2C|b+XGiL1&j%tSVxt2w>kilaO7+htXZAi(&s4+g&9m7QBZI4 zU8W#{G=4m}!~3joKJ51^qQQ&A3T;o^4CY-!m;~i(!=1w!4erxNz>VB-kB#y8$saOb z)Y~o*qa%eS G}RZ*gt;Rne7bj(M9qAFpg#L|E;o;Nbk zbh6b6MOT+W3I`2w`(Q0RNB8h80`0J<6|g!H{Qj1Zue+A?o%-hA#xGMY{H!r>8mP`4 zaQ7Tf7iM2xvZ8^ag0~(3Rua&q5*J|%TyNZPGF8^=i81gBB<`c_f3B2SBGigSAqSQM zc$bT^=_ZuVs|}i&Q@%nGzma+Q`O}!;8ZH@X#NaG!^|?|w>w#3jho95;$WTBDRNG9Q zgt|@cJ;Svc*ssx$T)Z0J1LmCaFO=3e0ot9ogQ1@gu6J1LlZkO>)@m|VrC@3&$7AsZ zhMwbVS2l$L)#IgQBV=F|Cxsu3vJ!uD(Xd8=^mPDazwbBcS*hw_Yw}}7o!$*ConDZ9 zVS0P3;WlUEyFsTMfWa2Cg;q#6*=PzQ94aC1kG;+f&Y5}SR6az~Qf}>2C4AqZzdB8h zL(7-zW-Bl8ckf}ZbO0kJZ$8{;H92?2M((r^-N@({N36IxWw%ka8KH$=1n4ZmflrnH zN*{Cv!^CKGOux**Csz&aFkeD2zFN^1Uf`ob>n&}OrQOMuo 4VX^s2%e^=xa+z!hq-7g6Y~*htlGst)A$-^3s9yTwZax zS82bvN5A^o8&%Zv_D;An+$$kOu{0k3mlZTvdi?PZvsbJtWBp$P+Aoc>e3v$p)7L$Z ztYtg=6B1Mo2>P5c&wOZ@UgK!?4*3UEDvQ${S5W$%GL7_f9ZM`xfuQOtC2#y42H !*}2#m9x!iD#=`mDTj6qngrAv zjfB{Q{M%xKL^JJyM|*Q~d^z!^CY!5=6IhwXpj<-#z3swFMT618Uizys@~t |Yx_1&k|diD2^J +-=4P&b{19WH>%%HTriDdluE zLwcs$nwWQ#uSq5{%8Umd0nOz3K!behj0v2Vu^PRx=hNOK{nr%hh57+MW-sAD)$_gb zPybU`{r}@k{cqM-N`bxE=BS|A;XDIO27j*?oV@ziaKLm;js|xHO&XYa4y6i^x&ffb zhs!1}50^RcF&f+P!z9Uu1E={w?X-|cM4kK7f6LHcu>ZG0(Qf~#G35V~?$qzIAB9>A U%}dpZ{sVq$Di80W0T zQL0oCP$C3KLR6NrA|fL~5+RZV5)uMrkdWkkz6l^B{I2W$ ec!1sU;}5@~--%c|_nIGnaq-uX*XrKo2mf$?Ha!`;=f~RQ^?ole-$MNtK?q3t zxoOTPd*M}Y9qa3VjbCMx*a)NEo5eW*R(|{or`rwu)5NA+FwMl|@RK+n@GlFi|KI$J zjZl`DnVDHxS*cd5i3oyLDdi3h7HO#_CTCL*57;mz96CXx+9aw8(G^oD=IlPXydd() zk?V7}4tz~~);q8AhPo%D)an5F6BCo*Kk6w5OA9T`c%`MKn9jMnwI#TgySBErT+fTY zZua8cCDw4cTwZTZE4UH)g~0p%k%I}d!X=-@MBh +gZPTw(6+2?A)Yqq%M(4}|8$xTO)NZ24+E{CZeAS*_G*@O(U5a@7CpfHR2MPnAzc9I-199-ImnU*2WkJ@DX@H?F8v%#dl z>M0vjQCWDMR-<&un(xeRv9$&gvt_&FjWZk`%x0?l`HU6% 7-a1p mKN75l1@|{^3S!e~k*sX> zZL(JI%e_Q4a*m0K*G$B7+a{j%p?DOrL~S0|)RYU)SJ-qM`L9;VWmyS>eRwZj5zW!9 zLsy&o&nVz*Fx{ThFaBT|6wKYJD+iYe_+X`6)ru*TP;7b5humJTaGMT4UD NHwChcd^R6L ;3H)D!R6sF!Pa{zs;x;sAtEWWwy8I#v{o#jqCMM6$ ztjC1BXo_`|;Es0lGNKac 5$wo_foAMcNKC)0)Ru3o$xU?ceao}Xh zb}4(t%v{NLIK{H(nbf`p-uZY%$;!S+L>updrFM|dAXXB#zPVp(*0w~1%g$}*7N?q- znDAZ-(rBS18um%HdIQ<#$?@=wmh|(d1D9JC_xCGptv9Oycl@@|@tVzA%x@GsM433Z z+d{G(K9GFM#N>U-Vj9U_^lUyMv5rT}^>p{(NK|6YtyHY+bqhP{v6Ri|0hg62PFS${ zUGytxMK>j*Io9qolV?%T%w5d+l9o2iDn2a)(N`VjP7tG5QM7-y=6;i3^c#xb!9=t* zHJ=&6iSr}r15Mn!m4wVwRTBH@CMG$T>z%Nrhn+O_RIICn=klx)Rz{KiaS%& l(1$fa^e+{!8xz@ z;XUfbxW5B*r&1LS_>N$9V|%^RBXJ_!Ws!+V;rH7m)H&6?uM64&B@WKoxTbpL&a-v| z?CU$~- |3{_=2XhZC-aMt$m%He#r!Gt37PGGu#8U=pTyPAFOvzOQ+d!dLX|0!D+wR zyGjag(~fR1F Y|as^pyYME>c}$+DmqwPQEQbyR~ (iB7O^Mlyl&I$Z|+Eu``ozldyD?GMKc%%Rhi^jG7UV<~}6|0$t z&T`A`U=}VV#At*ar?Cm+r74;(3j?yAv;e1iQtJs5wi5ZK*zaxiXEAR{i6r6?rJpXn z$wn)9k5^Vwb`XY?>xTMOaE4U+jJq` k z5^%%bo3d$eMR6?_(IiFw{W(zgzv|5$ngy&wF%!4oimAo| z*5Kq5SXd>NG<$2*FF!~f$ymuO3$_FMo-;LTEB$~XPJdvF!5&x1yQMTNVm|Q3_v6?P zdT_f!G&`QW!<^a#-1Tf;)7v< zDQa>4Iox28tc$6C5A1 zU?y{Zo|UiL%XDbyg&@s7BgvENxowLa{AgroErwq!=xkCRxH38)8j52_Zom|6D%rns zWWIjZ|JBk5-wSYfd =-xE%&hGfdO}C?U}{2wl6r}fBlhXfWx1 ueP0vQZOv>2|D z37eI!Zf $E-GqirbH+dVtmE8yCT( 0`yDk45|M8NY3UoR z$1&+;`oTJZw